I am using NavigableListDetailPaneScaffold, to show list of items and then it's details when clicked.
The screen is accompanied by its viewModel and I would love to run the fetching code in the constructor. But I can't because;
- I lack a way to get the argument within the class (probably from savedStateHandle)
- The class is initialized immediately the ListDetailPane is created (not when navigated to the detail pain)
@HiltViewModel
class DetailsScreenViewModel
@Inject constructor(
private val repository: ItemRepository,
// private val savedStateHandle: SavedStateHandle
) : ViewModel() {
init {
//run fetching code here once
}
}
Although, I can achieve this using a member function together with LauchedEffect(Unit) to ensure it ran once but I am not satisfied using it.
fun fetch(index: Int? = null) {
val combinedFlow: Flow<EditDetailUiState> = combine(
repository.requestAllItemDetails(index),
repository.requestAllUnits()
) { item, allUnits ->
}
viewModelScope.launch {
combinedFlow.collect { state ->
editDetailUiState = state
}
}
}
I understand that when using NavHost, arguments could be extracted through the savedStateHandle. Is there a way to achieve this using the NavigableListDetailPaneScaffold or any advice about it?
You should not collect flows in your view model.
Instead, use a pattern like this:
private val selectedItem: MutableStateFlow<Int?> = MutableStateFlow(null)
val editDetailUiState: StateFlow<EditDetailUiState> = selectedItem
.flatMapLatest(repository::requestAllItemDetails)
.combine(repository.requestAllUnits()) { item, allUnits ->
EditDetailUiState(item, allUnits)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = EditDetailUiState(),
)
fun selectItem(index: Int? = null) {
selectedItem.value = index
}
Now you only transform the flows in the view model and finally expose them as a single, combined StateFlow of EditDetailUiState
.
In your composables you can then retrieve the state like this:
val state by viewModel.editDetailUiState.collectAsStateWithLifecycle()
Make sure you use the gradle dependency androidx.lifecycle:lifecycle-runtime-compose
.
Please note that I used EditDetailUiState()
as the initial value of the StateFlow. This value is used until all flows provided their first value. Adapt the value as needed, you can also use null
.
You can now easily add additional flows to combine
, for example the flows returned from a DataStore or a database like Room. Then use this same view model for both the list pane and the detail pane.
You can read more about the collection of StateFlows here: https://medium.com/androiddevelopers/consuming-flows-safely-in-jetpack-compose-cde014d0d5a3
There is also a codelab for ViewModel and State in Compose: https://developer.android.com/codelabs/basic-android-kotlin-compose-viewmodel-and-state