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

その 2 今度は LiveData を使ってみます。

  • ブロードキャストを送信する
  • LiveDataを使う
  • Messanger を使う
  • LiveData を使うならこんな感じ

    package red.txn.service_livedata
    
    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 red.txn.service_livedata.ui.theme.ServicelivedataTheme
    
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            enableEdgeToEdge()
            setContent {
                ServicelivedataTheme {
                    Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                        Greeting(
                            name = "Android",
                            modifier = Modifier.padding(innerPadding)
                        )
                    }
                }
            }
    
            // launch service
            Intent(this, LiveDataService::class.java).also { intent ->
                bindService(intent, connection, Context.BIND_AUTO_CREATE)
            }
        }
    
        private var bound = false
        private lateinit var liveDataService: LiveDataService
    
        private val connection = object : ServiceConnection {
            override fun onServiceConnected(name: ComponentName?, service: IBinder) {
                val binder = service as LiveDataService.MyBinder
                liveDataService = binder.getService()
                bound = true
                observeData()
            }
            override fun onServiceDisconnected(name: ComponentName?) {
                bound = false
            }
        }
    
        fun observeData() {
            liveDataService.BUS.observe(this) { newData ->
                Log.d("AAA", "Activity:" + newData.toString())
            }
        }
    }
    
    
    //***
    //*** Composable functions
    //***
    @Composable
    fun Greeting(name: String, modifier: Modifier = Modifier) {
        Text(
            text = "Hello $name!",
            modifier = modifier
        )
    }
    
    @Preview(showBackground = true)
    @Composable
    fun GreetingPreview() {
        ServicelivedataTheme {
            Greeting("Android")
        }
    }

    bindService() でサービス起動をしているので少し様子が異なるが、バインドしたとき observeData() で監視している。とっても簡単

    package red.txn.service_livedata
    
    import android.app.Service
    import android.content.Intent
    import android.os.Binder
    import android.os.IBinder
    import android.util.Log
    import androidx.lifecycle.MutableLiveData
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.launch
    
    class LiveDataService : Service() {
        companion object {
            const val TAG = "LiveDataService"
        }
    
        val BUS = MutableLiveData<String>()
    
        override fun onBind(intent: Intent): IBinder {
            Log.d(TAG, "serviee onBind()")
            Thread {
                for (i in 1..2) {
                    Thread.sleep(2000)
                    updateData("データ$i")
                    Log.d(TAG, "データ$i")
                }
            }.start()
    
            val scope = CoroutineScope(Dispatchers.IO)
            scope.launch {
                for (i in 1..2) {
                    Thread.sleep(2000)
                    updateData("データ2$i")
                    Log.d(TAG, "データ2$i")
                }
            }
            return MyBinder()
        }
        inner class MyBinder: Binder() {
            fun getService(): LiveDataService = this@LiveDataService
        }
    
        private fun updateData(newData: String) {
            BUS.postValue(newData)
        }
    }

    AndroidManifest.xml は生成されたものなので省略。

    startService() でサービス起動したい場合は、こんなふうになった。

    package red.txn.service_livedata
    
    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 red.txn.service_livedata.ui.theme.ServicelivedataTheme
    
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            enableEdgeToEdge()
            setContent {
                ServicelivedataTheme {
                    Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                        Greeting(
                            name = "Android",
                            modifier = Modifier.padding(innerPadding)
                        )
                    }
                }
            }
    
            // launch service
            Intent(this, LiveDataService::class.java).also { intent ->
                startService(intent)
            }
            LiveDataService.BUS.observe(this) {
                Log.d("BUS change:", "Activity:" + it.toString())
            }
        }
    }
    
    
    //***
    //*** Composable functions
    //***
    @Composable
    fun Greeting(name: String, modifier: Modifier = Modifier) {
        Text(
            text = "Hello $name!",
            modifier = modifier
        )
    }
    
    @Preview(showBackground = true)
    @Composable
    fun GreetingPreview() {
        ServicelivedataTheme {
            Greeting("Android")
        }
    }

    サービスの方では LiveData を companion object に入れてインスタンスなしでも取得できるようにしている。

    package red.txn.service_livedata
    
    import android.app.Service
    import android.content.Intent
    import android.os.Binder
    import android.os.IBinder
    import android.util.Log
    import androidx.lifecycle.MutableLiveData
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.launch
    
    class LiveDataService : Service() {
        companion object {
            const val TAG = "LiveDataService"
            val BUS = MutableLiveData<String>()
        }
    
        override fun onBind(intent: Intent): IBinder? {
            return null
        }
    
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            super.onStartCommand(intent, flags, startId)
    
            Thread {
                for (i in 1..2) {
                    Thread.sleep(2000)
                    BUS.postValue("newData: $i")
                }
            }
    
            return START_STICKY
        }
    }

    Gemini を使ってみているが、前の文脈からの続きの質問したら流れをぶった切って新しい話題のように回答するのやめてほしい。Claude のほうがその辺楽かも。