Flutter MobileアプリケーションのMVVMアーキテクチャ

Flutterを学び始め、最近、Model-View-ViewModelアーキテクチャをFlutterアプリケーションに組み込むために1日を費やしました。 通常、JavaでAndroid用に作成し、AndroidViewModelとLiveData / MutableLiveDataを使用してMVVMを実装します。 つまり、パターンのプログラミングと適用の経験があり、アプリケーションは単純なタイマーです。 そのため、単純なタスクにかかるような長い時間を予見するものはありませんでした。







FlutterでのMVVMに関する記事と指示の検索(RxDartを使用しない)では、完全なソースを参照せずに1つの例示したので、Flutterでこのパターンを研究することに興味がある人にとっては少し簡単にしたいと思います。







プロジェクト



MVVMを使用しないプロジェクトは、カウントダウンタイマーを備えた単一の画面です。 ボタンを押すと、状態に応じてタイマーが開始または一時停止します。 時間がなくなると、通知が発行されるか、サウンドが再生されます。







モデルの説明



MVVMの実装を開始しましょう。まず、ウィジェットとモデルの間でやり取りするために必要なインターフェイスについて説明しました(timer_view_model.dartファイルが作成されました)。







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





つまり、ボタンの状態を変更するイベント(タイマーを停止-続行)を受け取り、タイマーがいつ終了したかを知り、画面に表示する必要がある時間を取得したいのです。 また、タイマーを停止/開始したいです。 厳密に言えば、このインターフェースの説明はオプションです。ここでは、モデルに必要なものを示したいだけです。

ViewModelの実装



モデルのさらなる実装はtimer_view_model_impl.dartファイルです







タイマーは、実際には単一のサブスクライバーを持つStreamControllerとして機能します。 コードの基礎はこの記事から取られています 。 コントローラの説明だけがあります。これはタイマーで動作し、一時停止して再度開始できます。 一般的に、ほぼ完全に一致します。 私のタスクのためにコードが変更されました:







 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; }
      
      





ここで、モデル全体のタイマーの開始と停止はどのように機能しますか?







 @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);



。 停止/継続は、サブスクリプションの一時停止/再開によって実装されます( _timeSubscription.pause();



/ _timeSubscription.resume();



)。 ここでは、_timerStateActiveタイマーアクティビティステータスストリーム内のレコードと、タイマーがオンであったかどうかに関する情報のストリーム_timerIsEndedがあります。



すべてのフローコントローラーには初期化が必要です。 また、初期値を追加する価値があります。







 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)); }
      
      





インターフェイスで説明されているストリームを受信する:







 @override Stream<bool> get timeIsOver => _timerIsEnded.stream; @override Stream<bool> get timerIsActive { return _timerStateActive.stream; } @override Stream<String> get timeTillEndReadable => _timeFormatted.stream;
      
      





つまり、ストリームに何かを書き込むには、コントローラーが必要です。 そこに何かを置いて置くことはまったく不可能です(例外は、1つの関数でストリームが生成される場合です)。 そして、すでにウィジェットは、モデルコントローラーによって制御される完成したフローを取得します。







ウィジェットと状態



ウィジェットへ。 状態コンストラクターで初期化されたViewModel







 _MyHomePageState() { viewModel = new TimerViewModelImpl(); }
      
      





次に、スレッドの初期化リスナーに追加されます:







  viewModel.timerIsActive.listen(_setIconForButton); viewModel.timeIsOver.listen(informTimerFinished); viewModel.timeTillEndReadable.listen(secondChanger);
      
      





リスナーは以前とほとんど同じ機能で、nullチェックのみが追加され、_setIconForButtonが少し変更されました。



 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; } }); } }
      
      





main.dartの残りの変更は、すべてのタイマーロジックの削除です-現在はViewModelにあります。

おわりに



私のMVVM実装は追加のウィジェット(StreamBuilderなど)を使用しません。ウィジェットの構成は同じままです。 この状況は、AndroidがViewModelとLiveDataを使用する方法に似ています。 つまり、モデルが初期化され、モデルの変更にすでに反応しているリスナーが追加されます。







すべての変更を含むプロジェクトリポジトリ








All Articles