2

Imagine something like this:

class FileModel: ViewModel() {
    private val _state: MutableStateFlow<FileState> = MutableStateFlow(initialState)
    val state: StateFlow<FileState> = _state.asStateFlow()
    
    fun readFile() {
        //read file and set state
    }
}

class DatabaseModel: ViewModel() {
    private val _state: MutableStateFlow<DatabaseState> = MutableStateFlow(initialState)
    val state: StateFlow<DatabaseState> = _state.asStateFlow()

    fun readDatabase() {
        //read database and set state
    }
}

readFile() and readDatabase() can fail. What I could do is store these errors in their respective states. But that would make it difficult to show them in the UI to the user, as I would have to listen to all states of several models.

I would like to have a centralized model for errors:

class ErrorModel: ViewModel() {
    private val _state: MutableStateFlow<ErrorState> = MutableStateFlow(initialState)
    val state: StateFlow<ErrorState> = _state.asStateFlow()

    fun reportError(e: AppError) {
        //set error to state
    }
}

How could FileModel report errors to ErrorModel?

I thought about passing the function reportError to other ViewModels but I'm not sure if a reference to another Viewmodel is a good idea (I don't completly understand ViewModelStoreOwner yet and how instances are handled).

Any ideas how to solve this problem? Is the idea of having a centralized ErrorModel even a good idea?

1 Answer 1

1

This is not how view models are supposed to be used.

A view model is not a model itself. A view model is named as such because it connects the view (the UI) with the models (the data sources). This pattern is also called Model–view–viewmodel (MVVM). There are many variations, but in essence the view model needs to asssemble all required data in one place so the UI can easily access it.

You will usually have one view model per screen. You will not have one view model for file access (like your FileModel), another view model for database access (like your DatabaseModel), and one view model for errors (like your ErrorModel). So the question you are asking is missing the point, this is not what you really want to do in the first place.

Instead, your view models should be specific to the content they provide. You may have a ProductsViewModel, an AccountViewModel, a CheckoutViewModel and so on - it all depends on the actual functionality of your app.

For example: If you need to read the products from your database to display them in the ProductsScreen composable, you would have a ProductsViewModel that would expose its state like this:

class ProductsViewModel : ViewModel() {
    val state: StateFlow<ProductsUiState>
}

How exactly the data is read from the database depends on the database implementation, but if you want to handle errors here, you would eventually either have a list of products, or an error message. So ProductsUiState could look something like this:

sealed interface ProductsUiState {
    data class Success(val products: List<Product>) : ProductsUiState
    data class Error(val message: String) : ProductsUiState
}

Your composable can then decide what to do:

@Composable
fun ProductsScreen(viewModel: ProductsViewModel) {
    val state = viewModel.state.collectAsStateWithLifecycle().value

    when(state) {
        is ProductsUiState.Success -> ListOfProducts(state.products)
        is ProductsUiState.Error -> Text("Products couldn't be loaded: ${state.message}")
    }
}

The key here is that errors are only relevant at the place where the data is actually needed that is now - due to the error - absent. So you don't need to have the error message present globally throughout your app. The above error, for example, is not relevant when the AccountScreen is currently displayed instead of the ProductsScreen. So the AccountViewModel never needs this error state in the first place. It suffices if that is solely encapsulated in the ProductsViewModel.


Final note: Although you are right with the assessment that the database access can fail, don't overcomplicate things in your UI logic. What would be the benefit for the user to know that (and why) the local database couldn't be read? Errors like these only help when the user can actually do something about it.

For example, when they switched off wifi then your app cannot access the server over the internet. In that case an error message informing the user about it is helpful: The user can then decide to switch wifi back on. But what should the user do to remedy the database error? In an Android app, for example, you would be better off simply crashing the app in that case. Then the user can decide to try again by restarting the app, clearing app data respectively reinstalling the app or buying a new device.

Your app does not need to gracefully handle corrupt local storage - be it caused by a software or a hardware defect. The solutions for these kind of errors are not specific to your app, so don't make it more complicated than it needs to be.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.