Arsitektur MVVM dalam Aplikasi Seluler Flutter

Saya mulai belajar Flutter dan baru-baru ini menghabiskan sepanjang hari mencoba menggabungkan arsitektur Model-View-ViewModel ke dalam aplikasi Flutter saya. Biasanya saya menulis untuk Android di Java, saya mengimplementasikan MVVM menggunakan AndroidViewModel dan LiveData / MutableLiveData. Artinya, ada pengalaman dalam pemrograman dan penerapan pola, aplikasi ini adalah penghitung waktu yang sederhana. Jadi tidak ada yang meramalkan begitu banyak waktu dihabiskan untuk tugas sederhana


Pencarian untuk artikel dan instruksi tentang MVVM di Flutter (tanpa menggunakan RxDart) memberikan satu contoh, tanpa referensi ke sumber lengkap, jadi saya ingin membuatnya sedikit lebih mudah bagi mereka yang tertarik mempelajari pola ini di Flutter.


Proyek


Proyek tanpa MVVM adalah layar tunggal dengan penghitung waktu mundur. Dengan menekan tombol, timer mulai atau jeda tergantung pada negara. Ketika waktu habis, pemberitahuan dikeluarkan atau suara dimainkan.


Deskripsi model


Mari kita mulai implementasi MVVM, pertama saya menggambarkan antarmuka yang saya butuhkan untuk berinteraksi antara widget dan model (file timer_view_model.dart telah dibuat):

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

Artinya, saya ingin menerima acara yang mengubah keadaan tombol (hentikan timer - lanjutkan), untuk mengetahui kapan timer telah berakhir, untuk mendapatkan waktu yang perlu ditampilkan di layar. Saya juga ingin menghentikan / memulai timer. Sebenarnya, deskripsi antarmuka ini adalah opsional, di sini saya hanya ingin menunjukkan apa yang diperlukan dari model.

Implementasi ViewModel


Implementasi lebih lanjut dari model adalah file timer_view_model_impl.dart


Timer berfungsi sebagai StreamController dengan satu pelanggan. Dasar untuk kode ini diambil dari artikel ini . Hanya ada deskripsi controller, yang berfungsi pada timer dan dapat dijeda dan mulai lagi. Secara umum, pasangan yang hampir sempurna. Kode berubah untuk tugas saya:

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

Sekarang bagaimana cara memulai dan menghentikan timer melalui model bekerja:

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

Agar penghitung waktu mulai bekerja, Anda harus berlangganan padanya _timeSubscription = _timer.listen(_onTimeChange); . Berhenti / lanjut diimplementasikan melalui jeda / lanjutkan langganan ( _timeSubscription.pause(); / _timeSubscription.resume(); ). Di sini, ada catatan dalam aliran status aktivitas waktu _timerStateActive dan aliran informasi tentang apakah timer aktif atau tidak _timerIsEnded.

Semua pengontrol aliran memerlukan inisialisasi. Juga layak menambahkan nilai awal.

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

Menerima aliran seperti yang dijelaskan dalam antarmuka:

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

Artinya, untuk menulis sesuatu ke aliran, Anda memerlukan pengontrol. Tidak mungkin mengambil dan meletakkan apa pun di sana (pengecualian adalah saat streaming dihasilkan dalam satu fungsi). Dan sudah widget mengambil arus selesai, yang dikendalikan oleh pengontrol model.


Widget dan status


Sekarang ke widget. ViewModel diinisialisasi dalam konstruktor keadaan

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

Kemudian, dalam inisialisasi, pendengar untuk utas ditambahkan:

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

Fungsi pendengar hampir sama seperti sebelumnya, hanya pemeriksaan nol yang ditambahkan dan _setIconForButton berubah sedikit:

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

Sisa dari perubahan di main.dart adalah penghapusan semua logika timer - sekarang tinggal di ViewModel.

Kesimpulan


Implementasi MVVM saya tidak menggunakan widget tambahan (seperti StreamBuilder), komposisi widget tetap sama. Situasinya mirip dengan bagaimana Android menggunakan ViewModel dan LiveData. Artinya, model diinisialisasi, kemudian pendengar yang sudah bereaksi terhadap perubahan model ditambahkan.


Repositori proyek dengan semua perubahan

Source: https://habr.com/ru/post/id427327/


All Articles