我开始学习Flutter,最近一整天都在尝试将Model-View-ViewModel体系结构整合到Flutter应用程序中。 通常,我使用Java为Android编写程序,我使用AndroidViewModel和LiveData / MutableLiveData实现MVVM。 也就是说,有编程和应用模式的经验,该应用程序是一个简单的计时器。 因此,没有什么可以预见到在一个简单的任务上要花费那么多时间。
在Flutter中(不使用RxDart)在MVVM上搜索文章和说明给出了一个示例,但没有参考完整的源代码,因此对于那些对在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;
也就是说,要向流中写入内容,需要一个控制器。 几乎不可能带走任何东西(唯一的例外是在一个函数中生成流)。 小部件已经拾取了由模型控制器控制的完成的流程。
小部件和状态
现在到小部件。 在状态构造函数中初始化ViewModel
_MyHomePageState() { viewModel = new TimerViewModelImpl(); }
然后,在初始化中,添加线程的侦听器:
viewModel.timerIsActive.listen(_setIconForButton); viewModel.timeIsOver.listen(informTimerFinished); viewModel.timeTillEndReadable.listen(secondChanger);
侦听器与以前几乎具有相同的功能,仅添加了空检查,并且_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。 也就是说,模型被初始化,然后添加已经对模型更改做出反应的侦听器。
具有所有更改的项目存储库