MVVM architecture in mobile applications on Flutter

I started learning Flutter and recently spent the whole day trying to integrate the Model-View-ViewModel architecture into my application on Flutter. I usually write for Android in Java, I implement MVVM using AndroidViewModel and LiveData / MutableLiveData. That is, the experience of programming and applying a pattern is, the application is a simple timer. So nothing foreshadowed so much time-consuming for a simple task.

The search for articles and instructions on MVVM in Flutter (without using RxDart) gave one example without reference to the full source, so I want to make it a little easier for those interested in learning this pattern in Flutter.


A project without MVVM is a single screen with a countdown timer. By pressing the button, the timer starts or pauses depending on the state. When time runs out, a notification is issued or a sound is played.

Model Description

Let's start the implementation of MVVM, first I described the interface that I would need for the interaction between the widget and the model (the file timer_view_model.dart was created):

abstract class TimerViewModel {
  Stream<bool> get timerIsActive;
  Stream<String> get timeTillEndReadable;
  Stream<bool> get timeIsOver;
   void changeTimerState();

That is, I want to receive button state change events (stop the timer - continue), to know when the timer is over, to get the time that needs to be displayed on the screen. I also want to stop / start the timer. Strictly speaking, the description of this interface is optional, here I just want to show what is required of the model.

ViewModel implementation

Further implementation of the model is the file timer_view_model_impl.dart

The timer works in fact as a StreamController with one subscriber. The basis for the code is taken from this article . There is just a description of the controller, which runs on a timer and can be paused and started again. In general, almost a perfect match. Code changed for my task:

static Stream<DateTime> timedCounter(Duration interval, Duration maxCount) {
    StreamController<DateTime> controller;
    Timer timer;
    DateTime counter = new DateTime.fromMicrosecondsSinceEpoch(maxCount.inMicroseconds);
     void tick(_) {
      counter = counter.subtract(oneSec);
      controller.add(counter); // Ask stream to send counter values as event.
      if (counter.millisecondsSinceEpoch == 0) {
        controller.close(); // Ask stream to shut down and tell listeners.
     void startTimer() {
      timer = Timer.periodic(interval, tick);
     void stopTimer() {
      if (timer != null) {
        timer = null;
     controller = StreamController<DateTime>(
        onListen: startTimer,
        onPause: stopTimer,
        onResume: startTimer,
        onCancel: stopTimer);

Now, how does the start and stop of the timer work through the model:

  void changeTimerState() {
    if (_timeSubscription == null) {
      _timer = timedCounter(oneSec, pomodoroSize);
      _timeSubscription = _timer.listen(_onTimeChange);
    } else {
      if (_timeSubscription.isPaused) {
      } else {

To start the timer, you need to subscribe to it _timeSubscription = _timer.listen(_onTimeChange);. Stop / continue implemented through pause / resume subscriptions ( _timeSubscription.pause();/ _timeSubscription.resume();). It also records the _timerStateActive timer activity status stream and the flow of information about whether or not the _timerIsEnded timer has been turned on.

All thread controllers require initialization. Also add initial values.

TimerViewModelImpl() {
    _timerStateActive = new StreamController();
    _timerIsEnded = new StreamController();
    _timeFormatted = new StreamController();
    DateTime pomodoroTime = new DateTime.fromMicrosecondsSinceEpoch(pomodoroSize.inMicroseconds);

Getting streams, as described in the interface:

  Stream<bool> get timeIsOver =>;
  Stream<bool> get timerIsActive {
  Stream<String> get timeTillEndReadable =>;

That is, to write something to the stream, you need a controller. Just so take and put something there can not (exception - when the stream is generated in one function). And already the widget takes ready-made threads, which are controlled by the model controllers.

Widget and state

Now to the widget. ViewModel is initialized in the state constructor

_MyHomePageState() {
    viewModel = new TimerViewModelImpl();

Then, in the initialization, listeners are added for the streams:


Listeners are almost the same functions that were before, only a check for null was added and _setIconForButton changed a bit:

Icon iconTimerStart = new Icon(iconStart);
Icon iconTimerPause = new Icon(iconCancel);
void _setIconForButton(bool started) {
    if (started != null) {
      setState(() {
        if (started) {
          iconTimer = iconTimerPause;
        } else {
          iconTimer = iconTimerStart;

The remaining changes in main.dart are the removal of all timer logic - now it lives in the ViewModel.


My version of the MVVM implementation does not use additional widgets (such as StreamBuilder), the composition of the widgets remains the same. The situation is similar to how ViewModel and LiveData are used in Android. That is, the model is initialized, then listeners are added that are already responding to changes in the model.

Project repository with all changes

Also popular now: