My UI is not updating to show the new list of items when I change customers.
I have a Composable screen to display the customer and a list of the items for that customer. When I select a new customer, the customer id on the screen changes, but the list of items for that customer does not get updated. I always only see the first list of items that was displayed. How do I get the UI to update?
CartViewModel
:
class CartViewModel @Inject constructor(
val app: Application,
private val repository: CartRepository,
private val pendingCustomerRepository: PendingCustomerRepository,
private val offlineOrderRepo: OfflineOrderDetailRepository,
) : AndroidViewModel(app) {
val offlineItemsStateFlow = offlineOrderRepo.getOfflineItemsForCustomer(currentCustomerId)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L), emptyList())
fun getOfflineItemsForCustomer() {
offlineItemsStateFlow = offlineOrderRepo.getOfflineItemsForCustomer(currentCustomerId)
}
}
CartView
:
@Composable
fun SharedTransitionScope.CartView(
navController: NavController,
cartViewModel: CartViewModel,
onBack: ()->Unit,
) {
val offlineItems by cartViewModel.offlineItemsStateFlow.collectAsStateWithLifecycle()
var isChangeRolePopupOpen by remember { mutableStateOf(false) }
Column(
Modifier
// consumeWindowInsets keep imePadding from doubling up
.consumeWindowInsets(it)
.imePadding()
) {
Text("Current offline customer is: ${cartViewModel.currentCustomerId}")
if (true) {
cartViewModel.updateCurrentCustomerId()
}
OfflineOrderContent(
orderList = offlineItems,
updateOfflineItemQuantity = { it ->
cartViewModel.updateOfflineItemQuantity(it)
},
onDeleteItem = { it ->
cartViewModel.deleteOfflineItemFor(it)
}
)
}
}
You use the MutableState currentCustomerId
in your view model and expect any changes to trigger a re-execution of the repos getOfflineItemsForCustomer
. That doesn't work, though, because a changed State only triggers the re-execution of Compose functions, i.e. those that are annotated with @Composable
. And no, the solution is not to make your repository functions composables as well; only UI-related functions should be composables.
Instead, replace your MutableState with a MutableStateFlow like this:
private val _currentCustomerId = MutableStateFlow(SettingsHelper.getCustomerId(app))
val currentCustomerId: StateFlow<String> = _currentCustomerId.asStateFlow()
fun updateCurrentCustomerId() {
_currentCustomerId.value = SettingsHelper.getCustomerId(app)
}
(Whatever SettingsHelper
is; it seems like this should be moved to a Data Store or at least a SavedStateHandle instead)
A MutableStateFlow is similar to a MutableState in that they share the same semantics: They are both a container that holds a value, which can be observed for changes. Despite their similar name they are entirely unrelated, though: The former is an integral component of the Kotlin programming language, where the latter is only part of the Compose framework from Google.
currentCustomerId
is now a StateFlow that you need to collect in your composables the same as you already did with offlineItems
.
Wih this out of the way, you can now simply base the database flow on this new _currentCustomerId
flow:
val offlineItemsStateFlow: StateFlow<List<OfflineOrderDetail>> = _currentCustomerId
.flatMapLatest(offlineOrderRepo::getOfflineItemsForCustomer)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5.seconds),
initialValue = emptyList(),
)
flatMapLatest
transforms the flow containing the customer id into a new flow. That is done by calling the repository's getOfflineItemsForCustomer
function. If you are not familiar with function references (the syntax with ::
), that is just short for this:
.flatMapLatest { offlineOrderRepo.getOfflineItemsForCustomer(it) }
And now the resulting flow is converted to a StateFlow as you did before (note that I also replaced 5_000L
by 5.seconds
which is more clear; you might need to update your imports as well)
That's it: Whenever you call updateCurrentCustomerId
to change _currentCustomerId
, the offlineItemsStateFlow
is updated with a new database flow for the new customer id. In consequence, your UI will automatically update as well.