Home > Blockchain >  How can I have different states with different viewmodels?
How can I have different states with different viewmodels?

Time:10-08

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.

  • Related