Home > database >  Proper handle of variable/function according to lifecycle in jetpack compose
Proper handle of variable/function according to lifecycle in jetpack compose

Time:11-25

I want to change variable value or call function according to lifecycle in jetpack compose. I tried according to this answer. Also read from the doc. I did this without any problem.

I have @Composable function

@Composable
fun BluetoothConnectionContentStateful(
    context: Context = LocalContext.current,
    viewModel: PairViewModel = getViewModel()
) {
    var selectedIndexOfAvailableItem by remember { mutableStateOf(DEVICE_NOT_SELECTED_INDEX) }
    val rememberPairScreenState = rememberConnectionScreenState(context, viewModel)
    val bluetoothEnableState by remember { derivedStateOf { viewModel.isBluetoothEnabled } }
    LaunchedEffect(key1 = bluetoothEnableState) {
        viewModel.handleBluetoothScanState(rememberPairScreenState.bluetoothAdapter)
    }

    LaunchedEffect(key1 = selectedIndexOfAvailableItem) {
        viewModel.handleTimeWarning(selectedIndexOfAvailableItem)
    }

    ComposableLifecycle { source, event ->
        if (event == Lifecycle.Event.ON_PAUSE) {
             viewmodel.stopScan()
        } else if (event == Lifecycle.Event.ON_RESUME) {
          viewModel.handleBluetoothScanState(rememberPairScreenState.bluetoothAdapter)
        }
    }
}

ComposableLifecycle

@Composable
fun ComposableLifecycle(
    lifeCycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onEvent: (LifecycleOwner, Lifecycle.Event) -> Unit
) {
    DisposableEffect(lifeCycleOwner) {
        val observer = LifecycleEventObserver { source, event ->
            onEvent(source, event)
        }
        lifeCycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifeCycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

PairViewModel

class PairViewModel : BaseViewModel() {

    companion object {
        private const val WARNING_PERIOD_IN_MILLES: Long = 60000 // For 1 minute scan
        const val DEVICE_NOT_SELECTED_INDEX = -1
    }

    var isBluetoothEnabled by mutableStateOf(false)
        private set

    private var job = Job()
        get() {
            if (field.isCancelled) field = Job()
            return field
        }

    fun startScan(adapter: BluetoothAdapter) = viewModelScope.launch {
        // start scan
    }

    fun stopScan(adapter: BluetoothAdapter) {
       // stop scan
    }


    fun handleBluetoothScanState(bluetoothAdapter: BluetoothAdapter) {
        if (isBluetoothEnabled) {
            startScan(bluetoothAdapter)
        } else {
            cancelTimeWarning()
            stopScan(bluetoothAdapter)
        }
    }


    fun handleTimeWarning(selectedIndexOfAvailableItem: Int) {
        if (selectedIndexOfAvailableItem == DEVICE_NOT_SELECTED_INDEX) {
            startTimeWarning()
        } else {
            cancelTimeWarning()
        }
    }

    fun startTimeWarning() {
        viewModelScope.launch(job) {
            delay(WARNING_PERIOD_IN_MILLES)
            // make event here
        }
    }

    internal fun cancelTimeWarning() {
        if (job.isActive) {
            job.cancel()
        }
        // make event here
    }
}

Note bluetoothEnableState is Bluetooth ON and OFF i.e. true and false respectively.

I am listening bluetoothEnableState using LaunchedEffect when bluetooth state change ON or OFF. This is working fine. Now the problem occur,

1. when activity start the viewModel.handleBluetoothScanState(rememberPairScreenState.bluetoothAdapter) is called two times on onResume and LaunchedEffect.

2. when user disable bluetooth it state change to false and he goes to onPause. After coming back onResume handleBluetoothScanState will call and also change the bluetooth state to true then again call the LaunchedEffect.

I will explain more detail of 2nd condition

On Resume             Close App   Open App
Bluetooth disable ->  On Pause -> OnResume -> Bluetooth enable
      |                              |            |
     \ /                            \ /          \ /
Launcheffect                      function     Launcheffect 

So I want to prevent to call double time function so any idea how can we solve this? Thanks

CodePudding user response:

I don't understand your use-case and I can't compile your code, but since you only want to startScan when blueTooth is ON and stopScan when blueTooth is OFF, I think you don't need to handle the same call/logic in your onResume

// I don't think you need it here,
else if (event == Lifecycle.Event.ON_RESUME) {
      viewModel.handleBluetoothScanState(rememberPairScreenState.bluetoothAdapter)
}

since LaunchedEffect already reacts to bluetooth state changes ON(true)/OFF(false) and its already calling the same function in your ViewModel.

  • Related