2
\$\begingroup\$

I'm working on a stateful Jetpack Compose composable that follows the MVI architecture. I'm using collectAsStateWithLifecycle() to observe the view state from a view model, but I'm unsure about the best approach to collect side effects with lifecycle awareness.

I'd appreciate your insights on:

  • The correctness of the current approach.

  • Potential improvements or alternative techniques.

  • Best practices for collecting viewstate and side effects with lifecycle awareness in Jetpack Compose.

    @Composable
        fun CountryListScreenContent(callback: (countryName: String) -> Unit) {
            val viewModel: CountryListViewModel = hiltViewModel()
            val countryListViewState by viewModel.viewState.collectAsStateWithLifecycle()
            LaunchedEffect(Unit) {
                viewModel.sideEffect.collect {
                    if (it is CountryListContract.SideEffect.NavigateToDetails) {
                        val countryName = it.countryName
                        callback(countryName)
                    }
                }
            }
            CountryListViewState(viewState = countryListViewState, callback = { it ->
                viewModel.sendIntent(
                    ...
                )
            })
        }   
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Does your CountryListViewModel itself have any access to a CoroutineScope? How do you create it? What does the callback do? It looks a bit to me like the callback-related code belongs in the viewmodel instead of in your compose code. \$\endgroup\$ Commented Feb 4 at 23:08

1 Answer 1

2
\$\begingroup\$

I have a composable for handling lifecycle events

@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)
        }
    }
}

and Its usage is as follows:


@Composable
fun HomeScreen(
    context: Context = LocalContext.current,
    viewModel: MainViewModel = koinViewModel(),
    navController: NavController
) {
//...
    val isLoading by viewModel.isLoading.collectAsState()

    ComposableLifecycle { _, event ->
        val TAG = "HomeScreen LC Event"
        when (event) {
            Lifecycle.Event.ON_START -> {
                Timber.tag(TAG).d("HomeScreen On Start")
                viewModel.doStuff()
                //...
            }
            Lifecycle.Event.ON_RESUME -> {
                Timber.tag(TAG).d("HomeScreen On Resume")
                //...
            }

            else -> {}
        }
    }


    Box(modifier = Modifier
        .fillMaxSize()){
        //...
    }
}

This is an alternative approach that I have been using for a while now. This is only for lifecycle events management code and the side effect code of yours is handled correctly as far as I can see. Basically this is a better approach to handle Lifecycle Events within composable functions. As in MVI it is in best Practices to handle lifecycle events and side effects inside the reactive UI components, in this case composables.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. \$\endgroup\$ Commented Apr 20 at 14:55

Not the answer you're looking for? Browse other questions tagged or ask your own question.