Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpackandroid-architecture-components

State Holder in jetpack compose


I am learning in State in jetpack compose. I found that State holders as source of truth. So created my some data can you guys guide me if I am doing wrong here.

PairViewModel.kt

class PairViewModel : ViewModel() {

    var isBluetoothEnabled = mutableStateOf(false)
        private set

    fun onBluetoothEnable(value: Boolean) {
        isBluetoothEnabled.value = value
    }
}

PairScreen.kt

class PairScreenState(context: Context, viewModel: PairViewModel) {

    private val bluetoothManager: BluetoothManager = context.getSystemService(BluetoothManager::class.java)
    private val bluetoothAdapter: BluetoothAdapter by lazy {
        bluetoothManager.adapter
    }

    init {
        viewModel.onBluetoothEnable(bluetoothAdapter.isEnabled)
    }

    fun checkBluetoothStatus(bluetoothStatus: MutableState<Boolean>): BroadcastReceiver {
        return object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                if (intent?.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
                    when (intent.getIntExtra(
                        BluetoothAdapter.EXTRA_STATE,
                        BluetoothAdapter.ERROR
                    )) {
                        BluetoothAdapter.STATE_OFF -> {
                            bluetoothStatus.value = false
                        }
                        BluetoothAdapter.STATE_ON -> {
                            bluetoothStatus.value = true
                        }
                    }
                }
            }
        }
    }

}

@Composable
fun rememberPairScreenState(
    context: Context,
    viewModel: PairViewModel
) = remember {
    PairScreenState(context, viewModel)
}

@Composable
fun PairContent(
    context: Context = LocalContext.current,
    viewModel: PairViewModel = getViewModel(),
    rememberPairScreenState: PairScreenState = rememberPairScreenState(context, viewModel),
) {
    AnimatedVisibility(visible = true) {
        AppBarScaffold() {
            Column(
                modifier = Modifier
                    
                    .fillMaxSize()
                    .verticalScroll(rememberScrollState())
            ) {
                rememberPairScreenState.checkBluetoothStatus(viewModel.isBluetoothEnabled).apply {
                    context.registerReceiver(this, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
                }
                if (viewModel.isBluetoothEnabled.value) {
                    println(">> Enable >>>")
                } else {
                    println(">> Disable >>>")
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun PairContentPreview() {
    PairContent()
}

I am using Bluetooth as example to understand state holder in my use case. Please guide me if you find anything wrong in my code. Thanks


Solution

  • Ill try my best here, I get where you're coming from, having a code that it's hard to verify if its the proper way of doing "yet", regardless of how many source materials you review like in github, sometimes references just doesn't exist yet right?

    For State hoisting/handling, its good to follow the principles coming from the community. So the way I handle State Hoisting, is thinking of its purpose

    So if its just something that needs to be local within the @Composable

    remember {...}
    

    If its something that deals with multiple logic and values, State class

    class PersonState(val personParam: Person) {
            .....
    }
    
    @Composable 
    fun rememberPersonState(person: Person) = remember(key1= person) {
           PersonState(person)
    }
    

    If its something that deals with repository, network calls, use-cases where persistence is a major part of the requirement, ViewModel, and lifecyle is something you have to be aware of. ViewModel

    class PersonScreenViewModel {
         /..RepositoryStateFlows../
         /..Data structural updates../
    }
    

    So far this mindset and approach helped me a bit when deciding how would I hoist my states.

    As for your PairScreenState, consider this use-case solution coming from this post Detect if Soft Keyboard is Open or Close, where you can detect if the keyboard is open or not

    I would have your BlueTooth usecase where I would implement it as a Composable utility function and returns a State where I can define a DisposableEffect, though this code is not working but I think you'll get my point here.

    enum class BlueTooth {
        ON, OFF
    }
    
    @Composable
    fun BlueToothAsState(): State<BlueTooth> {
        val blueToothState = remember { mutableStateOf(BlueTooth.OFF) }
        DisposableEffect(view) {
            var mReceiver : BroadcastReceiver? = object : BroadcastReceiver()  {
                    /.../
                    when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,  BluetoothAdapter.ERROR)) {
                            BluetoothAdapter.STATE_OFF -> {
                                blueToothState = BlueTooth.OFF
                            }
                            BluetoothAdapter.STATE_ON -> {
                                blueToothState = BlueTooth.ON
                            }
                        }
                    }
               }
            onDispose {
                mReceiver = null
            }
        }
    
        return blueToothState
    }
    

    As for the other parts of the code, I don't think you need it here if its just always set to true

     AnimatedVisibility(visible = true)