I want to listen for sensor data in a @Composable
and cancel listening for sensor data when returning to the previous interface.
So I wrote the following code:
PresetsList {
navigator.navigate(
PresetsEditorDestination(
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
presetsModel = it
)
)
}
PresetsList
contains multiple jumpable PresetsEditor
interfaces
@OptIn(ExperimentalLifecycleComposeApi::class)
@Destination
@Composable
fun PresetsEditor(
orientation: Int,
presetsModel: PresetsModel,
viewModel: EditorViewModel = hiltViewModel()
) {
val context = LocalContext.current
val systemUiController = LocalSystemUiController.current
val dialogState by viewModel.dialogState.collectAsState()
val steeringValue by viewModel.sensorFlow.collectAsStateWithLifecycle()
systemUiController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
DisposableEffect(Unit) {
// Force this @Composable to be landscape and hide the statusBar.
val activity = context.findActivity() ?: return@DisposableEffect onDispose {}
val originalOrientation = activity.requestedOrientation
activity.requestedOrientation = orientation
systemUiController.isSystemBarsVisible = false
onDispose {
activity.requestedOrientation = originalOrientation
systemUiController.isSystemBarsVisible = true
viewModel.stopListeningSensor() // stop listening sensor.
}
}
println("steeringValue: $steeringValue")
EditorContent(
presetsModel,
dialogState,
viewModel::onClickLabel,
viewModel::onDismissRequest
)
}
viewModel.kt
@HiltViewModel
class EditorViewModel @Inject constructor(
private val sensor: AbstractSensor
) : ViewModel(){
private val _sensorFlow = MutableStateFlow(0f)
val sensorFlow = _sensorFlow.asStateFlow()
init {
sensor.startListening()
sensor.setOnSensorValuesChangedListener {
_sensorFlow.value = it
}
}
}
But I encountered a problem. When I jump to this @Composable
from other interfaces, the code in my onDispose
will run the stopListeningSensor
method once, which will cause my sensor data to end shortly after I start listening. So I'm a little lost on how to use DisposableEffect
to solve this problem.
CodePudding user response:
You're reading the value of steeringValue
inside your Composable, which is a value bound to change, as you reveal. What's the invisible issue here? Obviously the change in the value will trigger a recomposition leading to onDispose
being called, which is the expected behaviour. If all you want is to run a codeblock when the Composable goes out-of-composition, then it would be easier to place that codeblock at the place where you make the navigation call. It might be a little risky to place it within the Composable without proper knowledge and handling of scopes and side-effects. One way I see to add it within the Composable while taking care of all of that, is to use the BackHandler
Composable. It receives a lambda that would only be executed when the user presses the back button.
BackHandler { ... }
Move the `onDispose logic to one of these blocks, and it should work.
There's no way to know when a Composable goes out-of-composition, so you're only bet is to place the logic in a custom-handled event, like the back press, or when the app is pushed to the background, using onStop
, or to be the most accurate, place the call at the place where you call the navigation method, which is the architecture most normal developers would go with. You probably won't be able to perform the latter since you're using an out-of-earth library for navigation, so... Yeah, good luck to you sir/lady.
CodePudding user response:
I didn't describe my problem clearly, although I wrote comments in the code. My problem is jumping from a normal @Composable (in portrait) to a @Composable in landscape and after the jump starts listening for sensor data.
The reason why onDispose
is triggered once when entering @Composable is actually because configuration
is changed, and the life cycle of Activity is executed once ON_STOP
(because of the change of screen orientation)
The solution is also simple: remove the init block in my viewModel as it doesn't need such a long life cycle (eg when the app is in the background, no need to listen for sensor data), and start listening for sensor data in the DisposableEffect
block.
val activity = LocalContext.current as MainActivity
DisposableEffect(Unit) {
// Force this @Composable to be landscape and hide the statusBar.
val originalOrientation = activity.requestedOrientation
activity.requestedOrientation = orientation
systemUiController.isSystemBarsVisible = false
viewModel.startListeningSensor()
onDispose {
activity.requestedOrientation = originalOrientation
systemUiController.isSystemBarsVisible = true
viewModel.stopListeningSensor() // stop listening sensor.
}
}
Finally, I'd like to say to Richard Onslow Roper that the navigation library I'm using is fine, it's just that I stupidly didn't think that the lifecycle caused by screen changes affects the DisposableEffect (it does exactly what Richard Onslow Roper said in the first comment, My @Composable is destroyed) and doesn't clearly describe what's going on in my code.