dev.tejasb
dev.tejasb

Reputation: 578

Android Jetpack Compose call view-model function only once

There is a screen level composable under an activity. When user is navigated to that screen, I'm using a LaunchedEffect(Unit) {} to call view-model function which does some work. Now, this works fine until the device rotates or any configuration change occurs. After rotation or so, LaunchedEffect(Unit) {} is executed again which is not desirable.

I know I can use a flag in the view-model or in the composable function itself to prevent doing the work again. But, writing here to find if there is any ideal solution to this problem as this seems to be a common problem in Jetpack Compose.

PS: The function needs to be in the screen level view-model only and not at activity-level. Also, cannot use init of view-model as the function which the screen-level composable is calling needs some data as it's parameters.

Upvotes: 5

Views: 2719

Answers (3)

Mr.Moustard
Mr.Moustard

Reputation: 1307

From your compose view, you can do something like that,

LaunchedEffect(viewModel) {
    viewModel.whatever()
}

It will be triggered only when viewModel change, so it should be executed only once. I hope it helps.

Upvotes: 0

Rafsanjani
Rafsanjani

Reputation: 5443

You can implement some basic caching in your ViewModel. Here is a hypothetical example of where the ViewModel will return cached data to subscribers during configuration changes.

class MainViewModel : ViewModel() {
    sealed interface MainViewState {
        data object Content : MainViewState
        data object Loading : MainViewState
        data class Error(val error: Throwable) : MainViewState
    }

    private val _viewState: MutableStateFlow<MainViewState> =
        MutableStateFlow(MainViewState.Loading)

    val viewState = _viewState.asStateFlow()

    init {
       loadData()
    }

    private fun loadData() = viewModelScope.launch {
        // We have cached data, do nothing, StateFlow will re-emit latest state to new subscribers
        if (_viewState.value == MainViewState.Content) {
            return@launch
        }

        _viewState.update {
            MainViewState.Loading
        }

        // Load some data and return MainViewState.Content or MainViewState.Error
    }
}

Upvotes: 0

Faruk
Faruk

Reputation: 1501

LaunchedEffect will run every time it recomposes. You can use rememberSaveable for this and add a flag to not run the code again. Actually, it would be more accurate to do this in a viewModel, but since you specifically want it this way, you can look at this example.

var workCompleted by rememberSaveable { mutableStateOf(false) }

    LaunchedEffect(Unit) {
        if (!workCompleted) {
            // your work
            workCompleted = true
        }
    }

Upvotes: 0

Related Questions