View Models for Flow Data and Retrofit HTTP Requests

133 Views Asked by At

I have the following code that handles DataStore preferences and I was wondering how I could implement it with View Models and then access the data in a composable view. Also thinking ahead, I'm wondering how to handle retrofit HTTP requests in a view model since from my understanding it's best practice to keep data mutation and data handling outside of the UI components. I'm assuming I would need two data models, one for preferences and one for HTTP request data. Any help would be greatly appreciated. Thank you.

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

val ACCOUNT_TYPE = stringPreferencesKey("account_type")
        val accountTypeFlow: Flow<String> = applicationContext.dataStore.data
            .map { preferences ->
                preferences[ACCOUNT_TYPE] ?: ""
            }

Edit: To be more clear, I have code to set and get DataStore preference data, but I don't know how to implement it in a View Model, and access the data from the View Model in a composable view. I'd also like to create a View Model for the data that is received from a Retrofit HTTP request, but I'm also unsure as how to do that.

Edit 2: I can ask a separate question in regard to Retrofit, but I can narrow down the first question to this: How can I implement get and set functionality for a DataStore variable in a View Model?

1

There are 1 best solutions below

3
VonC On BEST ANSWER

To use DataStore preferences in a ViewModel, you would create a ViewModel that fetches and updates preferences, a bit as in "Android Compose DataStore Tutorial " from Ethan Denvir.

class PreferencesViewModel(application: Application) : AndroidViewModel(application) {
    private val dataStore: DataStore<Preferences> = application.dataStore

    // LiveData to expose the account type from DataStore
    private val _accountType = MutableLiveData<String>()
    val accountType: LiveData<String> = _accountType

    init {
        viewModelScope.launch {
            fetchAccountType()
        }
    }

    private suspend fun fetchAccountType() {
        dataStore.data.map { preferences ->
            preferences[ACCOUNT_TYPE] ?: ""
        }.collect { accountTypeValue ->
            _accountType.postValue(accountTypeValue)
        }
    }

    // Function to update account type in DataStore
    fun updateAccountType(newAccountType: String) {
        viewModelScope.launch {
            dataStore.edit { preferences ->
                preferences[ACCOUNT_TYPE] = newAccountType
            }
        }
    }
}

To access the PreferencesViewModel in a composable function, you can use viewModel() composable (as in this example) to obtain an instance of your ViewModel and observeAsState() to observe the LiveData as state in your composable.

@Composable
fun PreferencesScreen(preferencesViewModel: PreferencesViewModel = viewModel()) {
    val accountType by preferencesViewModel.accountType.observeAsState("")

    Column {
        Text(text = "Account Type: $accountType")
        Button(onClick = {
            preferencesViewModel.updateAccountType("NewAccountType")
        }) {
            Text("Update Account Type")
        }
    }
}

For handling Retrofit HTTP requests, a similar ViewModel approach can be used, where the ViewModel would handle making the network requests and updating the UI state accordingly.


I have one error with that code: I get an unresolved reference error for observeAsState in the composable function, and I'm not sure why. Any further guidance would be much appreciated.

First, make sure that you have the Jetpack Compose LiveData integration library added to your module's build.gradle file. If it is missing, you will need to add it. :

dependencies {
    implementation "androidx.compose.runtime:runtime-livedata:<compose_version>"
}

Replace <compose_version> with the version of Compose you are using. Make sure all Compose-related dependencies use the same version to avoid compatibility issues.

In your Kotlin file where you are using observeAsState, make sure you have the following import statement:

import androidx.compose.runtime.livedata.observeAsState

I transferred everything to a new project and for some reason I get an unresolved reference error with dataStore, both on the import line and when used in the View Model, despite having dataStore core and preference implementation.

Do you know why that may be?

Check that you have correctly added the necessary DataStore dependencies in your build.gradle (Module) file.

dependencies {
    // DataStore Preferences
    implementation "androidx.datastore:datastore-preferences:1.1"

    // Optional: If you are also using Proto DataStore
    implementation "androidx.datastore:datastore-core:1.1"
}

I assume the latest version, as shown in "Jetpack / Libraries / DataStore"

Make sure you are using the correct import statements for the DataStore in your ViewModel or any other Kotlin file. If you are using the preferencesDataStore delegate for creating an instance of DataStore<Preferences>, you need an import like:

import androidx.datastore.preferences.preferencesDataStore

The context extension val Context.dataStore: DataStore<Preferences> typically resides in a separate Kotlin file (e.g., DataStoreUtils.kt) and looks something like this:

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

Make sure this utility file is correctly placed in your project and that the import statement is correctly resolved.

If you are initializing the dataStore in a ViewModel using the application context, see if your ViewModel extends AndroidViewModel, which provides you with the application context. As a quick check:

class PreferencesViewModel(application: Application) : AndroidViewModel(application) {
    private val Context.dataStore by preferencesDataStore(name = "settings")
    private val dataStore: DataStore<Preferences> = application.dataStore
}

Perform a Gradle sync: sometimes, Gradle may not correctly pick up new dependencies until you sync the project with the Gradle files. Additionally, try cleaning and rebuilding the project:

  • Clean Project: Build > Clean Project
  • Rebuild Project: Build > Rebuild Project