Ini adalah bagian keempat dari seri arsitektur Flutter saya:
Meskipun 2 bagian sebelumnya jelas tidak terkait dengan pola RxVMS, mereka diperlukan untuk pemahaman yang jelas tentang pendekatan ini. Sekarang kita beralih ke paket paling penting yang Anda perlukan untuk menggunakan RxVMS dalam aplikasi Anda.
GetIt: ServiceLocator Cepat
Saat Anda mengingat diagram yang menunjukkan berbagai elemen RxVMS dalam aplikasi ...

... mungkin Anda bertanya-tanya bagaimana berbagai pemetaan, manajer, dan layanan saling mengetahui. Lebih penting lagi, Anda mungkin bertanya-tanya bagaimana satu elemen dapat mengakses fungsi yang lain.
Dengan begitu banyak pendekatan berbeda (seperti Widget yang Diwarisi, wadah IoC, DI ...), saya pribadi lebih suka Service Locator. Dalam hal ini, saya memiliki artikel khusus tentang GetIt - implementasi saya dari pendekatan ini, tetapi di sini saya akan sedikit menyentuh topik ini. Secara umum, Anda mendaftarkan objek dalam layanan ini satu kali, dan kemudian Anda memiliki akses ke sana di seluruh aplikasi. Ini semacam singleton ... tetapi dengan lebih banyak fleksibilitas.
Gunakan
Menggunakan GetIt cukup jelas. Di awal aplikasi, Anda mendaftarkan layanan dan / atau manajer yang Anda rencanakan untuk digunakan selanjutnya. Di masa depan, panggil saja metode GetIt untuk mengakses instance kelas terdaftar.
Fitur yang bagus adalah Anda dapat mendaftarkan antarmuka atau kelas abstrak seperti implementasi konkret. Saat mengakses sebuah instance, cukup gunakan antarmuka / abstraksi, dengan mudah mengganti implementasi yang diperlukan selama pendaftaran. Ini memungkinkan Anda untuk dengan mudah mengganti Layanan asli ke MockService.
Sedikit latihan
Saya biasanya menginisialisasi SeviceLocator saya dalam file bernama service_locator.dart melalui variabel global. Dengan demikian satu variabel global diperoleh untuk seluruh proyek.
Kapan pun Anda ingin mengakses, panggil saja
 RegistrationType object = sl.get<RegistrationType>();  
Catatan yang sangat penting: Saat menggunakan GetIt, SELALU menggunakan gaya yang sama saat mengimpor file - baik paket (disarankan) atau jalur relatif, tetapi tidak keduanya mendekati sekaligus. Ini karena Dart memperlakukan file-file tersebut sebagai berbeda, terlepas dari identitasnya.
Jika ini sulit bagi Anda, silakan kunjungi blog saya untuk detailnya.
Perintah
Sekarang kita menggunakan GetIt untuk mengakses objek kita di mana saja (termasuk antarmuka pengguna), saya ingin menjelaskan bagaimana kita dapat mengimplementasikan fungsi-fungsi handler untuk acara-acara UI. Cara paling sederhana adalah menambahkan fungsi ke manajer dan memanggilnya di widget:
 class SearchManager { void lookUpZip(String zip); } 
dan kemudian di UI
 TextField(onChanged: sl.get<SearchManager>().lookUpZip,) 
Ini akan lookUpZip pada setiap perubahan di TextField . Tetapi bagaimana kita menyampaikan hasilnya kepada kita? Karena kami ingin menjadi reaktif, kami akan menambahkan StreamController ke SearchManager kami:
 abstract class SearchManager { Stream<String> get nameOfCity; void lookUpZip(String zip); } class SearchManagerImplementation implements SearchManager{ @override Stream<String> get nameOfCity => cityController.stream; StreamController<String> cityController = new StreamController(); @override Future<void> lookUpZip(String zip) async { var cityName = await sl.get<ZipApiService>().lookUpZip(zip); cityController.add(cityName); } } 
dan di UI:
 StreamBuilder<String>( initialData:'', stream: sl.get<SearchManager>().nameOfCity, builder: (context, snapshot) => Text(snapShot.data); 
Meskipun pendekatan ini berhasil, itu tidak optimal. Inilah masalahnya:
- kode redundan - kita selalu harus membuat metode, StreamController, dan pengambil untuk alirannya, jika kita tidak ingin menampilkannya secara eksplisit di domain publik
- status sibuk - bagaimana jika kita ingin menampilkan Spinner saat fungsi melakukan tugasnya?
- penanganan kesalahan - apa yang terjadi jika suatu fungsi melempar pengecualian?
Tentu saja, kita bisa menambahkan lebih banyak StreamControllers untuk menangani status dan kesalahan ... tetapi segera itu membosankan, dan paket rx_command berguna di sini .
RxCommand menyelesaikan semua masalah di atas dan banyak lagi. RxCommand merangkum fungsi (sinkron atau asinkron) dan secara otomatis memposting hasilnya ke aliran.
Menggunakan RxCommand, kita dapat menulis ulang manajer kita seperti ini:
 abstract class SearchManager { RxCommand<String,String> lookUpZipCommand; } class SearchManagerImplementation implements SearchManager{ @override RxCommand<String,String> lookUpZipCommand; SearchManagerImplementation() { lookUpZipCommand = RxCommand.createAsync((zip) => sl.get<ZipApiService>().lookUpZip(zip)); } } 
dan di UI:
 TextField(onChanged: sl.get<SearchManager>().lookUpZipCommand,) ... StreamBuilder<String>( initialData:'', stream: sl.get<SearchManager>().lookUpZipCommand, builder: (context, snapshot) => Text(snapShot.data); 
yang jauh lebih ringkas dan mudah dibaca.
Perintah Rx secara detail

RxCommand memiliki satu input dan lima output yang dapat diobservasi:
- canExecuteInput adalah - Observable<bool>opsional yang dapat Anda lewati ke fungsi pabrik saat membuat RxCommand. Ini memberi sinyal kepada RxCommand apakah dapat dijalankan, tergantung pada nilai terakhir yang diterima.
 
 
- isExecuting adalah - Observable<bool>yang memberi sinyal apakah perintah saat ini menjalankan fungsinya. Ketika sebuah tim sibuk, itu tidak dapat dijalankan kembali. Jika Anda ingin menampilkan Spinner saat runtime, dengarkan- isExecuting
 
 
- canExecute adalah - Observable<bool>yang memberi sinyal kemampuan untuk menjalankan perintah. Ini, misalnya, berjalan baik dengan StreamBuilder untuk mengubah tampilan tombol antara kondisi on / off.
 artinya adalah sebagai berikut:
 
 -  - Observable<bool> canExecute = Observable.combineLatest2<bool,bool>(canExecuteInput,isExecuting) => canExecuteInput && !isExecuting).distinct.
 
 - yang artinya 
 - akan falsejika isExecuting mengembalikantrue
- itu akan dikembalikan truehanya jika isExecuting mengembalikanfalseDAN canExecuteInput tidak mengembalikanfalse.
 
 
- thrownExceptions adalah - Observable<Exception>. Semua pengecualian yang dapat dibuang oleh fungsi yang dikemas akan ditangkap dan dikirim ke Observable ini. Lebih mudah untuk mendengarkannya dan menampilkan kotak dialog jika terjadi kesalahan.
 
 
- (tim itu sendiri) sebenarnya juga bisa diamati. Nilai-nilai yang dikembalikan oleh fungsi kerja akan dikirim pada saluran ini, sehingga Anda bisa langsung meneruskan RxCommand ke StreamBuilder sebagai parameter aliran 
 
- hasil berisi semua status perintah dalam satu - Observable<CommandResult>, di mana- CommandResultdidefinisikan sebagai
 
 
 
.results Observable sangat berguna jika Anda ingin meneruskan hasil dari perintah langsung ke StreamBuilder. Ini akan menampilkan konten yang berbeda tergantung pada status perintah, dan itu berfungsi dengan sangat baik dengan RxLoader dari paket rx_widgets . Berikut adalah contoh widget RxLoader yang menggunakan .results Observable:
 Expanded(  
Membuat Perintah Rx
RxCommands dapat menggunakan fungsi sinkron dan asinkron yang:
- Mereka tidak memiliki parameter dan tidak mengembalikan hasilnya;
- Mereka memiliki parameter dan tidak mengembalikan hasilnya;
- Mereka tidak memiliki parameter dan mengembalikan hasil;
- Mereka memiliki parameter dan mengembalikan hasilnya;
Untuk semua opsi, RxCommand menawarkan beberapa metode pabrik, dengan mempertimbangkan penangan yang sinkron dan asinkron:
 static RxCommand<TParam, TResult> createSync<TParam, TResult>(Func1<TParam, TResult> func,... static RxCommand<void, TResult> createSyncNoParam<TResult>(Func<TResult> func,... static RxCommand<TParam, void> createSyncNoResult<TParam>(Action1<TParam> action,... static RxCommand<void, void> createSyncNoParamNoResult(Action action,... static RxCommand<TParam, TResult> createAsync<TParam, TResult>(AsyncFunc1<TParam, TResult> func,... static RxCommand<void, TResult> createAsyncNoParam<TResult>(AsyncFunc<TResult> func,... static RxCommand<TParam, void> createAsyncNoResult<TParam>(AsyncAction1<TParam> action,... static RxCommand<void, void> createAsyncNoParamNoResult(AsyncAction action,... 
Bahkan jika fungsi Anda yang dikemas tidak mengembalikan nilai, RxCommand akan mengembalikan nilai kosong setelah fungsi dijalankan. Dengan demikian, Anda dapat mengatur pendengar untuk perintah seperti itu sehingga merespon penyelesaian fungsi.
Akses ke hasil terbaru
RxCommand.lastResult memberi Anda akses ke nilai sukses terakhir dari hasil eksekusi perintah, yang dapat digunakan sebagai initialData di StreamBuilder.
Jika Anda ingin mendapatkan hasil terakhir yang termasuk dalam acara CommandResult pada saat run time atau jika terjadi kesalahan, Anda dapat mengirimkan emitInitialCommandResult = true saat membuat perintah.
Jika Anda ingin menetapkan nilai awal ke .lastResult , misalnya, jika Anda menggunakannya sebagai initialData di StreamBuilder, Anda bisa meneruskannya dengan parameter initialLastResult saat membuat perintah.
Contoh - membuat Flutter reaktif
Versi terbaru dari contoh telah diatur ulang untuk RxVMS , jadi sekarang Anda harus memiliki pilihan yang baik tentang cara menggunakannya.
Karena ini adalah aplikasi yang sangat sederhana, kami hanya perlu satu manajer:
 class AppManager { RxCommand<String, List<WeatherEntry>> updateWeatherCommand; RxCommand<bool, bool> switchChangedCommand; RxCommand<String, String> textChangedCommand; AppManager() {  
Anda dapat menggabungkan Perintah Rx yang berbeda bersama-sama. Perhatikan bahwa switchedChangedCommand sebenarnya adalah canExecute yang dapat diobservasi untuk updateWeatherCommand .
Sekarang mari kita lihat bagaimana Manajer digunakan dalam antarmuka pengguna:
  return Scaffold( appBar: AppBar(title: Text("WeatherDemo")), resizeToAvoidBottomPadding: false, body: Column( children: <Widget>[ Padding( padding: const EdgeInsets.all(16.0), child: TextField( key: AppKeys.textField, autocorrect: false, controller: _controller, decoration: InputDecoration( hintText: "Filter cities", ), style: TextStyle( fontSize: 20.0, ),  
Pola Penggunaan Khas
Kami telah melihat satu cara untuk merespons berbagai status perintah menggunakan CommandResults . Dalam kasus di mana kita ingin menampilkan apakah perintah itu berhasil (tetapi tidak menampilkan hasilnya), pola umum adalah mendengarkan perintah Observables di fungsi initState StatefulWidget. Ini adalah contoh dari proyek nyata.
Definisi untuk createEventCommand :
  RxCommand<Event, void> createEventCommand; 
Ini akan membuat objek Acara dalam database dan tidak akan mengembalikan nilai nyata apa pun. Tetapi, seperti yang telah kita pelajari sebelumnya, bahkan perintah RxC dengan tipe pengembalian void akan mengembalikan item data tunggal saat selesai. Dengan demikian, kita dapat menggunakan perilaku ini untuk memicu tindakan dalam aplikasi kita segera setelah perintah selesai:
 @override void initState() {  
Penting : jangan lupa untuk menyelesaikan langganan saat kami tidak lagi membutuhkannya:
 @override void dispose() { _eventCommandSubscription?.cancel(); _errorSubscription?.cancel(); super.dispose(); } 
Selain itu, jika Anda ingin menggunakan layar penghitung yang sibuk, Anda dapat:
- mendengarkan perintah isExecutingObservable dalam fungsiinitState;
- perlihatkan / sembunyikan konter dalam berlangganan; juga
- gunakan Command itu sendiri sebagai sumber data untuk StreamBuilder
Membuat hidup lebih mudah dengan RxCommandListeners
Jika Anda ingin menggunakan beberapa Observable, Anda mungkin harus mengelola beberapa langganan. Kontrol langsung mendengarkan dan melepaskan sekelompok langganan bisa sulit, membuat kode kurang dibaca dan menempatkan Anda pada risiko kesalahan (misalnya, lupa cancel dalam proses penyelesaian).
Versi terbaru dari rx_command menambahkan helper class RxCommandListener , yang dirancang untuk menyederhanakan pemrosesan ini. Konstruktornya menerima perintah dan penangan untuk berbagai perubahan status:
 class RxCommandListener<TParam, TResult> { final RxCommand<TParam, TResult> command;  
Anda tidak perlu melewati semua fungsi handler. Semuanya bersifat opsional, jadi Anda cukup mentransfer yang Anda butuhkan. Anda hanya perlu memanggil dispose untuk RxCommandListener Anda dalam fungsi dispose Anda, dan itu akan membatalkan semua yang digunakan di dalam berlangganan.
Mari kita bandingkan kode yang sama dengan dan tanpa RxCommandListener dalam contoh nyata lainnya. Perintah selectAndUploadImageCommand digunakan di sini pada layar obrolan tempat pengguna dapat mengunggah gambar. Ketika perintah dipanggil:
- Dialog ImagePicker ditampilkan.
- Setelah memilih gambar dimuat
- Setelah unduhan selesai, perintah mengembalikan alamat penyimpanan gambar sehingga Anda dapat membuat entri obrolan baru.
Tanpa RxCommandListener :
 _selectImageCommandSubscription = sl .get<ImageManager>() .selectAndUploadImageCommand .listen((imageLocation) async { if (imageLocation == null) return;  
Menggunakan RxCommandListener :
 selectImageListener = RxCommandListener( command: sl.get<ImageManager>().selectAndUploadImageCommand, onValue: (imageLocation) async { if (imageLocation == null) return; sl.get<EventManager>().createChatEntryCommand(new ChatEntry( event: widget.event, isImage: true, content: imageLocation.downloadUrl, )); }, onIsBusy: () => MySpinner.show(context), onNotBusy: MySpinner.hide, onError: (ex) => showMessageDialog(context, 'Upload problem', "We cannot upload your selected image at the moment. Please check your internet connection")); 
Secara umum, saya akan selalu menggunakan RxCommandListener jika ada lebih dari satu yang bisa diamati.
Coba RxCommands dan lihat bagaimana hal itu dapat membuat hidup Anda lebih mudah.
Omong-omong, Anda tidak perlu menggunakan RxVMS untuk memanfaatkan RxCommands .
Untuk informasi lebih lanjut tentang RxCommand baca paket RxCommand readme .