Dasar-Dasar Aliran Dart

Ini adalah bagian kedua dari seri saya tentang Flutter Architecture:



Streaming adalah blok bangunan utama RxVMS , pemahaman mereka sangat diperlukan untuk bekerja dengan perpustakaan ini, jadi kami akan membahasnya lebih detail di pos ini.


Ternyata termasuk Rx dalam posting ini akan membuatnya terlalu lama, jadi saya membaginya menjadi dua bagian.


Biarkan mengalir


Saya membaca banyak komentar yang mereka katakan stream, dan terutama Rx, terlalu rumit untuk dipahami dan, akibatnya, untuk digunakan.


Saya ingin Anda tahu bahwa saya tidak menganggap diri saya seorang guru Rx. Memanfaatkan semua kekuatannya tidaklah mudah, dan saya akui bahwa saya terus belajar. Tapi izinkan saya memperbaiki satu kesalahan sejak awal: Anda tidak perlu menjadi penyihir Rx untuk mulai mendapatkan banyak manfaat dari menggunakan utas dan teknologi ini . Saya akan melakukan segala upaya untuk menjelaskan aliran kepada Anda dengan cara yang paling mudah diakses.


Apa itu utas?


Menurut pendapat saya, analogi terbaik untuk thread adalah conveyor belt. Anda dapat meletakkan sesuatu di salah satu ujungnya dan "sesuatu" ini secara otomatis akan ditransfer ke yang lain. Tidak seperti pipa fisik, utas memanipulasi objek data, mentransfernya secara otomatis dari awal - tetapi di mana? Seperti dalam pipa nyata, jika tidak ada yang menangkap data di ujung lainnya, mereka hanya akan "jatuh" dan menghilang (ini, tentu saja, tidak sepenuhnya benar untuk Dart Streams, tetapi yang terbaik untuk menangani stream seolah-olah itu masalahnya) .



Untuk menghindari kehilangan data, Anda dapat mengatur "jebakan" pada output stream. Dengan cara ini Anda dapat mengambil data dan melakukan manipulasi yang diperlukan bersamanya setiap kali objek data mencapai akhir aliran.



Ingat:


  • Jika jebakan tidak diatur, data akan hilang selamanya dan tidak akan ada cara untuk mendapatkannya lagi (sekali lagi, tidak persis dengan Dart Streams, tetapi Anda lebih baik berpura-pura)
  • Setelah mengirim data ke aliran, Anda tidak perlu menjeda program dan menunggu hingga mencapai akhir, semua ini terjadi di latar belakang.
  • Perangkap dapat menerima data kapan saja, tidak perlu segera setelah mengirim (tapi jangan khawatir, alirannya sebenarnya sangat cepat). Bayangkan Anda tidak tahu seberapa cepat atau berapa lama sabuk konveyor bergerak. Ini berarti menempatkan sesuatu di aliran benar-benar terpisah dari reaksi terhadap elemen di ujung lainnya. Perangkap Anda akan bekerja dan menangkap item ketika sampai di sana. (Beberapa dari Anda mungkin sudah menyadari bahwa ini cocok dengan cara reaktif Flutter memperbarui widgetnya)
  • Anda dapat mengatur jebakan jauh sebelum pekerjaan dimulai dan item pertama muncul
  • Alurnya bekerja berdasarkan prinsip FIFO. Data selalu datang dalam urutan ditempatkan di aliran.

Apa itu Rx?


Rx, kependekan dari Reactive Extensions, adalah aliran steroid. Ini adalah konsep yang sangat mirip dengan Streams, yang diciptakan untuk .Net framework oleh tim Microsoft. Sejak. Net sudah memiliki tipe Stream, yang digunakan untuk file I / O, mereka disebut Rx streams Observables dan menciptakan banyak fungsi untuk memanipulasi data yang melewatinya. Dart memiliki Streaming yang dibangun ke dalam spesifikasi bahasanya yang sudah menawarkan sebagian besar fungsi ini, tetapi tidak semua. Inilah sebabnya mengapa paket RxDart dikembangkan; Ini didasarkan pada Dart Streams, tetapi memperluas fungsionalitasnya. Saya akan membahas Rx dan RxDart di bagian selanjutnya dari seri ini.


Beberapa istilah


Dart Streams dan Rx menggunakan beberapa terminologi yang mungkin terlihat menyeramkan, jadi inilah terjemahannya. Pertama kali muncul istilah Dart, kemudian Rx.


  • Aliran / Teramati . Ini adalah "saluran pipa" yang dijelaskan sebelumnya. Stream dapat dikonversi ke Observable dan di mana pun Stream diharapkan, Anda dapat menetapkan Observable. Jadi jangan bingung jika saya mencampur istilah-istilah ini dalam proses menjelaskan
  • mendengarkan / berlangganan - atur perangkap pendengar
  • StreamController / Subjek . Sisi "kiri" dari sabuk konveyor tempat Anda meletakkan data dalam Stream. Mereka sedikit berbeda dalam sifat dan karakteristik mereka, tetapi melayani tujuan yang sama.
  • Memancarkan item / data . Saat ketika data muncul di pintu keluar "pipa"

Penciptaan aliran


Jika Anda berniat untuk terus mempelajari topik ini, silakan tiru proyek ini dengan contoh. Saya akan menggunakan sistem uji Dart / Flutter.


Untuk membuat aliran, Anda membuat StreamController


var controller = new StreamController<String>(); controller.add("Item1"); //      

Tipe template (dalam hal ini String) yang dilewati ketika membuat StreamController menentukan tipe objek yang dapat kami kirim ke stream. Ini bisa berupa tipe APA SAJA! Anda dapat membuat StreamController<List<MyObject>>() jika Anda mau dan streaming akan mentransfer seluruh lembar alih-alih satu objek.


Pengaturan perangkap


Jika Anda menjalankan tes yang ditentukan, maka Anda tidak dapat melihat apa-apa, karena tidak ada yang menangkap garis kami pada output stream. Sekarang atur perangkap:


 var controller = new StreamController<String>(); controller.stream.listen((item) => print(item)); //  controller.add("Item1"); controller.add("Item2"); controller.add("Item3"); 

Sekarang jebakan diatur menggunakan metode .listen() . Catatan ini terlihat seperti controller.stream.listen , tetapi jika Anda menggulirkannya ke belakang, seperti semacam album dari tahun 60an, maka makna sebenarnya dari apa yang tertulis akan muncul: "dengarkan aliran dari pengontrol ini"


Anda perlu meneruskan fungsi tertentu ke metode .listen() untuk memanipulasi data yang masuk. Fungsi harus menerima parameter dari tipe yang ditentukan saat membuat StreamController, dalam hal ini, String.


Jika Anda menjalankan kode di atas, Anda akan melihat


 Item1 Item2 Item3 

Menurut pendapat saya, masalah terbesar bagi pendatang baru ke Streams adalah Anda dapat menentukan reaksi untuk elemen yang dipancarkan jauh sebelum elemen pertama dimasukkan ke dalam aliran, memicu panggilan untuk reaksi ini.


Akhiri mendengarkan


Kode di atas melewatkan bagian kecil tapi penting. StreamSubscription listen() mengembalikan StreamSubscription - objek berlangganan streaming. Panggilan ke metode .cancel() mengakhiri langganan, membebaskan sumber daya, dan mencegah fungsi mendengarkan Anda dipanggil setelah itu menjadi tidak perlu.


 var controller = new StreamController<String>(); StreamSubscription subscription = controller.stream.listen((item) => print(item)); // This is the Trap controller.add("Item1"); controller.add("Item2"); controller.add("Item3"); //    ,        //  ,     Stream   await Future.delayed(Duration(milliseconds: 500)); subscription.cancel; 

Detail Pendengar


Fungsi untuk listen() dapat berupa fungsi lambda atau sederhana.


 void myPrint(String message) { print(message); } StreamSubscription subscription = controller.stream.listen((item) => print(item)); //  - StreamSubscription subscription2 = controller.stream.listen(myPrint); //    StreamSubscription subscription3 = controller.stream.listen((item) { print(item); print(item.toUpperCase); }); // - 

Catatan penting: sebagian besar aliran Dart hanya memungkinkan langganan satu kali, yaitu, mereka tidak dapat berlangganan kembali setelah berlangganan selesai - ini akan menimbulkan pengecualian. Ini perbedaan mereka dari implementasi Rx lainnya.


Tanda tangan penuh untuk listen() terlihat seperti ini:


  /* excerpt from the API doc * The [onError] callback must be of type `void onError(error)` or * `void onError(error, StackTrace stackTrace)`. If [onError] accepts * two arguments it is called with the error object and the stack trace * (which could be `null` if the stream itself received an error without * stack trace). * Otherwise it is called with just the error object. * If [onError] is omitted, any errors on the stream are considered unhandled, * and will be passed to the current [Zone]'s error handler. * By default unhandled async errors are treated * as if they were uncaught top-level errors. * * If this stream closes and sends a done event, the [onDone] handler is * called. If [onDone] is `null`, nothing happens. * * If [cancelOnError] is true, the subscription is automatically canceled * when the first error event is delivered. The default is `false`. */ StreamSubscription<T> listen(void onData(T event), {Function onError, void onDone(), bool cancelOnError}); 

Ini berarti bahwa Anda dapat melakukan lebih dari sekadar melewatkan satu penangan untuk data yang dikirim. Anda juga dapat memiliki penangan untuk kesalahan, dan yang lain untuk menutup aliran di sisi pengontrol ( onDone ). Pengecualian yang onError() dari dalam Stream akan onError() jika Anda memberikannya, jika tidak mereka hanya ditelan dan Anda tidak akan pernah tahu bahwa ada sesuatu yang salah.


Contoh Flutter Thread


Untuk memfasilitasi pemahaman bab-bab berikut, saya membuat cabang repositori yang terpisah.
Tolong tiru dia


Sebagai contoh pertama, saya mengambil aplikasi penghitung terkenal yang Anda dapatkan saat membuat proyek Flutter baru, dan mengatur ulangnya sedikit. Saya menambahkan kelas model untuk menahan status aplikasi, yang pada dasarnya adalah nilai penghitung:


 class Model { int _counter = 0; StreamController _streamController = new StreamController<int>(); Stream<int> get counterUpdates => _streamController.stream; void incrementCounter() { _counter++; _streamController.add(_counter); } } 

di sini Anda dapat melihat templat yang sangat khas: alih-alih menerbitkan seluruh StreamController, kami cukup menerbitkan properti Stream-nya.


Untuk membuat Model tersedia untuk UI, saya membuatnya menjadi bidang statis di objek App karena saya tidak ingin memasukkan InheritedWidget atau ServiceLocator. Sebagai contoh sederhana, ini akan lolos begitu saja, tetapi saya tidak akan melakukannya dalam aplikasi ini!


Tambahkan ke main.dart :


 class _MyHomePageState extends State<MyHomePage> { int _counter = 0; StreamSubscription streamSubscription; @override void initState() { streamSubscription = MyApp.model.counterUpdates.listen((newVal) => setState(() { _counter = newVal; })); super.initState(); } //   State   ,   , //       @override void dispose() { streamSubscription?.cancel(); super.dispose(); } 

initState() baik untuk mengatur pendengar, dan sebagai warga negara Darts yang baik, kami selalu merilis langganan di dispose() , kan?


Di pohon widget, kita hanya perlu mengadaptasi handler onPressed dari tombol FAB (tombol dengan aksi mengambang).


 floatingActionButton: new FloatingActionButton( onPressed: MyApp.model.incrementCounter, tooltip: 'Increment', child: new Icon(Icons.add), ), 

Dengan cara ini, kami membuat pemisahan bersih antara Tampilan dan Model menggunakan Stream.


Terapkan StreamBuilder


Sumber


Alih-alih menggunakan initState() dan setState() untuk kebutuhan kita, Flutter hadir dengan widget StreamBuilder nyaman. Seperti yang mungkin sudah Anda duga, dibutuhkan fungsi Stream dan metode konstruktor yang dipanggil setiap kali Stream mengembalikan nilai baru. Dan sekarang kita tidak perlu inisialisasi dan rilis eksplisit:


 body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( 'You have pushed the button this many times:', ), StreamBuilder<int>( initialData: 0, stream: MyApp.model.counterUpdates, builder: (context, snappShot) { String valueAsString = 'NoData'; if (snappShot != null && snappShot.hasData) { valueAsString = snappShot.data.toString(); } return Text( valueAsString, style: Theme.of(context).textTheme.display1, ); }), ], ), ), 

Kami hampir selesai, aku janji. Inilah tiga hal yang harus Anda ketahui:


  • Keuntungan hebat menggunakan StreamBuilder daripada solusi pertama adalah bahwa memanggil setState() di listen() selalu mengatur ulang seluruh halaman, sementara StreamBuilder hanya akan memanggil pembuatnya.
  • Variabel snapShot berisi data terbaru yang diterima dari Stream. Selalu pastikan bahwa itu berisi data yang valid sebelum menggunakannya.
  • Berdasarkan prinsip inisialisasi selama, StreamBuilder tidak bisa mendapatkan nilai selama frame pertama. Untuk menyiasatinya, kami meneruskan nilai untuk initialData , yang digunakan untuk perakitan pertama, yaitu, untuk bingkai pertama layar. Jika kami tidak lulus initialData , pembangun kami akan dipanggil untuk pertama kalinya dengan data yang tidak valid. Alternatif untuk menggunakan initialData adalah mengembalikan widget placeholder jika snapShot tidak valid, yang ditampilkan hingga kami mendapatkan data yang valid, misalnya:


     // ,           StreamBuilder<int>( stream: MyApp.model.databaseUpdates, builder: (context, snappShot) { if (snappShot != null && snappShot.hasData) { return Text( snappShot.data.toString(), style: Theme.of(context).textTheme.display1, ); } //      ,   Spinner return CircularProgressIndicator (); }) 


Pada posting selanjutnya kita akan melihat bagaimana mengkonversi data di stream kita dan melakukannya dengan cepat. Banyak terima kasih kepada Scott Stoll untuk membaca bukti dan umpan balik penting.



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


All Articles