J'ai commencé à apprendre Flutter et j'ai récemment passé toute la journée à essayer d'intégrer l'architecture Model-View-ViewModel dans mon application Flutter. Habituellement, j'écris pour Android en Java, j'implémente MVVM en utilisant AndroidViewModel et LiveData / MutableLiveData. Autrement dit, il existe une expérience dans la programmation et l'application du modèle, l'application est une simple minuterie. Rien ne laissait donc présager autant de temps consacré à une tâche simple.
La recherche d'articles et d'instructions sur MVVM dans Flutter (sans utiliser RxDart) a donné un exemple, sans référence à la source complète, donc je veux le rendre un peu plus facile pour ceux qui sont intéressés à étudier ce modèle dans Flutter.
Projet
Un projet sans MVVM est un écran unique avec un compte à rebours. En appuyant sur le bouton, la minuterie démarre ou s'arrête en fonction de l'état. Lorsque le temps est écoulé, une notification est émise ou un son est émis.
Description du modèle
Commençons l'implémentation de MVVM, j'ai d'abord décrit l'interface dont j'ai besoin pour interagir entre le widget et le modèle (le fichier timer_view_model.dart a été créé):
abstract class TimerViewModel { Stream<bool> get timerIsActive; Stream<String> get timeTillEndReadable; Stream<bool> get timeIsOver; void changeTimerState(); }
Autrement dit, je veux recevoir des événements qui changent l'état du bouton (arrêter le chronomètre - continuer), savoir quand le chronomètre est terminé, obtenir le temps qui doit être affiché à l'écran. Je veux également arrêter / démarrer la minuterie. À strictement parler, une description de cette interface est facultative, ici je veux juste montrer ce qui est requis du modèle.
Implémentation de ViewModel
Une autre implémentation du modèle est le fichier timer_view_model_impl.dart
La minuterie fonctionne en fait comme un StreamController avec un seul abonné. La base du code est tirée de cet article . Il y a juste une description du contrôleur, qui fonctionne sur une minuterie et il peut être mis en pause et redémarré. En général, un match presque parfait. Le code a changé pour ma tâche:
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; }
Maintenant, comment fonctionne le démarrage et l'arrêt de la minuterie à travers le modèle:
@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); } } }
Pour que la minuterie commence Ă fonctionner, vous devez vous y abonner
_timeSubscription = _timer.listen(_onTimeChange);
. Les arrêts / continuations sont implémentés via des abonnements pause / reprise (
_timeSubscription.pause();
/
_timeSubscription.resume();
). Ici, il y a un enregistrement dans le flux d'état d'activité du minuteur _timerStateActive et un flux d'informations indiquant si le minuteur était activé ou non _timerIsEnded.
Tous les contrôleurs de débit nécessitent une initialisation. Il convient également d'ajouter des valeurs initiales.
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)); }
Réception de flux comme décrit dans l'interface:
@override Stream<bool> get timeIsOver => _timerIsEnded.stream; @override Stream<bool> get timerIsActive { return _timerStateActive.stream; } @override Stream<String> get timeTillEndReadable => _timeFormatted.stream;
Autrement dit, pour écrire quelque chose dans le flux, vous avez besoin d'un contrôleur. Il est tout simplement impossible de prendre et de mettre quoi que ce soit là (une exception est lorsque le flux est généré dans une fonction). Et déjà , le widget récupère les flux finis, qui sont contrôlés par les contrôleurs de modèle.
Widget et état
Passons maintenant au widget. ViewModel initialisé dans le constructeur d'état
_MyHomePageState() { viewModel = new TimerViewModelImpl(); }
Ensuite, lors de l'initialisation, des écouteurs de threads sont ajoutés:
viewModel.timerIsActive.listen(_setIconForButton); viewModel.timeIsOver.listen(informTimerFinished); viewModel.timeTillEndReadable.listen(secondChanger);
Les écouteurs sont presque les mêmes fonctions qu'auparavant, seule la vérification des valeurs nulles a été ajoutée et _setIconForButton a légèrement changé:
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; } }); } }
Le reste des changements dans main.dart est la suppression de toute la logique de la minuterie - maintenant elle vit dans le ViewModel.
Conclusion
Mon implémentation MVVM n'utilise pas de widgets supplémentaires (tels que StreamBuilder), la composition des widgets reste la même. La situation est similaire à la façon dont Android utilise ViewModel et LiveData. Autrement dit, le modèle est initialisé, puis les écouteurs qui réagissent déjà aux modifications du modèle sont ajoutés.
Référentiel de projets avec toutes les modifications