state – Best way to display errors from a model to the user?

I’m developing an app (using Flutter) that has a model that contains most of the business logic, and a view that displays the user interface. The model can call notifyListeners to inform the view that some data in the model has changed, and the UI should be rebuilt.

I currently keep track of the status of some pieces of data in the model using flags that indicate wether the data is in an OK, ERROR or LOADING state.

Simplified example:

class ContactModel{

  int contactListState = OK; // OK/ERROR/LOADING are int constants
  List<String> contacts = ();

  Future<void> fetchContactList(){
    contactListState = LOADING;
    bool success = false;
    // Make some API call, which may or may not succeed

    contactListState = success ? OK : ERROR;

The UI would display a spinner when contactListState==LOADING or an error indicator when contactListState==ERROR.

This all works fine, BUT… now I want to display more fine grained error messages to the user about what exactly went wrong. There are a few possibilities:

  1. return Future<String> instead of Future<void>, returning a user friendly error message.
  2. store the last error message just like the state, i.e. add the member String contactListErrorMessage.
  3. throw an exception in the model, possibly containing a user friendly error message, catch it where I made the call and display the error message to the user.

Each have their pros and cons:

  1. Simple and easy to understand, but becomes more complex if I actually wanted to return something useful from the method on success.
  2. Kind of makes sense in that it is similar to the existing flag contactListState. But how long should an error message “live”? Who is responsible for clearing it? Not to mention the amount of members needed as the model grows.
  3. Makes for easier logic in the model, but probably more complex logic in the view instead. For example, one would need to add a stateful variable to store the error message in the view, so it doesn’t disappear on the next UI rebuild.

What is the most common way to solve this kind of error message handling? Are there any additional pros/cons that make any of the options a clear winner?