Tlaster / PreCompose

Compose Multiplatform Navigation && State Management
https://tlaster.github.io/PreCompose/
MIT License
839 stars 49 forks source link

Navigating is losing Screen state #318

Open JagadishaIncture opened 4 months ago

JagadishaIncture commented 4 months ago

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

Tlaster commented 4 months ago

Hi can you provide some sample code that reproduce this issue?

JagadishaIncture commented 4 months ago

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)
                    }
                }
            }
        }
}

}`

JagadishaIncture commented 4 months ago

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
                }
            }
        }
    }
}

}`

JagadishaIncture commented 4 months ago

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() }) } ) }

Tlaster commented 4 months ago

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.

JagadishaIncture commented 4 months ago

any solution her how to avoid api call when returning back to GroupsScreen?

Tlaster commented 4 months ago

You can place your API call in ViewModel's initializer.

JagadishaIncture commented 4 months ago

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)
}

}`