Artikel ini tumbuh dari publikasi " pola BLoC dengan contoh sederhana " di mana kami menemukan apa pola ini dan bagaimana menerapkannya dalam contoh counter sederhana klasik.
Menurut komentar dan untuk pengertian terbaik saya, saya memutuskan untuk mencoba menulis aplikasi di mana jawaban atas pertanyaan akan diterima:
- Cara mentransfer status kelas tempat BloC berada di seluruh aplikasi
- Cara menulis tes untuk pola ini
- (pertanyaan tambahan) Bagaimana menjaga status data antara peluncuran aplikasi sambil tetap berada dalam pola BLoC
Di bawah ini adalah animashka dari contoh yang dihasilkan, dan di bawah cut adalah tanya jawab :)
Dan di akhir artikel, masalah yang menarik adalah bagaimana memodifikasi aplikasi untuk menerapkan operator Debounce dari pola ReactiveX (lebih tepatnya, reactiveX adalah perpanjangan dari pola Observer)

Deskripsi aplikasi dan kode dasar
Tidak terkait dengan BLoC dan Penyedia
- Aplikasi ini memiliki tombol + - dan geser yang menggandakan tombol ini berfungsi
- Animasi dilakukan melalui flutter mixin bawaan - TickerProviderStateMixin
Ditautkan ke BLoC dan Penyedia
- Dua layar - pada yang pertama kami menggesek, pada detik perubahan counter ditampilkan
- Kami menulis status ke penyimpanan permanen telepon (iOS & Android, paket https://pub.dev/packages/shared_preferences )
- Menulis dan membaca informasi dari penyimpanan persisten tidak sinkron, kami juga melakukannya melalui BLoC
Kami sedang menulis aplikasi
Sebagai berikut dari definisi pola BLoC, tugas kami adalah menghapus semua logika dari widget dan bekerja dengan data melalui kelas di mana semua input dan output adalah Streaming.
Pada saat yang sama, karena kelas di mana BLoC berada digunakan pada layar yang berbeda, kita perlu mentransfer objek yang dibuat dari kelas ini di seluruh aplikasi.
Ada beberapa metode untuk ini, yaitu:
- Melewati konstruktor kelas, yang disebut status angkat . Kami tidak akan menggunakannya, karena ternyata sangat membingungkan, maka jangan melacak transfer negara.
- Buat dari kelas di mana kita memiliki BLoC singleton dan impor di tempat yang kita butuhkan. Ini sederhana dan nyaman, tetapi, dari sudut pandang pribadi saya, menyulitkan konstruktor kelas dan sedikit membingungkan logika.
- Gunakan paket Penyedia - yang direkomendasikan oleh tim Flutter untuk manajemen negara. Lihat videonya
Dalam contoh ini, kita akan menggunakan Penyedia - untuk memberikan contoh semua metode tidak memiliki kekuatan yang cukup :)
Struktur umum
Jadi kita ada kelas
class SwipesBloc { // some stuff }
dan agar objek yang dibuat dari kelas ini dapat diakses di seluruh pohon widget, kami, pada tingkat tertentu dari widget aplikasi, menentukan penyedia dari kelas ini. Saya melakukan ini di bagian paling atas pohon widget, tetapi yang terbaik adalah melakukannya di tingkat serendah mungkin.
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ Provider<SwipesBloc>(create: (_) => SwipesBloc()), ], child: MaterialApp( title: 'Swipe BLoC + Provider',
Setelah menambahkan desain yang indah ini ke widget apa pun di bagian bawah pohon, sebuah objek dengan semua data tersedia untuk kita. Detail cara bekerja dengan Penyedia di sini dan di sini .
Selanjutnya, kita perlu memastikan bahwa ketika Anda mengklik tombol atau geser semua data ditransfer ke Stream dan, kemudian, di semua layar, data diperbarui dari Stream yang sama.
Kelas untuk BLoC
Untuk melakukan ini, kami membuat kelas BLoC di mana kami menggambarkan tidak hanya aliran, tetapi juga penerimaan dan pencatatan status dari penyimpanan permanen telepon.
import 'dart:async'; import 'package:rxdart/rxdart.dart'; import 'package:shared_preferences/shared_preferences.dart'; class SwipesBloc { Future<SharedPreferences> prefs = SharedPreferences.getInstance(); int _counter; SwipesBloc() { prefs.then((val) { if (val.get('count') != null) { _counter = val.getInt('count') ?? 1; } else { _counter = 1; } _actionController.stream.listen(_changeStream); _addValue.add(_counter); }); } final _counterStream = BehaviorSubject<int>.seeded(1); Stream get pressedCount => _counterStream.stream; void get resetCount => _actionController.sink.add(null); Sink get _addValue => _counterStream.sink; StreamController _actionController = StreamController(); StreamSink get incrementCounter => _actionController.sink; void _changeStream(data) async { if (data == null) { _counter = 1; } else { _counter = _counter + data; } _addValue.add(_counter); prefs.then((val) { val.setInt('count', _counter); }); } void dispose() { _counterStream.close(); _actionController.close(); } }
Jika kita hati-hati melihat kelas ini, kita akan melihat bahwa:
- Setiap properti yang tersedia secara eksternal adalah input dan output dari Streaming.
- Saat pertama kali berjalan di perancang, kami mencoba mendapatkan data dari penyimpanan permanen ponsel.
- Mudah direkam dalam penyimpanan permanen ponsel
Tugas kecil untuk pemahaman yang lebih baik:
- Menghapus sepotong kode dari. Lalu dari konstruktor lebih cantik untuk membuat metode terpisah.
- Cobalah untuk mengimplementasikan kelas ini tanpa penyedia sebagai Singleton
Terima dan kirim data dalam aplikasi
Sekarang kita perlu mentransfer data ke Stream ketika mengklik tombol atau geser dan dapatkan data ini di kartu dan di layar terpisah.
Ada beberapa pilihan cara melakukan ini, saya memilih yang klasik, kami membungkus bagian-bagian pohon di mana Anda perlu menerima / mentransfer data ke Konsumen
return Scaffold( body: Consumer<SwipesBloc>( builder: (context, _swipesBloc, child) { return StreamBuilder<int>( stream: _swipesBloc.pressedCount, builder: (context, snapshot) { String counterValue = snapshot.data.toString(); return Stack( children: <Widget>[ Container(
Nah, lalu dapatkan data
_swipesBloc.pressedCount,
Transfer data
_swipesBloc.incrementCounter.add(1);
Itu saja, kami mendapat kode yang jelas dan dapat dikembangkan dalam aturan pola BLoC.
Contoh kerja
Tes
Anda dapat menguji widget, Anda dapat membuat mokas, Anda dapat e2e.
Kami akan menguji widget dan menjalankan aplikasi dengan memeriksa bagaimana peningkatan penghitung bekerja. Informasi tentang tes di sini dan di sini .
Pengujian widget
Jika kami memiliki data sinkron, maka kami dapat menguji semuanya dengan widget. Dalam kasus kami, kami hanya dapat memeriksa bagaimana widget dibuat dan bagaimana inisialisasi berjalan.
Kode di sini , dalam kode ada upaya untuk memeriksa peningkatan penghitung setelah mengklik - itu memberikan kesalahan, karena data melewati BLoC.
Untuk menjalankan tes, gunakan perintah
flutter test
Tes integrasi
Dalam opsi pengujian ini, aplikasi berjalan pada emulator, kita dapat menekan tombol, geser dan periksa apa yang terjadi sebagai hasilnya.
Untuk melakukan ini, kami membuat 2 file:
test_driver / app.dart
test_driver / app_test.dart
Di yang pertama, kami menghubungkan apa yang dibutuhkan, dan yang kedua, langsung tes. Sebagai contoh, saya melakukan pengecekan:
- Keadaan awal
- Penambahan selisih setelah menekan tombol
import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; void main() { group( 'park-flutter app', () { final counterTextFinder = find.byValueKey('counterKey'); final buttonFinder = find.byValueKey('incrementPlusButton'); FlutterDriver driver; setUpAll(() async { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) { driver.close(); } }); test('test init value', () async { expect(await driver.getText(counterTextFinder), ^_^quot quot^_^); }); test('test + 1 value after tapped', () async { await driver.tap(buttonFinder); // Then, verify the counter text is incremented by 1. expect(await driver.getText(counterTextFinder), ^_^quot quot^_^); }); }, ); }
Kode di sana
Untuk menjalankan tes, gunakan perintah
flutter drive --target=test_driver/app.dart
Tantangan
Hanya untuk memperdalam pemahaman Anda. Dalam aplikasi modern (situs), fungsi Debounce dari ReactiveX sering digunakan.
Sebagai contoh:
- Sebuah kata dimasukkan di bilah pencarian dan sebuah petunjuk keluar hanya ketika jarak antara himpunan huruf lebih dari 2 detik
- Ketika suka diletakkan, Anda dapat mengklik 10 kali per detik - penulisan ke basis data akan terjadi jika kesenjangan dalam klik lebih dari 2-3 detik
- ... dll.
Tugas: untuk membuat perubahan digit hanya jika lebih dari 2 detik berlalu antara tekanan + atau -. Untuk melakukan ini, edit hanya kelas BLoC, sisa kode harus tetap sama.
Itu saja. Jika ada yang bengkok atau salah, perbaiki di sini atau di github , cobalah untuk mencapai yang ideal :)
Pengodean yang bagus untuk semua orang!