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.
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(); }
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) { timer.cancel(); controller.close(); // Ask stream to shut down and tell listeners. } } void startTimer() { timer = Timer.periodic(interval, tick); } void stopTimer() { if (timer != null) { timer.cancel(); timer = null; } } controller = StreamController<DateTime>( onListen: startTimer, onPause: stopTimer, onResume: startTimer, onCancel: stopTimer); return controller.stream; }
Now, how does the start and stop of the timer work through the model:
')
@override void changeTimerState() { if (_timeSubscription == null) { print("subscribe"); _timer = timedCounter(oneSec, pomodoroSize); _timerIsEnded.add(false); _timerStateActive.add(true); _timeSubscription = _timer.listen(_onTimeChange); _timeSubscription.onDone(_handleTimerEnd); } else { if (_timeSubscription.isPaused) { _timeSubscription.resume(); _timerStateActive.add(true); } else { _timeSubscription.pause(); _timerStateActive.add(false); } } }
_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(); _timerStateActive.add(false); _timerIsEnded = new StreamController(); _timeFormatted = new StreamController(); DateTime pomodoroTime = new DateTime.fromMicrosecondsSinceEpoch(pomodoroSize.inMicroseconds); _timeFormatted.add(DateFormat.ms().format(pomodoroTime)); }
Getting streams, as described in the interface:
@override Stream<bool> get timeIsOver => _timerIsEnded.stream; @override Stream<bool> get timerIsActive { return _timerStateActive.stream; } @override Stream<String> get timeTillEndReadable => _timeFormatted.stream;
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.
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:
viewModel.timerIsActive.listen(_setIconForButton); viewModel.timeIsOver.listen(informTimerFinished); viewModel.timeTillEndReadable.listen(secondChanger);
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; } }); } }
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.
Source: https://habr.com/ru/post/427327/
All Articles