วิธีใช้ประโยชน์จาก BLoC สำหรับการแชร์โค้ดใน Flutter และ AngularDart

เผยแพร่แล้ว: 2022-03-11

กลางปีที่แล้ว ฉันต้องการย้ายแอป Android ไปยัง iOS และเว็บ Flutter เป็นตัวเลือกสำหรับแพลตฟอร์มมือถือ และฉันกำลังคิดว่าจะเลือกอะไรสำหรับเว็บไซต์

ในขณะที่ฉันตกหลุมรัก Flutter ตั้งแต่แรกเห็น ฉันยังคงมีข้อแม้: ในขณะที่เผยแพร่สถานะลงในแผนผังวิดเจ็ต Flutter's InheritedWidget หรือ Redux—ด้วยรูปแบบต่างๆ ทั้งหมด—จะใช้งานได้ แต่ด้วยเฟรมเวิร์กใหม่อย่าง Flutter คุณจะ คาดหวังว่าเลเยอร์การดูจะมีปฏิกิริยาตอบสนองมากขึ้นเล็กน้อย กล่าวคือ วิดเจ็ตจะไร้สัญชาติ และเปลี่ยนแปลงตามสถานะที่ป้อนจากภายนอก แต่กลับไม่ใช่ นอกจากนี้ Flutter รองรับเฉพาะ Android และ iOS แต่ฉันต้องการเผยแพร่บนเว็บ ฉันมีตรรกะทางธุรกิจมากมายในแอปของฉัน และฉันต้องการใช้ซ้ำให้มากที่สุด และแนวคิดในการเปลี่ยนรหัสอย่างน้อยสองที่สำหรับการเปลี่ยนแปลงครั้งเดียวในตรรกะทางธุรกิจนั้นไม่เป็นที่ยอมรับ

ฉันเริ่มมองหาวิธีที่จะเอาชนะสิ่งนี้และเจอ BLoC สำหรับการแนะนำตัวแบบสั้นๆ ฉันแนะนำให้ดู Flutter/AngularDart – การแชร์โค้ดร่วมกันได้ดีขึ้น (DartConf 2018) เมื่อคุณมีเวลา

รูปแบบ BLoC

ไดอะแกรมของโฟลว์การสื่อสารในมุมมอง BLoC ที่เก็บ และชั้นข้อมูล

BLoC เป็นคำที่ประดิษฐ์ขึ้นโดย Google ซึ่งหมายถึง "ประโยชน์ ใช้สอย lo gic c omponents" แนวคิดของรูปแบบ BLoC คือการจัดเก็บตรรกะทางธุรกิจของคุณให้มากที่สุดเท่าที่จะเป็นไปได้ในโค้ด Dart แท้ ๆ เพื่อให้แพลตฟอร์มอื่นสามารถนำกลับมาใช้ใหม่ได้ เพื่อให้บรรลุสิ่งนี้ มีกฎที่คุณต้องปฏิบัติตาม:

  • สื่อสารในชั้น มุมมองสื่อสารกับเลเยอร์ BLoC ซึ่งสื่อสารกับที่เก็บ และที่เก็บข้อมูลคุยกับชั้นข้อมูล อย่าข้ามเลเยอร์ขณะสื่อสาร
  • สื่อสารผ่านอินเทอร์เฟซ อินเทอร์เฟซต้องเขียนด้วยรหัส Dart ที่ไม่ขึ้นกับแพลตฟอร์ม สำหรับข้อมูลเพิ่มเติม โปรดดูเอกสารประกอบเกี่ยวกับอินเทอร์เฟซโดยนัย
  • BLoCs เปิดเผยเฉพาะสตรีมและซิงก์ I/O ของ BLoC จะกล่าวถึงในภายหลัง
  • ให้มุมมองที่เรียบง่าย เก็บตรรกะทางธุรกิจออกจากมุมมอง พวกเขาควรแสดงข้อมูลและตอบสนองต่อการโต้ตอบของผู้ใช้เท่านั้น
  • ทำให้แพลตฟอร์ม BLoCs ไม่เชื่อเรื่องพระเจ้า BLoCs เป็นรหัส Dart ล้วนๆ ดังนั้นจึงไม่ควรมีตรรกะหรือการพึ่งพาเฉพาะแพลตฟอร์ม อย่าแยกสาขาเป็นรหัสตามเงื่อนไขของแพลตฟอร์ม BLoCs นั้นใช้ตรรกะใน Dart ล้วนๆ และอยู่เหนือการจัดการกับแพลตฟอร์มฐาน
  • ฉีดการพึ่งพาเฉพาะแพลตฟอร์ม นี่อาจฟังดูขัดแย้งกับกฎข้างต้น แต่โปรดฟังฉัน BLoC นั้นไม่เชื่อเรื่องพระเจ้าของแพลตฟอร์ม แต่ถ้าพวกเขาต้องการสื่อสารกับที่เก็บเฉพาะแพลตฟอร์มล่ะ ฉีดเลย. ด้วยการตรวจสอบการสื่อสารผ่านอินเทอร์เฟซและการฉีดที่เก็บเหล่านี้ เราจึงมั่นใจได้ว่าไม่ว่าที่เก็บของคุณจะเขียนขึ้นสำหรับ Flutter หรือ AngularDart BLoC ก็ไม่สนใจ

สิ่งสุดท้ายที่ต้องจำไว้คืออินพุตสำหรับ BLoC ควรเป็นซิงก์ ในขณะที่เอาต์พุตจะผ่านสตรีม ทั้งสองส่วนนี้เป็นส่วนหนึ่งของ StreamController

หากคุณปฏิบัติตามกฎเหล่านี้อย่างเคร่งครัดในขณะที่เขียนแอปบนเว็บ (หรือมือถือ!) การสร้างเวอร์ชันสำหรับอุปกรณ์พกพา (หรือเว็บ!) สามารถทำได้ง่ายพอๆ กับการสร้างมุมมองและอินเทอร์เฟซเฉพาะแพลตฟอร์ม แม้ว่าคุณจะเพิ่งเริ่มใช้ AngularDart หรือ Flutter ก็ยังง่ายที่จะสร้างมุมมองด้วยความรู้พื้นฐานเกี่ยวกับแพลตฟอร์ม คุณอาจใช้ codebase ซ้ำมากกว่าครึ่ง รูปแบบ BLoC ช่วยให้ทุกอย่างมีโครงสร้างและดูแลรักษาง่าย

การสร้างแอป AngularDart และ Flutter BLoC Todo

ฉันสร้างแอปสิ่งที่ต้องทำง่ายๆ ใน Flutter และ AngularDart แอปนี้ใช้ Firecloud เป็นแบ็คเอนด์และเป็นแนวทางเชิงโต้ตอบเพื่อดูการสร้าง แอพมีสามส่วน:

  • bloc
  • todo_app_flutter
  • todoapp_dart_angular

คุณสามารถเลือกให้มีชิ้นส่วนเพิ่มเติมได้ ตัวอย่างเช่น อินเทอร์เฟซข้อมูล อินเทอร์เฟซสำหรับโลคัลไลเซชัน ฯลฯ สิ่งที่ต้องจำไว้คือแต่ละเลเยอร์ควรสื่อสารกับอีกชั้นหนึ่งผ่านอินเทอร์เฟซ

รหัส BLoC

ในไดเร็กทอรี bloc/ :

  • lib/src/bloc : โมดูล BloC ถูกเก็บไว้ที่นี่เป็นไลบรารี Dart แท้ที่มีตรรกะทางธุรกิจ
  • lib/src/repository : ส่วนต่อประสานกับข้อมูลถูกเก็บไว้ในไดเร็กทอรี
  • lib/src/repository/firestore : ที่เก็บประกอบด้วยอินเทอร์เฟซ FireCloud กับข้อมูลร่วมกับโมเดลของมัน และเนื่องจากเป็นแอปตัวอย่าง เราจึงมีโมเดลข้อมูล todo.dart โมเดลเดียวและอินเทอร์เฟซเดียวไปยังข้อมูล todo_repository.dart อย่างไรก็ตาม ในแอปในโลกแห่งความเป็นจริง จะมีโมเดลและอินเทอร์เฟซของที่เก็บเพิ่มเติม
  • lib/src/repository/preferences มี preferences_interface.dart ซึ่งเป็นอินเทอร์เฟซแบบง่ายที่จัดเก็บชื่อผู้ใช้ที่ลงชื่อเข้าใช้สำเร็จไปยังที่จัดเก็บในเครื่องบนเว็บหรือค่ากำหนดที่ใช้ร่วมกันบนอุปกรณ์มือถือ
 //BLOC abstract class PreferencesInterface{ //Preferences final DEFAULT_USERNAME = "DEFAULT_USERNAME"; Future initPreferences(); String get defaultUsername; void setDefaultUsername(String username); }

การใช้งานเว็บและมือถือต้องใช้สิ่งนี้กับร้านค้าและรับชื่อผู้ใช้เริ่มต้นจากที่จัดเก็บ/ค่ากำหนดในเครื่อง การใช้งาน AngularDart ของสิ่งนี้ดูเหมือนว่า:

 // ANGULAR DART class PreferencesInterfaceImpl extends PreferencesInterface { SharedPreferences _prefs; @override Future initPreferences() async => _prefs = await SharedPreferences.getInstance(); @override void setDefaultUsername(String username) => _prefs.setString(DEFAULT_USERNAME, username); @override String get defaultUsername => _prefs.getString(DEFAULT_USERNAME); }

ไม่มีอะไรน่าตื่นเต้นที่นี่—มันนำสิ่งที่ต้องการไปใช้ คุณอาจสังเกตเห็นวิธีการ initPreferences() async ที่คืน null ต้องใช้วิธีนี้ในฝั่ง Flutter เนื่องจากการรับอินสแตนซ์ SharedPreferences บนมือถือนั้นไม่ตรงกัน

 //FLUTTER @override Future initPreferences() async => _prefs = await SharedPreferences.getInstance();

มาดู lib/src/bloc dir กันสักหน่อย มุมมองใดๆ ที่จัดการตรรกะทางธุรกิจบางอย่างควรมีองค์ประกอบ BLoC ใน dir นี้ คุณจะเห็น view BLoCs base_bloc.dart , endpoints.dart และ session.dart อันสุดท้ายมีหน้าที่ในการลงชื่อเข้าใช้และออกจากผู้ใช้และจัดหาปลายทางสำหรับอินเทอร์เฟซของที่เก็บ สาเหตุที่มีส่วนต่อประสานเซสชันคือแพ็คเกจ firebase และ firecloud นั้นไม่เหมือนกันสำหรับเว็บและมือถือและต้องใช้งานตามแพลตฟอร์ม

 // BLOC abstract class Session implements Endpoints { //Collections. @protected final String userCollectionName = "users"; @protected final String todoCollectionName = "todos"; String userId; Session(){ _isSignedIn.stream.listen((signedIn) { if(!signedIn) _logout(); }); } final BehaviorSubject<bool> _isSignedIn = BehaviorSubject<bool>(); Stream<bool> get isSignedIn => _isSignedIn.stream; Sink<bool> get signedIn => _isSignedIn.sink; Future<String> signIn(String username, String password); @protected void logout(); void _logout() { logout(); userId = null; } }

แนวคิดคือทำให้คลาสเซสชันเป็นสากล (ซิงเกิลตัน) ตามตัวรับ _isSignedIn.stream จะจัดการการสลับแอประหว่างมุมมองล็อกอิน/รายการสิ่งที่ต้องทำ และจัดเตรียมจุดปลายสำหรับการใช้งานที่เก็บหากมี userId อยู่ (กล่าวคือ ผู้ใช้ลงชื่อเข้าใช้)

base_bloc.dart เป็นฐานสำหรับ BLoC ทั้งหมด ในตัวอย่างนี้ จะจัดการตัวบ่งชี้การโหลดและการแสดงกล่องโต้ตอบข้อผิดพลาดตามความจำเป็น

สำหรับตัวอย่างตรรกะทางธุรกิจ เราจะดูที่ todo_add_edit_bloc.dart ชื่อยาวของไฟล์อธิบายวัตถุประสงค์ของไฟล์ มีวิธีโมฆะส่วนตัว _addUpdateTodo(bool addUpdate)

 // BLOC void _addUpdateTodo(bool addUpdate) { if(!addUpdate) return; //Check required. if(_title.value.isEmpty) _todoError.sink.add(0); else if(_description.value.isEmpty) _todoError.sink.add(1); else _todoError.sink.add(-1); if(_todoError.value >= 0) return; final TodoBloc todoBloc = _todo.value == null ? TodoBloc("", false, DateTime.now(), null, null, null) : _todo.value; todoBloc.title = _title.value; todoBloc.description = _description.value; showProgress.add(true); _toDoRepository.addUpdateToDo(todoBloc) .doOnDone( () => showProgress.add(false) ) .listen((_) => _closeDetail.add(true) , onError: (err) => error.add( err.toString()) ); }

อินพุตสำหรับวิธีนี้คือ bool addUpdate และเป็นผู้ฟังของ final BehaviorSubject<bool> _addUpdate = BehaviorSubject<bool>() สุดท้าย เมื่อผู้ใช้คลิกปุ่มบันทึกในแอป เหตุการณ์จะส่งค่าจริงของหัวเรื่องและทริกเกอร์ฟังก์ชัน BLoC นี้ รหัสกระพือปีกชิ้นนี้ใช้เวทย์มนตร์ในด้านมุมมอง

 // FLUTTER IconButton(icon: Icon(Icons.done), onPressed: () => _todoAddEditBloc.addUpdateSink.add(true),),

_addUpdateTodo ตรวจสอบว่าทั้งชื่อและคำอธิบายไม่ว่างเปล่า และเปลี่ยนค่าของ _todoError BehaviorSubject ตามเงื่อนไขนี้ ข้อผิดพลาด _todoError มีหน้าที่กระตุ้นการแสดงข้อผิดพลาดการดูในฟิลด์อินพุตหากไม่มีการระบุค่า หากทุกอย่างเรียบร้อยดี มันจะตรวจสอบว่าจะสร้างหรืออัปเดต TodoBloc หรือไม่ และสุดท้าย _toDoRepository จะทำการเขียนไปยัง FireCloud

ตรรกะทางธุรกิจอยู่ที่นี่ แต่โปรดทราบว่า:

  • เฉพาะสตรีมและซิงก์ที่เป็นสาธารณะใน BLoC _addUpdateTodo เป็นส่วนตัวและไม่สามารถเข้าถึงได้จากมุมมอง
  • _title.value และ _description.value ถูกเติมโดยผู้ใช้ป้อนค่าในการป้อนข้อความ การป้อนข้อความในเหตุการณ์การเปลี่ยนแปลงข้อความจะส่งค่าไปยังซิงก์ที่เกี่ยวข้อง ด้วยวิธีนี้ เรามีการเปลี่ยนแปลงค่าเชิงโต้ตอบใน BLoC และแสดงค่าเหล่านี้ในมุมมอง
  • _toDoRepository ขึ้นอยู่กับแพลตฟอร์มและให้บริการโดยการฉีด

ตรวจสอบรหัสของวิธีการ todo_list.dart BLoC _getTodos() มันฟังสแน็ปช็อตของคอลเลคชันสิ่งที่ต้องทำและสตรีมข้อมูลคอลเลกชั่นเพื่อแสดงในมุมมอง รายการมุมมองถูกวาดใหม่ตามการเปลี่ยนแปลงสตรีมคอลเลกชัน

 // BLOC void _getTodos(){ showProgress.add(true); _toDoRepository.getToDos() .listen((todosList) { todosSink.add(todosList); showProgress.add(false); }, onError: (err) { showProgress.add(false); error.add(err.toString()); }); }

สิ่งสำคัญที่ต้องระวังเมื่อใช้สตรีมหรือเทียบเท่า rx คือต้องปิดสตรีม เราทำเช่นนั้นในวิธี dispose() ของแต่ละ BLoC กำจัด BLoC ของแต่ละมุมมองด้วยวิธีการกำจัด/ทำลาย

 // FLUTTER @override void dispose() { widget.baseBloc.dispose(); super.dispose(); }

หรือในโครงการ AngularDart:

 // ANGULAR DART @override void ngOnDestroy() { todoListBloc.dispose(); }

การฉีดที่เก็บเฉพาะแพลตฟอร์ม

ไดอะแกรมความสัมพันธ์ระหว่างรูปแบบ BLoC ที่เก็บสิ่งที่ต้องทำ และอื่นๆ

เราเคยพูดไปแล้วว่าทุกอย่างที่มาใน BLoC ต้องเป็น Dart ธรรมดาและไม่มีอะไรขึ้นอยู่กับแพลตฟอร์ม TodoAddEditBloc ต้องการ ToDoRepository เพื่อเขียนไปยัง Firestore Firebase มีแพ็คเกจที่ขึ้นอยู่กับแพลตฟอร์ม และเราต้องมีการใช้งานอินเทอร์เฟซ ToDoRepository แยกต่างหาก การใช้งานเหล่านี้จะถูกแทรกลงในแอพ สำหรับ Flutter ฉันใช้แพ็คเกจ flutter_simple_dependency_injection และดูเหมือนว่านี้:

 // FLUTTER class Injection { static Firestore _firestore = Firestore.instance; static FirebaseAuth _auth = FirebaseAuth.instance; static PreferencesInterface _preferencesInterface = PreferencesInterfaceImpl(); static Injector injector; static Future initInjection() async { await _preferencesInterface.initPreferences(); injector = Injector.getInjector(); //Session injector.map<Session>((i) => SessionImpl(_auth, _firestore), isSingleton: true); //Repository injector.map<ToDoRepository>((i) => ToDoRepositoryImpl(injector.get<Session>()), isSingleton: false); //Bloc injector.map<LoginBloc>((i) => LoginBloc(_preferencesInterface, injector.get<Session>()), isSingleton: false); injector.map<TodoListBloc>((i) => TodoListBloc(injector.get<ToDoRepository>(), injector.get<Session>()), isSingleton: false); injector.map<TodoAddEditBloc>((i) => TodoAddEditBloc(injector.get<ToDoRepository>()), isSingleton: false); } }

ใช้สิ่งนี้ในวิดเจ็ตดังนี้:

 // FLUTTER TodoAddEditBloc _todoAddEditBloc = Injection.injector.get<TodoAddEditBloc>();

AngularDart มีการฉีดในตัวผ่านผู้ให้บริการ

 // ANGULAR DART @GenerateInjector([ ClassProvider(PreferencesInterface, useClass: PreferencesInterfaceImpl), ClassProvider(Session, useClass: SessionImpl), ExistingProvider(Endpoints, Session) ])

และในองค์ประกอบ:

 // ANGULAR DART providers: [ overlayBindings, ClassProvider(ToDoRepository, useClass: ToDoRepositoryImpl), ClassProvider(TodoAddEditBloc), ExistingProvider(BaseBloc, TodoAddEditBloc) ],

เราจะเห็นว่า Session เป็น Global มีฟังก์ชันการลงชื่อเข้าใช้/เอาท์และปลายทางที่ใช้ใน ToDoRepository และ BLoCs ToDoRepository ต้องการอินเทอร์เฟซปลายทางซึ่งมีการใช้งานใน SessionImpl เป็นต้น มุมมองควรเห็นเฉพาะ BLoC และไม่มีอะไรเพิ่มเติม

มุมมอง

ไดอะแกรมของอ่างล้างมือและลำธารที่มีปฏิสัมพันธ์ระหว่าง BLoC และมุมมอง

การดูควรเรียบง่ายที่สุด พวกเขาแสดงเฉพาะสิ่งที่มาจาก BLoC และส่งข้อมูลของผู้ใช้ไปยัง BLoC เราจะพูดถึงมันด้วยวิดเจ็ต TodoAddEdit จาก Flutter และ TodoDetailComponent เทียบเท่ากับเว็บ พวกเขาแสดงชื่อและคำอธิบายของสิ่งที่ต้องทำที่เลือก และผู้ใช้สามารถเพิ่มหรืออัปเดตสิ่งที่ต้องทำ

กระพือ:

 // FLUTTER _todoAddEditBloc.todoStream.first.then((todo) { _titleController.text = todo.title; _descriptionController.text = todo.description; });

และต่อมาในรหัส…

 // FLUTTER StreamBuilder<int>( stream: _todoAddEditBloc.todoErrorStream, builder: (BuildContext context, AsyncSnapshot errorSnapshot) { return TextField( onChanged: (text) => _todoAddEditBloc.titleSink.add(text), decoration: InputDecoration(hintText: Localization.of(context).title, labelText: Localization.of(context).title, errorText: errorSnapshot.data == 0 ? Localization.of(context).titleEmpty : null), controller: _titleController, ); }, ),

วิดเจ็ต StreamBuilder สร้างขึ้นใหม่เองหากมีข้อผิดพลาด (ไม่ได้แทรกอะไร) สิ่งนี้เกิดขึ้นได้โดยการฟัง _todoAddEditBloc.todoErrorStream . _todoAddEditBloc.titleSink _todoAddEditBloc.todoErrorStream . _todoAddEditBloc.titleSink ซึ่งเป็นซิงก์ใน BLoC ที่มีชื่อเรื่องและได้รับการอัปเดตเมื่อผู้ใช้ป้อนข้อความในช่องข้อความ

ค่าเริ่มต้นของฟิลด์อินพุตนี้ (หากเลือกสิ่งที่ต้องทำ) จะถูกเติมโดยการฟัง _todoAddEditBloc.todoStream ซึ่งเก็บสิ่งที่ต้องทำที่เลือกไว้หรือค่าว่างหากเราเพิ่มสิ่งที่ต้องทำใหม่

การกำหนดค่าให้กับฟิลด์ข้อความทำได้โดยคอนโทรลเลอร์ _titleController.text = todo.title; .

เมื่อผู้ใช้ตัดสินใจที่จะบันทึกสิ่งที่ต้องทำ มันจะกดไอคอนตรวจสอบในแถบแอปและทริกเกอร์ _todoAddEditBloc.addUpdateSink.add(true) ที่เรียกใช้ _addUpdateTodo(bool addUpdate) ที่เราพูดถึงในส่วน BLoC ก่อนหน้านี้ และใช้ตรรกะทางธุรกิจทั้งหมดในการเพิ่ม อัปเดต หรือแสดงข้อผิดพลาดกลับไปยังผู้ใช้

ทุกอย่างมีปฏิกิริยาและไม่จำเป็นต้องจัดการกับสถานะวิดเจ็ต

รหัส AngularDart นั้นง่ายยิ่งขึ้น หลังจากให้ส่วนประกอบ BLoC โดยใช้ผู้ให้บริการแล้ว โค้ดไฟล์ todo_detail.html จะทำหน้าที่ในการแสดงข้อมูลและส่งการโต้ตอบของผู้ใช้กลับไปยัง BLoC

 // AngularDart <material-input #title label="{{titleStr}}" ngModel="{{(todoAddEditBloc.titleStream | async) == null ? '' : (todoAddEditBloc.titleStream | async)}}" (inputKeyPress)="todoAddEditBloc.titleSink.add($event)" [error]="(todoAddEditBloc.todoErrorStream | async) == 0 ? titleErrString : ''" autoFocus floatingLabel type="text" useNativeValidation="false" autocomplete="off"> </material-input> <material-input #description label="{{descriptionStr}}" ngModel="{{(todoAddEditBloc.descriptionStream | async) == null ? '' : (todoAddEditBloc.descriptionStream | async)}}" (inputKeyPress)="todoAddEditBloc.descriptionSink.add($event)" [error]="(todoAddEditBloc.todoErrorStream | async) == 1 ? descriptionErrString : ''" autoFocus floatingLabel type="text" useNativeValidation="false" autocomplete="off"> </material-input> <material-button animated raised role="button" class="blue" (trigger)="todoAddEditBloc.addUpdateSink.add(true)"> {{saveStr}} </material-button> <base-bloc></base-bloc>

คล้ายกับ Flutter เรากำลังกำหนด ngModel= จากสตรีมชื่อ ซึ่งเป็นค่าเริ่มต้น

 // AngularDart (inputKeyPress)="todoAddEditBloc.descriptionSink.add($event)"

เหตุการณ์เอาต์พุต inputKeyPress จะส่งอักขระที่ผู้ใช้พิมพ์ในการป้อนข้อความกลับไปที่คำอธิบายของ BLoC ปุ่มวัสดุ (trigger)="todoAddEditBloc.addUpdateSink.add(true)" จะส่งเหตุการณ์ add/update ของ BLoC ซึ่งจะทริกเกอร์ _addUpdateTodo(bool addUpdate) เดียวกันใน BLoC อีกครั้ง หากคุณดูที่โค้ด todo_detail.dart ของคอมโพเนนต์ คุณจะเห็นว่าแทบไม่มีอะไรเลยนอกจากสตริงที่แสดงบนมุมมอง ฉันวางไว้ที่นั่นและไม่ใช่ใน HTML เนื่องจากการแปลเป็นภาษาท้องถิ่นที่เป็นไปได้ซึ่งสามารถทำได้ที่นี่

ส่วนประกอบอื่นๆ เหมือนกันหมด—ส่วนประกอบและวิดเจ็ตไม่มีตรรกะทางธุรกิจ

อีกหนึ่งสถานการณ์ที่ควรค่าแก่การกล่าวถึง ลองนึกภาพว่าคุณมีมุมมองที่มีตรรกะการนำเสนอข้อมูลที่ซับซ้อน หรือบางอย่าง เช่น ตารางที่มีค่าที่ต้องจัดรูปแบบ (วันที่ สกุลเงิน ฯลฯ) อาจมีคนล่อลวงให้รับค่าจาก BLoC และจัดรูปแบบในมุมมอง ผิดแล้ว! ค่าที่แสดงในมุมมองควรมาที่มุมมองที่จัดรูปแบบแล้ว (สตริง) เหตุผลก็คือการจัดรูปแบบเองก็เป็นตรรกะทางธุรกิจด้วย อีกตัวอย่างหนึ่งคือเมื่อการจัดรูปแบบของค่าที่แสดงขึ้นอยู่กับพารามิเตอร์ของแอพบางตัวที่สามารถเปลี่ยนแปลงได้ในรันไทม์ โดยการให้พารามิเตอร์นั้นกับ BLoC และใช้วิธีตอบโต้เพื่อดูการแสดงผล ตรรกะทางธุรกิจจะจัดรูปแบบค่าและวาดใหม่เฉพาะส่วนที่จำเป็นเท่านั้น โมเดล BLoC ที่เรามีในตัวอย่างนี้ TodoBloc นั้นง่ายมาก การแปลงจากโมเดล FireCloud เป็นโมเดล BLoC ทำได้ในที่เก็บ แต่ถ้าจำเป็น ก็สามารถทำได้ใน BLoC เพื่อให้ค่าโมเดลพร้อมสำหรับการแสดงผล

ห่อ

บทความสั้น ๆ นี้ครอบคลุมแนวคิดหลักของการนำรูปแบบ BLoC ไปใช้ เป็นการพิสูจน์ที่ใช้งานได้ว่าการแชร์โค้ดระหว่าง Flutter และ AngularDart นั้นเป็นไปได้ ทำให้สามารถพัฒนาแบบเนทีฟและข้ามแพลตฟอร์มได้

จากการสำรวจตัวอย่าง คุณจะเห็นว่าเมื่อใช้งานอย่างถูกต้อง BLoC จะย่นเวลาในการสร้างแอพมือถือ/เว็บลงอย่างมาก ตัวอย่างคือ ToDoRepository และการนำไปใช้งาน รหัสการใช้งานเกือบจะเหมือนกันและแม้แต่ตรรกะในการเขียนมุมมองก็คล้ายกัน หลังจากใช้วิดเจ็ต/ส่วนประกอบสองสามรายการ คุณสามารถเริ่มการผลิตจำนวนมากได้อย่างรวดเร็ว

ฉันหวังว่าบทความนี้จะช่วยให้คุณเข้าใจถึงความสนุกและความกระตือรือร้นที่ฉันได้ทำกับเว็บ/แอปมือถือโดยใช้ Flutter/AngularDart และรูปแบบ BLoC หากคุณต้องการสร้างแอปพลิเคชันเดสก์ท็อปข้ามแพลตฟอร์มใน JavaScript โปรดอ่าน Electron: แอปเดสก์ท็อปข้ามแพลตฟอร์มที่ทำได้ง่าย โดยเพื่อน Toptaler Stephane P. Pericat

ที่เกี่ยวข้อง: The Dart Language: เมื่อ Java และ C # ไม่คมชัดเพียงพอ