In Compose Multiplatform mobile (Android & iOS), Currently, Navigation between screens causing unnecessary recomposition and loading the entire voyager screen after coming back to same screen . Is there any way to avoid the recomposition during navigation? We're attaching codebase for better understanding. If you paid attention to the code, If I switch between tickets or teams tab from Dashboard screen, It is causing the recomposition and as the result, the chat list is recomposition too. Can we avoid reloading or recomposition of screens?
Thanks.
Steps To Reproduce
Setup Navigator (Splash screen is initial screen)
@Composable
fun App() {
ScogoThemeUi {
Navigator(AppNavigationImpl().initialScreen())
}
}
Dashboard Screen (Splash screen navigates to it)
internal class DashboardScreen : Screen, KoinComponent {
@Composable
override fun Content() {
DashboardUiScreen()
}
}
@Composable
private fun DashboardUiScreen() {
val stateHolder by remember { mutableStateOf(DashboardStateHolder()) }
val bottomNavItems = stateHolder.getBottomNavItems()
private class TicketsTab : Tab, KoinComponent {
override val options: TabOptions
@Composable get() = remember { TabOptions(index = 0u, title = "") }
@Composable
override fun Content() {
val navigation: AppNavigation by inject()
navigation.toTicketContainer(null).Content()
}
}
private class TeamsTab : Tab {
override val options: TabOptions
@Composable get() = remember { TabOptions(index = 0u, title = "") }
@Composable
override fun Content() {
// TODO: Screen
Text("Teams")
}
}
3. Ticket Container Screen (TicketsTab)
```kotlin
internal class TicketContainerScreen : Screen, KoinComponent {
@Composable
override fun Content() {
val navigation: AppNavigation by inject()
TicketContainerUiScreen(navigation = navigation)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun TicketContainerUiScreen(navigation: AppNavigation) {
val stateHolder by remember { mutableStateOf(TicketContainerStateHolder()) }
val tabs by remember(stateHolder) { mutableStateOf(stateHolder.getTabs()) }
val selectedTab by stateHolder.selectedTab.collectAsState()
val pagerState = rememberPagerState { tabs.size }
LaunchedEffect(selectedTab) {
pagerState.animateScrollToPage(selectedTab.index)
}
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }.collectLatest { page ->
stateHolder.selectedTab(stateHolder.getTabByIndex(page))
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
Column(modifier = Modifier.fillMaxWidth()) {
TopAppBars.TextTitle(
modifier = Modifier.fillMaxWidth(),
title = ScogoTheme.strings.dashboard
)
TabRow(
modifier = Modifier.fillMaxWidth(),
containerColor = ScogoTheme.colorScheme.onPrimary,
selectedTabIndex = selectedTab.index
) {
tabs.forEach { tab ->
Tab(
selected = tab == selectedTab,
onClick = {
stateHolder.selectedTab(tab)
},
text = {
Text(
fontWeight = FontWeight.Bold,
text = tab.label
)
}
)
}
}
}
}
) { paddingValues ->
HorizontalPager(
modifier = Modifier.fillMaxSize().padding(paddingValues),
state = pagerState
) { index ->
when (stateHolder.getTabByIndex(index)) {
TicketContainerTab.Chats -> {
navigation.toChatList(null).Content()
}
TicketContainerTab.NewTickets -> {
navigation.toNewTicketList(null).Content()
}
TicketContainerTab.Tickets -> {
navigation.toTicketList(null).Content()
}
}
}
}
}
Chat List Screen (TicketContainerTab.Chats)
class ChatListScreen : Screen, KoinComponent {
@Composable
override fun Content() {
val viewModel: ChatListViewModel by inject()
val navigation: AppNavigation by inject()
ChatListUiScreen(
viewModel = viewModel,
onChatClick = { chat ->
navigation.toChatMessageList(
navigator = navigation.rootNavigator,
conversationId = chat.id
)
}
)
}
}
@Composable
private fun ChatListUiScreen(
viewModel: ChatListViewModel,
onChatClick: (ChatDisplayModel) -> Unit
) {
val state by viewModel.uiState.collectAsState(ChatListUiState.Idle)
LaunchedEffect(Unit) {
viewModel.start()
}
Scaffold(
modifier = Modifier
.fillMaxSize()
.padding(bottom = ScogoTheme.spacing.ten.dp)
) {
when (state) {
is ChatListUiState.Loading -> {
Shimmer(modifier = Modifier.fillMaxSize())
}
is ChatListUiState.ChatList -> {
ChatList(
state = state as ChatListUiState.ChatList,
onScrolledToBottom = viewModel::onChatListEnded,
onRefresh = viewModel::onRefresh,
onChatClick = onChatClick
)
}
is ChatListUiState.NotResults -> {
NotChatResults(modifier = Modifier.fillMaxSize())
}
else -> {}
}
}
}
5. AppNavigation
```kotlin
internal class AppNavigationImpl : AppNavigation {
private var _rootNavigator: Navigator? = null
override val rootNavigator: Navigator? get() = _rootNavigator
override fun setRootNavigator(navigator: Navigator) {
_rootNavigator = navigator
}
override fun initialScreen(): Screen {
return SplashScreen()
}
override fun toDashboard(navigator: Navigator?): Screen {
val screen = DashboardScreen()
navigator?.push(screen)
return screen
}
override fun toTicketContainer(navigator: Navigator?): Screen {
val screen = TicketContainerScreen()
navigator?.push(screen)
return screen
}
override fun toChatList(navigator: Navigator?): Screen {
val screen = ChatListScreen()
navigator?.push(screen)
return screen
}
}
In Compose Multiplatform mobile (Android & iOS), Currently, Navigation between screens causing unnecessary recomposition and loading the entire voyager screen after coming back to same screen . Is there any way to avoid the recomposition during navigation? We're attaching codebase for better understanding. If you paid attention to the code, If I switch between tickets or teams tab from Dashboard screen, It is causing the recomposition and as the result, the chat list is recomposition too. Can we avoid reloading or recomposition of screens? Thanks.
Steps To Reproduce
Setup Navigator (Splash screen is initial screen)
Dashboard Screen (Splash screen navigates to it)
@Composable private fun DashboardUiScreen() { val stateHolder by remember { mutableStateOf(DashboardStateHolder()) } val bottomNavItems = stateHolder.getBottomNavItems()
}
private class TicketsTab : Tab, KoinComponent { override val options: TabOptions @Composable get() = remember { TabOptions(index = 0u, title = "") }
}
private class TeamsTab : Tab { override val options: TabOptions @Composable get() = remember { TabOptions(index = 0u, title = "") }
}
@Composable private fun ChatListUiScreen( viewModel: ChatListViewModel, onChatClick: (ChatDisplayModel) -> Unit ) { val state by viewModel.uiState.collectAsState(ChatListUiState.Idle)
}
Attachments
Versions Information