0

I want to use service in jetpack compose. I am trying to bind services in compose app but it always return null to me. I tried this stack overflow. But it didn't work to me. Anyone guide me on this ?

import android.app.Service
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Binder
import android.os.IBinder
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext

@Composable
inline fun <reified BoundService : Service, reified BoundServiceBinder : Binder> rememberBoundLocalService(
    crossinline getService: @DisallowComposableCalls BoundServiceBinder.() -> BoundService,
): BoundService? {
    val context: Context = LocalContext.current
    var boundService: BoundService? by remember(context) { mutableStateOf(null) }
    val serviceConnection: ServiceConnection = remember(context) {
        object : ServiceConnection {
            override fun onServiceConnected(className: ComponentName, service: IBinder) {
                boundService = (service as BoundServiceBinder).getService()
            }

            override fun onServiceDisconnected(arg0: ComponentName) {
                boundService = null
            }
        }
    }
    DisposableEffect(context, serviceConnection) {
        context.bindService(Intent(context, BoundService::class.java), serviceConnection, Context.BIND_AUTO_CREATE)
        onDispose { context.unbindService(serviceConnection) }
    }
    return boundService
}

BluetoothService.kt

class BluetoothService : Service() {

    companion object {
        const val ACTION_GATT_CONNECTED =
            "com.abc.app.ACTION_GATT_CONNECTED"
        const val ACTION_GATT_DISCONNECTED =
            "com.abc.app.ACTION_GATT_DISCONNECTED"

        private const val STATE_DISCONNECTED = 0
        private const val STATE_CONNECTED = 2
    }

    private var connectionState = STATE_DISCONNECTED
    private var bluetoothAdapter: BluetoothAdapter? = null
    private val binder = LocalBinder()
    private var bluetoothGatt: BluetoothGatt? = null
    private val bluetoothGattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                // successfully connected to the GATT Server
                connectionState = STATE_CONNECTED
                broadcastUpdate(ACTION_GATT_CONNECTED)
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                // disconnected from the GATT Server
                connectionState = STATE_DISCONNECTED
                broadcastUpdate(ACTION_GATT_DISCONNECTED)
            }
        }
    }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }

    inner class LocalBinder : Binder() {
        fun getService(): BluetoothService {
            return this@BluetoothService
        }
    }

    fun initialize(adapter: BluetoothAdapter?): Boolean {
        bluetoothAdapter = adapter
        if (bluetoothAdapter == null) {
            logE(">> Unable to obtain a BluetoothAdapter.")
            return false
        }
        return true
    }

    fun connect(address: String): Boolean {
        bluetoothAdapter?.let { adapter ->
            try {
                val device = adapter.getRemoteDevice(address)
                // connect to the GATT server on the device
                bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
                return true
            } catch (exception: IllegalArgumentException) {
                logE("Device not found with provided address.")
                return false
            }
        } ?: run {
            logE("BluetoothAdapter not initialized")
            return false
        }
    }

    override fun onUnbind(intent: Intent?): Boolean {
        close()
        return super.onUnbind(intent)
    }

    private fun close() {
        bluetoothGatt?.let { gatt ->
            gatt.close()
            bluetoothGatt = null
        }
    }

    private fun broadcastUpdate(action: String) {
        val intent = Intent(action)
        sendBroadcast(intent)
    }
}

I am trying to use bluetoothService it always return null when I check in the condition

BluetoothConnectionContentStateful.kt

@OptIn(ExperimentalLifecycleComposeApi::class)
@Composable
fun BluetoothConnectionContentStateful(
    context: Context = LocalContext.current,
    viewModel: BloodPressurePairViewModel = getViewModel(),
    router: Router = get()
) {
    val activity = context as ComponentActivity
    val bluetoothService = rememberBoundLocalService<BluetoothService, BluetoothService.LocalBinder> { getService() }
    val scanDeviceList = viewModel.scanLeDevices.collectAsStateWithLifecycle()
    if (bluetoothService != null) {
        if (!bluetoothService.initialize(rememberPairScreenState.bluetoothAdapter)) {
            activity.logE(">> Unable to initialize Bluetooth")
        } else {
            activity.logE(">> initialize Bluetooth")
        }
    }else{
        activity.logE(">> bluetoothService null")
    }
}

bluetoothService always return null. Any idea about that?

Kotlin Learner
  • 3,995
  • 6
  • 47
  • 127
  • I strongly recommend that you bind from something other than a composable, such as from a viewmodel or repository. Or, as in this case, get rid of the service, as you have no need for one. The point behind a service is to allow your app to do work when it is not in the foreground, and that does not appear to be how you are using it. – CommonsWare Oct 11 '22 at 23:44
  • Sorry I didn't catch after `viewmodel or repository`. Can you explain me in more details? – Kotlin Learner Oct 11 '22 at 23:47
  • I do not know why `BluetoothService` exists. I recommend that you move that logic into a viewmodel (or possibly a repository). You will have simpler, more efficient, and more reliable code as a result. You do not need a service for what you are doing. – CommonsWare Oct 11 '22 at 23:50
  • I am building a bluetooth low energy application. According to [official documents](https://developer.android.com/guide/topics/connectivity/bluetooth) `BluetoothService` is recommended. – Kotlin Learner Oct 11 '22 at 23:54
  • Can you please give me solution how can I move logic to viewmodel ? – Kotlin Learner Oct 11 '22 at 23:55
  • "According to official documents BluetoothService is recommended" -- I assume that you are referring to [this page](https://developer.android.com/guide/topics/connectivity/bluetooth/connect-gatt-server). That documentation is incomplete and inaccurate. There *can* be a need for a service, but only if your app will be communicating with BLE devices in the background (and then it cannot purely be a bound service). Your implementation specifically blocks doing the BLE communications in the background, and so you do not need a service. – CommonsWare Oct 11 '22 at 23:59
  • Yes I am following your mention page. To be honest I don't know about that document is incomplete. I am just following what they mention. Do you have any sample or BLE device so I can work on that ? Thanks – Kotlin Learner Oct 12 '22 at 00:04

1 Answers1

0
  • It is not a good practice to bind the composable with service
  • Composable should only involve the presentation layer that listeners for the states in the view model
  • You can bind the composable to ViewModel or a repository and listen to the states in composable

In this project I can calling a API as a service, you can substitute with your service - Github link of source code and implementation

Devrath
  • 42,072
  • 54
  • 195
  • 297