Robert Menke
01/28/2022, 9:41 PMAuthenticationViewModel
which is a @HiltViewModel
that keeps track of the authenticated User
entity as follows:
private var authenticatedUser: LiveData<User?> = userRepository.observeAuthenticatedUser().asLiveData()
After I’ve verified that a user is authenticated I want to be able to observe authenticatedUser
and ideally receive the latest`User` instance without having to retrieve it from the database again. I have a component called ProfileScreen
that tries to access that data as follows:
val authViewModel: AuthenticationViewModel = hiltViewModel()
val user by authViewModel.getAuthenticatedUser().observeAsState()
val firstName by viewModel.firstName.observeAsState(user?.firstName ?: "")
When I set a breakpoint on firstName
the variable user
is always null
initially. What is the right way to keep my User
instance in memory without having to re-fetch from the DB whenever I ask for it?Ian Lake
01/28/2022, 11:29 PMcollect
on the Flow
, which is inherently an async operation. Now if your UserRepository
was driven by Room and returned a LiveData
itself, then you'd gain the natural caching that LiveData
has where it always remembers any previously returned value just like how StateFlow
worksIan Lake
01/28/2022, 11:33 PMprivate var authenticatedUser: StateFlow<User?> = userRepository.observeAuthenticatedUser().stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
Which would give you a StateFlow
(which also always remembers its current value), that is Lifecycle aware (the WhileSubscribed
prevents it from doing any work while your UI isn't collecting the StateFlow), and that still lets your Composable code call collectAsState()
to easily gather the latest valueRobert Menke
01/28/2022, 11:58 PMRobert Menke
01/29/2022, 7:10 PM@Composable
@PublishedApi
internal fun createHiltViewModelFactory(
viewModelStoreOwner: ViewModelStoreOwner
): ViewModelProvider.Factory? = if (viewModelStoreOwner is NavBackStackEntry) {
HiltViewModelFactory(
context = LocalContext.current,
navBackStackEntry = viewModelStoreOwner
)
} else {
// Use the default factory provided by the ViewModelStoreOwner
// and assume it is an @AndroidEntryPoint annotated fragment or activity
null
}
I had a NavBackStackEntry
for my SplashScreen
and a separate one for my ProfileScreen
and yet another that didn’t have any associated NavBackStackEntry
, so of course I was re-fetching user state after the initial authentication check.
Seems like the right solution for something like authenticated user state is to create 1 instance of the view model outside of the navigation graph and then pass that instance into each Composable
that needs it since being scoped to @AndroidEntryPoint
I believe makes more sense here. I think I’ll move forward with that for now.Robert Menke
01/29/2022, 8:00 PM