Navigating is losing Screen state
JagadishaIncture opened this issue · comments
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 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()
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)
}
}`