بدأت في تعلم Flutter وقضيت مؤخرًا طوال اليوم محاولًا دمج بنية Model-View-ViewModel في تطبيق Flutter الخاص بي. عادة أكتب لـ Android في Java ، وأقوم بتنفيذ MVVM باستخدام AndroidViewModel و LiveData / MutableLiveData. أي أن هناك خبرة في البرمجة وتطبيق النمط ، فالتطبيق عبارة عن مؤقت بسيط. لذلك لا شيء ينذر بالكثير من الوقت الذي يقضيه في مهمة بسيطة.
أعطى البحث عن المقالات والتعليمات حول MVVM في Flutter (بدون استخدام RxDart) مثالًا واحدًا دون الإشارة إلى المصدر الكامل ، لذلك أريد أن أجعل الأمر أسهل قليلاً بالنسبة لأولئك الذين يرغبون في دراسة هذا النمط في 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.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. أي أن النموذج تمت تهيئته ، ثم تتم إضافة المستمعين الذين يتفاعلون بالفعل مع تغييرات النموذج.
مستودع المشروع مع جميع التغييرات