Navigating is losing Screen state
Describe the bug When we navigate from Screen A to Screen B and coming back to Screen A which leads to recreate screen A . means screen start recreates and re hits api call and fetches data
Expected behavior Screen A state should be retained
Hi can you provide some sample code that reproduce this issue?
Screen A
` @Composable fun GroupsScreen( viewModel: GroupViewModel, baseViewModel: BaseViewModel, platformUtils: PlatformUtils, navigator: Navigator, localSharedStorage: LocalSharedStorage, showToolBar:Boolean=false ) {
var isLoading by remember { mutableStateOf(false) }
var isShimming by remember { mutableStateOf(false) }
var data: List<GroupsItemModel> by remember { mutableStateOf(ArrayList()) }
var currentPage by remember { mutableStateOf(0) }
val pageCount = 10
val totalRecords = 0
val lazyListState = rememberLazyGridState()
if (isLoading) {
DialogCustomCircleProgressbar()
}
Scaffold {
Column(
modifier = Modifier.fillMaxWidth().fillMaxHeight().padding(top = 5.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (showToolBar) {
ToolBarWithBack(navigator = navigator, stringResource(MR.strings.groups))
}
if (isShimming) {
GroupScreenShimmer(platformUtils)
}else{
LazyVerticalGrid(
columns = GridCells.Fixed(if (platformUtils.isTablet()) 2 else 1),
state = lazyListState,
modifier = Modifier.fillMaxHeight(1f).fillMaxWidth()
) {
itemsIndexed(data) { _, item ->
Card(
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.clickable {
val id = item.groupId.toString()
if(showToolBar){
navigator.navigate(NavigationRoute.GroupsDetailScreen.getRoute(id))
}
else{
navigator.navigate(NavigationRoute.GroupsDetailScreenAdmin.getRoute(id))
}
},
elevation = 2.dp
) {
Row(modifier = Modifier.fillMaxWidth()) {
RoundedCornerImageView(platformUtils,item.profileImage,Modifier.fillMaxWidth(0.3f).height(100.dp).padding(5.dp))
Column(modifier = Modifier.weight(1f).padding(8.dp), verticalArrangement = Arrangement.Center) {
Text(
text = item.name.toString(),
style = StyleUtils.getBoldFontStyle()
)
Spacer(modifier = Modifier.height(3.dp))
Text(
text = item.description.toString(),
maxLines = 1,
style = StyleUtils.getSemiBoldFontStyle(),
overflow = TextOverflow.Ellipsis
)
}
if (showToolBar) {
Box(modifier = Modifier.align(Alignment.CenterVertically)) {
Spacer(modifier = Modifier.width(8.dp))
DeleteButton(modifier = Modifier.size(40.dp)) {
baseViewModel.deletePost(
category = Category.GROUP.name,
item.groupId.toString(),
localSharedStorage.getUserId()
)
}
}
}
}
}
}
}
}
}
}
LaunchedEffect(Unit)
{
viewModel.getGroupPlace(currentPage, pageCount)
viewModel._uiState.collect {
when {
it.isLoading -> {
if (currentPage==0)
{
isShimming = true
}else{
isLoading =true
}
}
it.error.isNotEmpty() -> {
isShimming = false
isLoading =false
platformUtils.makeToast(it.error)
}
it.data != null -> {
isShimming = false
isLoading =false
data = it.data
}
}
}
}
LaunchedEffect(Unit)
{
baseViewModel._deleteResponse.collect {
when {
it.isLoading -> {
isLoading = true
}
it.error.isNotEmpty() -> {
isLoading = false
platformUtils.makeToast(it.error)
}
it.success != null -> {
isLoading = false
platformUtils.makeToast(it.success)
currentPage=0
viewModel.getGroupPlace(currentPage, pageCount)
}
}
}
}
LaunchedEffect(lazyListState) {
snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index }
.collect { lastIndex ->
if (lastIndex != null && lastIndex >= data.size - 1 && !isLoading) {
if(data.size < totalRecords){
if(platformUtils.isOnline()){
currentPage++
viewModel.getGroupPlace(currentPage, pageCount)
}
}
}
}
}
}`
Screen B
`@OptIn(ExperimentalAdaptiveApi::class) @Composable fun GroupsDetailScreen( navigator: Navigator, viewModel: GroupViewModel, platformUtils: PlatformUtils, id: String, showToolBar: Boolean = false ) { var isShimming by remember { mutableStateOf(false) } var data: List<GroupsDetailItemModel> by remember { mutableStateOf(emptyList()) } val lazyListState = rememberLazyGridState()
Scaffold(
topBar = {
Row(
modifier = Modifier
.fillMaxWidth()
.background(color = ColorResources.ComponentColor),
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(.8f)) {
ToolBarWithBack(
navigator = navigator,
title = stringResource(MR.strings.concert)
)
}
Column {
AdaptiveIconButton(onClick = {
if (showToolBar) {
navigator.navigate(NavigationRoute.UserInGroupScreen.getRoute(id))
} else {
navigator.navigate(NavigationRoute.UserInGroupScreenAdmin.getRoute(id))
}
}) {
Icon(
imageVector = Icons.Outlined.Person,
contentDescription = null,
tint = ColorResources.IconColor
)
}
}
}
}
) {
if (isShimming) {
GroupDetailScreenShimmer(platformUtils)
} else {
Column {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
if (showToolBar) {
Box(
modifier = Modifier
.background(
color = Color.Green,
shape = RoundedCornerShape(12.dp)
)
.padding(horizontal = 8.dp, vertical = 4.dp)
.clickable {
navigator.navigate(NavigationRoute.SendMessageScreen.getRoute(id))
}
.align(Alignment.TopEnd)
) {
Text(
text = stringResource(MR.strings.send),
color = Color.White,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
}
}
LazyVerticalGrid(
columns = GridCells.Fixed(if (platformUtils.isTablet()) 2 else 1),
state = lazyListState,
modifier = Modifier.fillMaxSize().padding(top = 5.dp)
) {
if (data.isEmpty()) {
item {
NoDataView()
}
} else {
itemsIndexed(data) { index, item ->
Card(
shape = RoundedCornerShape(12.dp),
elevation = 8.dp,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.size(40.dp)
.background(color = Color.DarkGray, shape = CircleShape),
contentAlignment = Alignment.Center
) {
Text(
text = item.createdByName?.take(1)?.uppercase() ?: "",
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.width(8.dp))
Text(
text = item.createdByName ?: "",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 16.sp
),
modifier = Modifier.weight(1f)
)
}
Spacer(modifier = Modifier.height(16.dp))
Spacer(modifier = Modifier.weight(1f))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = item.textContent ?: "",
style = TextStyle(
fontWeight = FontWeight.Light,
color = Color.Black
)
)
Text(
text = TimeConversionClass.convertLongToDate(item.createdOn!!.toLong()),
style = TextStyle(
fontWeight = FontWeight.Light,
color = Color.Gray
)
)
}
Spacer(modifier = Modifier.height(16.dp))
RoundedCornerImageView(
platformUtils,
item.imageContent,
Modifier.height(200.dp).padding(6.dp)
)
}
}
}
}
}
}
}
LaunchedEffect(Unit) {
viewModel.getGroupsItemDetail(id, getnoOfRecordsPerPage = 4, pageNumber = 0)
viewModel._uidetailState.collect { uiState ->
when {
uiState.isLoading -> {
isShimming = true
}
uiState.error.isNotEmpty() -> {
isShimming = false
platformUtils.makeToast(uiState.error)
}
uiState.data != null -> {
isShimming = false
data = uiState.data
}
}
}
}
}
}`
ToolBar code
@OptIn(ExperimentalAdaptiveApi::class) @Composable fun ToolBarWithBack(navigator: Navigator,title: String) { AdaptiveTopAppBar( title = { Text(title,style = StyleUtils.getBoldFontStyle()) }, navigationIcon = { Image(imageVector = Icons.Default.KeyboardArrowLeft, contentDescription = null, colorFilter = ColorFilter.tint(color = ColorResources.IconColor),modifier = Modifier.clickable { navigator.goBack() }) } ) }
Seems like you're fetching from LaunchedEffect
LaunchedEffect(Unit)
{
viewModel.getGroupPlace(currentPage, pageCount)
//...
}
LaunchedEffect(Unit) will run When LaunchedEffect enters the Composition, in this case, when you're navigating from GroupsScreen to other screen, you GroupsScreen is actually cleared from the composition, so when you're back from other screen to GroupsScreen, the GroupsScreen is entering the composition once again, which triggers LaunchedEffect to run once again.
any solution her how to avoid api call when returning back to GroupsScreen?
You can place your API call in ViewModel's initializer.
you suggets me here
`class GroupViewModel(private val mainUseCase: MainUseCase) : ViewModel() {
val _uiState = MutableSharedFlow<GroupStateHolder>()
fun getGroupPlace(currentPage: Int, pageCount: Int) {
val groupPayloadModel = CategoryPayloadModel().apply {
statusList = arrayListOf(Status.ACTIVE.name)
pageNumber = currentPage
noOfRecordsPerPage = pageCount
}
mainUseCase.getGroups(groupPayloadModel).onEach { res ->
when (res) {
is NetworkResult.Loading -> {
_uiState.emit(GroupStateHolder(isLoading = true))
}
is NetworkResult.Success -> {
data = emptyList()
data = data + (res.data ?: emptyList())
_uiState.emit(GroupStateHolder(data = data, totalRecords = res.totalRecords))
}
is NetworkResult.Error -> {
_uiState.emit(GroupStateHolder(error = res.message))
}
}
}.launchIn(viewModelScope)
}
}`