I am building a simple app which has a TabLayout with two tabs. One tab is a list of items which you can click and make a note about the item. The other tab is a list of notes you made in the first tab.
I have created a TabLayout using TabRows and HorizontalPager here is the code for that section:
@OptIn(ExperimentalPagerApi::class)
@Composable
fun HorizontalPagerTabLayout(
modifier: Modifier
) {
val tabData = listOf("Records" to Icons.Default.Phone, "Notes" to Icons.Default.Star)
val pagerState = rememberPagerState(
initialPage = 0
)
val tabIndex = pagerState.currentPage
val coroutineScope = rememberCoroutineScope()
Column {
TabRow(
selectedTabIndex = tabIndex,
modifier = modifier
) {
tabData.forEachIndexed { index, _ ->
Tab(
icon = {
Icon(imageVector = tabData[index].second, contentDescription = null)
},
selected = tabIndex == index,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
text = {
Text(
tabData[index].first,
color = if (pagerState.currentPage == index) Color.White else Color.LightGray
)
})
}
}
HorizontalPager(state = pagerState, count = tabData.size) { page ->
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
when (page) {
0 -> RecordsScreen(modifier = Modifier)
1 -> NotesScreen(modifier = Modifier)
}
}
}
}
}
In RecordScreen I have a logic that will make a Note about the Record and store it in RoomDB. That works as expected.
In NotesScreen I have a viewModel that will pull all the notes from RoomDB and diplay them in the NotesScreen tab.
Here is the code for NotesScreen:
@Composable
fun NotesScreen(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
modifier: Modifier,
notesViewModel: NotesViewModel = hiltViewModel()
) {
val notesList by notesViewModel.notesStateData.collectAsState()
// FAILED SOLUTION NUMBER 2
// LaunchedEffect(Unit){
// notesViewModel.getAllNotes()
// }
// FAILED SOLUTION NUMBER 3
// DisposableEffect(lifecycleOwner) {
// val observer = LifecycleEventObserver { _, event ->
// when (event) {
// Lifecycle.Event.ON_RESUME -> {
// notesViewModel.getAllNotes()
// }
// else -> {}
// }
// }
// lifecycleOwner.lifecycle.addObserver(observer)
// onDispose {
// lifecycleOwner.lifecycle.removeObserver(observer)
// }
// }
LazyColumn(
contentPadding = (PaddingValues(horizontal = 16.dp, vertical = 8.dp)),
modifier = modifier
) {
items(items = notesList.orEmpty()) { note ->
NoteItem(note)
}
}
}
From the code you can see that I commented out some failed solutions. My first solution is to include notesViewModel.getAllNotes() in init{} block of viewModel. But this calls the function only once, and not every time I get back to NotesScreen.
My second solution is to create a LaunchedEffect(Unit) because I want to call this viewModel function every time the NotesScreen is displayed. But it didn't work, it also calls the getAllNotes() only once.
My third solution is to create a DisposableEffect with observer that will tell me every time ON_RESUME happens, but this also didn't work.
I used debugger and logged some behaviours:
When I use init block, the init block happens as soon as I open the app and not when I open the screen. It seems like the HorizontalPager loads the screen immediately.
When I use second solution the LaunchEffect block happens when I navigate to the screen, but it doesn't get called again even though I switched tabs multiple times.
When I use DisposableEffect all the lifecycle states happen as soon as I open the app, ON_START, ON_CREATE, and ON_RESUME are logged as soon as app is opened even though I am not on NotesScreen yet. This means that the viewModel function got called only once, and now I can't see the new Notes I created untill I restart the app.
My desired behaviour: Every time I open NotesScreen I want to load all the notes from RoomDB, and not only once on app start. I am trying to trigger VM function that gets notes from DB automatically without refresh layouts or buttons but I can't seem to find a solution.
Any help would be heavily appreciated.
CodePudding user response:
You can use snapshotFlow for add page change callback to Pager. It Creates a Flow from observable Snapshot state. Here is a sample code
// Page change callback
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }.collect { page ->
when (page) {
0 -> viewModel.getAllNotes() // First page
1 -> // Second page
else -> // Other pages
}
}
}