Vivek Modi
Vivek Modi

Reputation: 7271

How to save HorizontalPager state when switching UI states in Jetpack Compose

I have two UI states: FIRST and SECOND. In the FIRST state, I have a HorizontalPager and other widgets. In the SECOND state, I have a simple Text widget. I am switching between the UI states with a Button click.

I want to save the index of the current page in the HorizontalPager when I switch to the SECOND state, so that when I switch back to the FIRST state, the HorizontalPager opens on the same page.

For example, if I am on page 3 of the HorizontalPager and I click the Button to switch to the SECOND state, I want the HorizontalPager to open on page 3 when I switch back to the FIRST state.

Currently, when I switch back to the FIRST state, the HorizontalPager always opens on page 0.

Note: The current page index of the HorizontalPager is only saved while the activity is active. This means that if the activity is destroyed (for example, when the user close the application or move to another activity), the HorizontalPager will start on page 0 when the activity is recreated.

MainActivity

class MainActivity : ComponentActivity() {
    @OptIn(ExperimentalFoundationApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ViewObersableTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    val pagerState = rememberPagerState()
                    SaveVariable(pagerState, VariableViewModel())
                }
            }
        }
    }
}

SaveVariable

@Composable
@OptIn(ExperimentalFoundationApi::class)
fun SaveVariable(pagerState: PagerState, viewModel: VariableViewModel) {
    LaunchedEffect(key1 = pagerState) {
        snapshotFlow { pagerState.currentPage }.collect { page ->
            viewModel.currentIndex = page
        }
    }
    LaunchedEffect(key1 = viewModel.setLastCurrentPage) {
        Log.e(">> LaunchedEffect", "${viewModel.currentIndex}")
    }
    SaveVariableItem(viewModel.currentIndex, viewModel.uiState, pagerState) {
        viewModel.changeUIi()
    }
}

VariableViewModel

class VariableViewModel : ViewModel() {
    var uiState by mutableStateOf(ScreenName.FIRST)
    var currentIndex by mutableStateOf(0)
    var setLastCurrentPage by mutableStateOf(false)
    fun changeUIi() {
        uiState = if (uiState == ScreenName.FIRST) {
            setLastCurrentPage = true
            ScreenName.SECOND
        } else {
            setLastCurrentPage = false
            ScreenName.FIRST
        }
    }
}

ScreenName

enum class ScreenName {
    FIRST,
    SECOND
}

SaveVariableItem

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SaveVariableItem(
    currentIndex: Int,
    uiState: ScreenName,
    pagerState: PagerState,
    changeUiState: () -> Unit,
) {
    Column {
        when (uiState) {
            ScreenName.FIRST -> {
                ScreenFirstView(pagerState)
            }

            ScreenName.SECOND -> {
                ScreenSecondView(currentIndex, "SECOND")
            }
        }
        ChangeUiStateButton(changeUiState)
    }
}

ChangeUiStateButton

@Composable
fun ChangeUiStateButton(changeUiState: () -> Unit) {
    Button(onClick = { changeUiState() }) {
        Text(text = "Add")
    }
}

ScreenFirstView

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ScreenFirstView(pagerState: PagerState) {
    Column(
        modifier = Modifier
            .wrapContentSize()
            .background(Color.Black)
    ) {
        HorizontalPager(
            modifier = Modifier.padding(20.dp),
            pageCount = 10,
            state = pagerState
        ) { page ->
            Text(
                text = "Page: $page",
                modifier = Modifier.fillMaxWidth(),
                color = Color.White
            )
        }
    }
}

ScreenSecondView

@Composable
fun ScreenSecondView(currentIndex: Int, screenImage: String) {
    Column(
        modifier = Modifier
            .wrapContentSize()
            .background(Color.Black)
    ) {
        Text(text = "$currentIndex ==== $screenImage", color = Color.White)
    }
}

My github sample is here.

implementation(platform("androidx.compose:compose-bom:2023.05.01"))

Upvotes: 2

Views: 1508

Answers (1)

WBarber
WBarber

Reputation: 93

Ive gotten this to work using state flows in the ViewModel. I cut the code down a little, but basically i had to add a LaunchedEffect into the composable containing the pager to update the PagerState upon composition using the saved page number in ViewModel. Not sure if there is a better way to accomplish this but this does work. heres the modified repo: https://github.com/Mr0btain/SavePagerState

Upvotes: 1

Related Questions