3

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

Kotlin Learner
  • 3,995
  • 6
  • 47
  • 127

1 Answers1

4

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)
z.g.y
  • 5,512
  • 4
  • 10
  • 36
  • What is your option on using a StateHolder class in conjunction with a ViewModel? In a application I was working in, I had my business logic hoisted into the ViewModel but logic relating to the UI such as if a field should be hidden or holding other composable state objects like ScrollState, ScaffoldState. I like this design because UI rules aren't bleeding into the viewModel; therefore the VM are more pure and UI framework agnostic. I feel this design would help support Views or Jetpack Compose. I haven't seen any examples using this design so I wonder if it's not recommended. – cincy_anddeveloper Feb 28 '23 at 14:38