Android でサービスと通信する-Channel編

サービスと通信するサンプル 3 部作の追加編です。で Flow (Channel) を使用してみます。

3 部作はこちら

  • ブロードキャストを送信する
  • LiveDataを使う
  • Messanger を使う
  • そもそも、なぜ上の3手法を試したのかというと、Claude か Gemini か ChatGTP に「サービスの状態をアクティビティに通知するにはどういう方法がある?」って聞いた結果、3 つの方法が回答にあったのがキッカケだった。

    でサンプルを探して作っていくうちに、Flow をイベントバスに使う方法もあるらしいということを探し当てたのでやってみる。

    MainActivity はこんな感じになりました。

    package red.txn.service_flow
    
    import android.content.ComponentName
    import android.content.Context
    import android.content.Intent
    import android.content.ServiceConnection
    import android.os.Bundle
    import android.os.IBinder
    import android.util.Log
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.activity.enableEdgeToEdge
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.padding
    import androidx.compose.material3.Scaffold
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.tooling.preview.Preview
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.launch
    import red.txn.service_flow.ui.theme.ServiceflowTheme
    
    class MainActivity : ComponentActivity() {
        companion object {
            const val TAG = "MainActivity"
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            enableEdgeToEdge()
            setContent {
                ServiceflowTheme {
                    Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                        Greeting(
                            name = "Android",
                            modifier = Modifier.padding(innerPadding)
                        )
                    }
                }
            }
    
            // launch service
            Intent(this, FlowService::class.java).also { intent ->
                startService(intent)
            }
            Log.d(TAG,"service launched")
    
            val scope = CoroutineScope(Dispatchers.IO)
            scope.launch {
                FlowService.BUS.collect {
                    Log.d(TAG, "Received data: $it")
                }
            }
        }
    }
    
    @Composable
    fun Greeting(name: String, modifier: Modifier = Modifier) {
        Text(
            text = "Hello $name!",
            modifier = modifier
        )
    }
    
    @Preview(showBackground = true)
    @Composable
    fun GreetingPreview() {
        ServiceflowTheme {
            Greeting("Android")
        }
    }

    LiveData の observe が Flow の cllect に変わった感じで極端な変化はないですね。わかりやすい。

    サービスのインスタンスなしでアクセスできているのは BUS がサービスの中で companion ojbect として定義されているからです。(理解あってる?)

    package red.txn.service_flow
    
    import android.app.Service
    import android.content.Intent
    import android.os.Binder
    import android.os.IBinder
    import android.util.Log
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.channels.Channel
    import kotlinx.coroutines.flow.MutableStateFlow
    import kotlinx.coroutines.flow.asSharedFlow
    import kotlinx.coroutines.flow.receiveAsFlow
    import kotlinx.coroutines.launch
    
    class FlowService : Service() {
        companion object {
            val TAG = "FlowService"
            private var _BUS = Channel<String>()
            val BUS = _BUS.receiveAsFlow()
        }
    
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            Log.d(TAG, "onBind")
            Thread {
                for (i in 1..2) {
                    Thread.sleep(2000)
                    updateData("データ$i")
                    Log.d(TAG, "データ$i")
                }
            }.start()
    
            return START_STICKY
        }
    
        override fun onBind(intent: Intent): IBinder? {
            return null
        }
    
        fun updateData(data: String) {
            val scope = CoroutineScope(Dispatchers.IO)
            scope.launch {
                _BUS.send(data)
            }
        }
    }

    BUS と _BUS は companion object から普通のメンバに変えてもいいかも。

    AIチャットボットの回答で漏れていた方法をたまたま見つけたわけですが、現時点では AI の回答が網羅性があり、正しい(妥当か)を判断するには読み取る人間側がそもそもその答え知らないといけない例として覚えておきたい。

    AIチャットボットに限らず検索の回答から漏れているものが認識されない、つまり存在しないことにされてしまうのはとても考えさせられると感じてしまう。

    参考

    LiveDataをFlowにリプレースしてみて得た知見(StateFlow、SharedFlow、Channel) #Android – Qiita