FLUTTER TUTORIAL

Using MVVM architecture in Flutter

Saurabh Pant
Dev Genius
Published in
4 min readJan 6, 2024

--

Making layers for the Cake!

As Flutter is UI toolkit, it is very important to keep our UI separate from the business logic. This helps us in maintaining and modifying the UI quite easily. Now if you’ve developed for native Android apps then you must be familiar with this approach but if you’re starting on Flutter, nothing to worry about, stay til the end to learn how can we use MVVM approach in Flutter.

What is MVVM?

Firstly, lets understand what this MVVM architecture is. Well! it is Model-View-ViewModel approach. This technique helps us in separating our data modelling logic from UI. It creates a layer between our UI and data modelling by providing a state to our UI. Our UI simply works on that state and update itself. The UI state is maintained by the viewmodel. That simple!

For instance, a dashboard screen with the MVVM architecture would look like this.

The UI state

Let’s consider an example where we have a dashboard screen in which we show

  • a simple loader while loading data
  • if we successfully received the data, we show success screen
  • if we failed to receive the data, we show error screen

To develop our dashboard screen, let’s first discuss what our dashboard ui state would contain. This state is the medium for our ui to understand how and what to render on screen.

In our example, we’ve the following state and the class would look like

class DashboardUiState {
final List<User>? users; // list of users
final bool? isLoading; // flag to indicate if the request is in progress
final String? error; // variable to show error message on UI

DashboardUiState({this.users, this.isLoading, this.error});

}

The class is quite self explanatory. We have flags for maintaining our UI as per our requirement. Let’s see how we gonna use it.

The ViewModel

Time to see how we actually create the viewmodel layer. So let’s create a class named DashboardViewModel as follows

class DashboardViewModel {}

Inside this class let’s create a stream of our ui state to emit everytime we receive an update in our data request as follows

class DashboardViewModel {
final _repository = SomeRepository(); // to fetch data from source

final StreamController<DashboardUiState> _uiStateController =
StreamController.broadcast();

// ui state stream to keep updating our stream for ui to update itself
Stream<DashboardUiState> get uiStateStream => _uiStateController.stream;
}

Here, we created our ui stream Stream<DashboardUiState> which we’ll observe on our UI to update it.

Now finally we create a function fetchUsers which will request data from our data sources.

class DashboardViewModel {
final _repository = SomeRepository(); // to fetch data from source

final StreamController<DashboardUiState> _uiStateController =
StreamController.broadcast();

// ui state stream to keep updating our stream for ui to update itself
Stream<DashboardUiState> get uiStateStream => _uiStateController.stream;

fetchUsers() async {
// adding a ui state indicating that we've a request in progress
_uiStateController.add(DashboardUiState(isLoading: true));
try {
/* adding users on our stream for the ui in the new uiState */
final result = await _repository.fetchUsers();
_uiStateController.add(DashboardUiState(users: result));
} on DataException catch (e) {
_uiStateController.add(DashboardUiState(error: e.errorMsg));
}
}
}

And that is it! So every time we’ve an update, we push a new ui state with the corresponding data.

The UI

Finally, its time for UI to listen to its ui state from view model and keep updating itself. Let’s do it.

Our dashboard screen will be a Stateful widget as follows

class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});

@override
State<StatefulWidget> createState() => _DashboardContainer();
}

class _DashboardContainer extends State<DashboardScreen> {
final _viewModel = DashboardViewModel();
var _uiState = DashboardUiState();
...
}

Let’s first create our build method for different ui state as follows

  @override
Widget build(BuildContext context) {
if (_uiState.isLoading ?? false) {
// show a progress indicator for loading state
return CircularProgressIndicator();
}

if (_uiState.error?.isNotEmpty ?? false) {
// show an error page
return Expanded(
child: PageNotFoundScreen(),
);
}

// if our code reaches here, it means we've the users list
// obviously you can check for users list size etc too
return UsersListScreen(users: _uiState.users!);
}

See! How easy to read and clean it became. Now let’s listen to the uistate stream in order to update the ui for every new ui state.

  @override
void initState() {
super.initState();
/* listening to the ui state for users which is updated in the view model */
_controller.uiStateStream.listen((uiState) {
setState(() {
_uiState = uiState;
});
});

/* fetch call for user */
_controller.fetchUsers();
}

We need to set up our stream listener only once so we do that in our initState function.

Bamm! And that is it. We’ve successfully added MVVM architecture for our Flutter app. This also made our code so clean and easy to read and maintain.

This is one way I found quite useful in my projects. It is clean and simple. You can try it out too and let me know what do you think.

That is all for now! Stay tuned!

Connect with me (if the content is helpful to you ) on

Until next time…

Cheers!

--

--

App Developer (Native & Flutter) | Mentor | Writer | Youtuber @_zaqua | Traveller | Content Author & Course Instructor @Droidcon | Frontend Lead @MahilaMoney