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