Development record of developer who study hard everyday.

레이블이 SideEffect인 게시물을 표시합니다. 모든 게시물 표시
레이블이 SideEffect인 게시물을 표시합니다. 모든 게시물 표시
, , ,

SideEffect in Android Composable(collectAsStateWithLifecycle, LaunchedEffect, rememberCoroutineScope)

 SideEffect in Android Composable


Android developer blog

✋✋✋ SideEffect

A side-effect in Compose is a change to the state of the app that happens outside the scope of a composable function.

For example, opening a new screen when the user taps on a button, or showing a message when the app doesn't have Internet connection.


1. collectAsStateWithLifecycle

dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.0"
}

First, add dependency of "androidx.lifecycle:lifecycle-runtime-compose"


@OptIn(ExperimentalMaterialApi::class)
@Composable
fun CraneHomeContent(
onExploreItemClicked: OnExploreItemClicked,
openDrawer: () -> Unit,
modifier: Modifier = Modifier,
viewModel: MainViewModel = viewModel(),
) {
// TODO Codelab: collectAsStateWithLifecycle step - consume stream of data from the ViewModel
// val suggestedDestinations: List<ExploreModel> = remember { emptyList() }
val suggestedDestinations by viewModel.suggestedDestinations.collectAsStateWithLifecycle()

val onPeopleChanged: (Int) -> Unit = { viewModel.updatePeople(it) }
var tabSelected by remember { mutableStateOf(CraneScreen.Fly) }

BackdropScaffold(
modifier = modifier,
scaffoldState = rememberBackdropScaffoldState(BackdropValue.Revealed),
frontLayerScrimColor = Color.Unspecified,
appBar = {
HomeTabBar(openDrawer, tabSelected, onTabSelected = { tabSelected = it })
},
backLayerContent = {
SearchContent(
tabSelected,
viewModel,
onPeopleChanged
)
},
frontLayerContent = {
when (tabSelected) {
CraneScreen.Fly -> {
ExploreSection(
title = "Explore Flights by Destination",
exploreList = suggestedDestinations,
onItemClicked = onExploreItemClicked
)
}
CraneScreen.Sleep -> {
ExploreSection(
title = "Explore Properties by Destination",
exploreList = viewModel.hotels,
onItemClicked = onExploreItemClicked
)
}
CraneScreen.Eat -> {
ExploreSection(
title = "Explore Restaurants by Destination",
exploreList = viewModel.restaurants,
onItemClicked = onExploreItemClicked
)
}
}
}
)
}

Second, in CrameHomeContent composable, you can consume the list of destinations as form of state using collectAsstateWithLifecycle() method.


💪💪💪 Compose also offers APIs for Android's most popular stream-based solutions:

- LiveData.observeAsState() in "androidx.compose.runtime:runtime-livedata"

- Observable.subscribeAsState() in "androidx.compose.runtime:runtime-rxjava2"


2. LaunchedEffect and rememberUpdatedState

To call suspend functions safely from inside a composable, use the LaunchedEffect API, which triggers a coroutine scoped side-effect in Compose.

When LaunchedEffect enters the Composition, it launches a coroutine with the block of code passed as a key parameter.

The coroutine will be canceled if LaunchedEffect leaves the composition.


LaunchedEffect take a variable number of keys as a parameter that are used to restart the effect whenever one of those keys changes.

@Composable
fun LandingScreen(onTimeout: () -> Unit, modifier: Modifier = Modifier) {
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
val currentOnTimeOut by rememberUpdatedState(onTimeout)    //1

LaunchedEffect(key1 = Unit) {    //2
delay(SplashWaitTime)
onTimeout.invoke()
}
Image(painterResource(id = R.drawable.ic_crane_drawer), contentDescription = null)
}
}

1 => The rememberUpdatedState() make us refer to the latest onTimeout function that LandingScreen was recomposed with

2 => Create an coroutine scope that matches the lifecycle of LandingScreen.

Because the key parameter is Unit that is not changed, the delay shouldn't start again.


3. rememberCoroutineScope

API to open the navigation drawer is suspend function.

How do we call suspend function in callback function?

@Composable
fun CraneHome(
onExploreItemClicked: OnExploreItemClicked,
modifier: Modifier = Modifier,
) {
val scaffoldState = rememberScaffoldState()
Scaffold(
scaffoldState = scaffoldState,
modifier = Modifier.statusBarsPadding(),
drawerContent = {
CraneDrawer()
}
) { padding ->
val scope = rememberCoroutineScope()
CraneHomeContent(
modifier = modifier.padding(padding),
onExploreItemClicked = onExploreItemClicked,
openDrawer = {
scope.launch {
scaffoldState.drawerState.open()
}
}
)
}
}

We want a CoroutineScope that follows the lifecycle of its call-site.

The rememberCoroutineScope API returns a CoroutineScope bound to the point in the Composition where you call it.

The scope will be automatically canceled once it leaves the Composition.


✊✊✊ LaunchedEffect vs rememberCoroutineScope

Using LauncedEffect is not possible to use in regular callback which is outside of the Composition.

Both can produce CoroutineScope but, LaunchedEffect can control when the side-effect would be called.


Share:
Read More