I am making an app where the user first need to login to be able to get alot of different data from a backend. (many endpoints)
So I have one viewmodel for the login, and I have alot of viewmodels for all the other data.
The other viewmodels require the token from the first viewmodel to be able to get data from the backend.
I don't know how I can do this.
I was thinking that I can have my login screen in a kind of state manager which will direct the UI to the correct screen like this
@ExperimentalComposeUiApi
@Composable
fun LoginState(vm: AuthViewModel, nc: NavController) {
val token by vm.token.collectAsState()
when (token) {
is Resource.Loading -> {
LoadingScreen()
}
is Resource.Success -> {
Scaffold(vm = vm)
}
is Resource.Error -> {
LoginScreen(vm = vm)
}
}
}
But then I would have to create the viewmodels inside the Scaffold
which is a composable function, and that is not possible.
Another thought was to use Hilt to do some kind of magic dependency injection, and then put all the viewmodels into a ViewModelManager
in the MainActivity
and then inject the Token
into the repositories
of each viewmodel
when login is successfull.
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val authViewModel: AuthViewModel by viewModels()
private val userViewModel: UserViewModel by viewModels()
private val venueViewModel: VenueViewModel by viewModels()
private val eventViewModel: EventViewModel by viewModels()
private val viewModelManager = ViewModelManager(
userViewModel = userViewModel,
authViewModel = authViewModel,
venueViewModel = venueViewModel,
eventViewModel = eventViewModel,
)
@ExperimentalMaterialApi
@ExperimentalComposeUiApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MoroAdminTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
ScaffoldExample(viewModelManager)
}
}
}
}
}
However I have no idea how to do this or if it is even possible - or a good solution.
CodePudding user response:
Problem: you want to share a value (token) to all of your view model
your token retrieved in AuthViewModel
and need to share it to the other viewModels
you can make your data in the other viewModels changes when the token changes
by using datastore Preferences
see implementation
Datastore preferences provides you with a flow of values whenever the value changes
Create a DatastoreManager Class
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
@Singleton
class DatastoreManager @Inject constructor(@ApplicationContext context: Context) {
private val dataStore = context.dataStore
val dataFlow = dataStore.data
.map { preferences ->
val token = preferences[PreferencesKeys.TOKEN]
}
suspend fun updateToken(token: String) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.TOKEN] = token
}
}
private object PreferencesKeys {
val TOKEN = preferencesKey<String>("token")
}
}
In AuthViewModel
Inject the DatastoreManager and set the token after login
datastore.updateToken(newToken)
In other ViewModels
Inject the DatastoreManager and use it's value
//this is a flow of tokens and will recive the token when you set it
val token = datastore.token
// if you are not familiar with flows and using only LiveData
val token = datastore.token.asLiveData()
// use the token to get the data from backend
val data = token.map {
// this code will trigger every time the token changes
yourGetDataFromBackendFunction(it)
}
CodePudding user response:
But then I would have to create the viewmodels inside the Scaffold which is a composable function, and that is not possible.
This is not true. You don't have to create view models in your Activity.
In any composable you can use viewModel()
Returns an existing ViewModel or creates a new one in the given owner (usually, a fragment or an activity), defaulting to the owner provided by LocalViewModelStoreOwner.
So you don't need any ViewModelManager
. Inside any composable you can use viewModel()
with the corresponding class. In your case you're using Hilt, you should use hiltViewModel()
instead: it'll also initialize your injections.
@Composable
fun AuthScreen(viewModel: AuthViewModel = hiltViewModel()) {
}
Or like this:
@Composable
fun VenueScreen() {
val viewModel: VenueViewModel = hiltViewModel()
}
First approach will allow you to easily test your screen with mock view model, without passing any arguments in your production code.
Check out more about view models in view models documentation and hilt documentation
As to your token question, you can pass it with injections. I don't think that your view model really needs the token, probably you should have some network manager which will use the token to make requests. And this network manager should use injection of some token provider.