สถาปัตยกรรม Flutter: Provider vs BLoC
เผยแพร่แล้ว: 2020-04-17การเขียนแอพด้วย Flutter สร้างโอกาสที่ดีในการเลือกสถาปัตยกรรม ตามปกติแล้ว คำตอบที่ดีที่สุดสำหรับคำถามที่ว่า "ฉันควรเลือกข้อใด" คือ “ขึ้นอยู่กับ” เมื่อคุณได้คำตอบนั้น คุณจะมั่นใจได้ว่าคุณจะพบผู้เชี่ยวชาญในการเขียนโปรแกรม
ในบทความนี้ เราจะพูดถึงหน้าจอที่ได้รับความนิยมมากที่สุดในแอปพลิเคชั่นมือถือและนำไปใช้ในสถาปัตยกรรม Flutter ยอดนิยมสองแบบ: Provider และ BLoC ด้วยเหตุนี้ เราจะได้เรียนรู้ข้อดีและข้อเสียของแต่ละโซลูชัน ซึ่งจะช่วยให้เราเลือกสถาปัตยกรรม Flutter ที่เหมาะสมสำหรับโมดูลหรือแอปพลิเคชันถัดไป
แนะนำสั้น ๆ เกี่ยวกับสถาปัตยกรรม Flutter
การเลือกสถาปัตยกรรมสำหรับโครงการพัฒนา Flutter มีความสำคัญอย่างยิ่ง เนื่องจากเรากำลังเผชิญกับกระบวนทัศน์การเขียนโปรแกรมที่ไม่เปิดเผยซึ่งใช้กันทั่วไปน้อยกว่า สิ่งนี้เปลี่ยนแนวทางในการจัดการ sate ที่นักพัฒนา Android หรือ iOS คุ้นเคยโดยสมบูรณ์ โดยการเขียนโค้ดมีความจำเป็น ข้อมูลที่มีอยู่ในที่เดียวในแอปพลิเคชันนั้นหาได้ไม่ยากในที่อื่น เราไม่มีการอ้างอิงโดยตรงไปยังมุมมองอื่นๆ ในแผนผัง ซึ่งเราสามารถได้รับสถานะปัจจุบัน
ผู้ให้บริการใน Flutter คืออะไร
ตามชื่อที่แนะนำ Provider คือสถาปัตยกรรม Flutter ที่ให้โมเดลข้อมูลปัจจุบันไปยังที่ที่เราต้องการในปัจจุบัน ประกอบด้วยข้อมูลบางส่วนและแจ้งให้ผู้สังเกตการณ์ทราบเมื่อมีการเปลี่ยนแปลง ใน Flutter SDK ประเภทนี้เรียกว่า ChangeNotifier เพื่อให้วัตถุประเภท ChangeNotifier พร้อมใช้งานสำหรับวิดเจ็ตอื่น เราจำเป็นต้องมี ChangeNotifierProvider มันจัดเตรียมวัตถุที่สังเกตได้สำหรับลูกหลานทั้งหมด ออบเจ็กต์ที่สามารถรับข้อมูลปัจจุบันได้คือ Consumer ซึ่งมีอินสแตนซ์ ChangeNotifier ในพารามิเตอร์ของฟังก์ชัน บิล ด์ที่สามารถใช้เพื่อป้อนมุมมองที่ตามมาด้วยข้อมูล
BLoC ใน Flutter คืออะไร
Business Logic Components เป็นสถาปัตยกรรม Flutter ที่คล้ายกับโซลูชันยอดนิยมในมือถือ เช่น MVP หรือ MVVM มีการแยกชั้นการนำเสนอออกจากกฎตรรกะทางธุรกิจ นี่เป็นการประยุกต์ใช้แนวทางการประกาศโดยตรงซึ่ง Flutter เน้นย้ำอย่างยิ่ง เช่น UI = f (state) BLoC เป็นสถานที่ซึ่งมีกิจกรรมจากอินเทอร์เฟซผู้ใช้ ภายในเลเยอร์นี้ อันเป็นผลมาจากการใช้กฎทางธุรกิจกับเหตุการณ์ที่กำหนด BLoC จะตอบสนองด้วยสถานะเฉพาะ ซึ่งจะกลับไปที่ UI เมื่อเลเยอร์การดูได้รับสถานะใหม่ มันจะสร้างมุมมองใหม่ตามสิ่งที่สถานะปัจจุบันต้องการ
อยากรู้เกี่ยวกับการพัฒนา Flutter หรือไม่?
ดูโซลูชันของเราวิธีสร้างรายการใน Flutter
รายการแบบเลื่อนได้น่าจะเป็นหนึ่งในมุมมองที่ได้รับความนิยมมากที่สุดในแอปพลิเคชันมือถือ ดังนั้น การเลือกสถาปัตยกรรม Flutter ที่เหมาะสมอาจมีความสำคัญที่นี่ ในทางทฤษฎี การแสดงรายการนั้นไม่ใช่เรื่องยาก สถานการณ์จะซับซ้อนขึ้น ตัวอย่างเช่น เมื่อเราเพิ่มความสามารถในการดำเนินการบางอย่างกับแต่ละองค์ประกอบ ที่ควรทำให้เกิดการเปลี่ยนแปลงในตำแหน่งต่าง ๆ ในแอป ในรายการของเรา เราจะสามารถเลือกองค์ประกอบแต่ละอย่างได้ และองค์ประกอบที่เลือกแต่ละรายการจะแสดงในรายการแยกต่างหากบนหน้าจอที่แตกต่างกัน

ดังนั้นเราต้องเก็บองค์ประกอบที่เลือกไว้เพื่อให้สามารถแสดงบนหน้าจอใหม่ได้ นอกจากนี้ เราจะต้องสร้างมุมมองใหม่ทุกครั้งที่มีการแตะช่องทำเครื่องหมาย เพื่อแสดงการทำเครื่องหมาย / ยกเลิกการเลือก
โมเดลรายการดูง่ายมาก:
คลาส SocialMedia { รหัสภายใน; ชื่อสตริง; ไอคอนสตริงสินทรัพย์; บูลคือรายการโปรด; สื่อสังคม( {@required this.id, @required this.title, @ ต้องการ this.iconAsset this.isFavourite = false}); เป็นโมฆะ setFavourite (บูล isFavourite) { this.isFavourite = เป็นรายการโปรด; } }
วิธีสร้างรายการด้วย Provider
ในรูปแบบผู้ให้บริการ โมเดลข้างต้นต้องเก็บไว้ในออบเจ็กต์ วัตถุควรขยาย ChangeNotifier เพื่อให้สามารถเข้าถึง SocialMedia จากที่อื่นในแอปได้
คลาส SocialMediaModel ขยาย ChangeNotifier { รายการสุดท้าย<SocialMedia> _socialMedia = [ /* บางรายการโซเชียลมีเดีย */ ]; UnmodifiableListView<SocialMedia> รับรายการโปรด { ส่งคืน UnmodifiableListView(_socialMedia.where((item) => item.isFavourite)); } UnmodifiableListView<SocialMedia> รับทั้งหมด { ส่งคืน UnmodifiableListView(_socialMedia); } เป็นโมฆะ setFavourite (int itemId, bool isChecked) { _สื่อสังคม .firstWhere((item) => item.id == itemId) .setFavourite(ตรวจสอบแล้ว); notifyListeners(); }
การเปลี่ยนแปลงใดๆ ในอ็อบเจ็กต์นี้ ซึ่งจะต้องมีการสร้างใหม่ในมุมมอง จะต้องส่งสัญญาณโดยใช้ notifyListeners() ในกรณีของ เมธอด setFavourite() เพื่อสั่งให้ Flutter แสดงแฟรกเมนต์ UI อีกครั้ง ซึ่งจะสังเกตการเปลี่ยนแปลงในอ็อบเจ็กต์นี้
ตอนนี้เราสามารถดำเนินการสร้างรายการได้แล้ว ในการเติม ListView ด้วยองค์ประกอบ เราจะต้องไปที่อ็อบเจ็กต์ SocialMediaModel ซึ่งเก็บรายการขององค์ประกอบทั้งหมด คุณสามารถทำได้สองวิธี:
- Provider.of<ModelType>(บริบท ฟัง: เท็จ)
- ผู้บริโภค
อันแรกจัดเตรียมอ็อบเจ็กต์ที่สังเกตได้และช่วยให้เราตัดสินใจว่าการดำเนินการที่ทำกับอ็อบเจ็กต์ควรสร้างวิดเจ็ตปัจจุบันใหม่โดยใช้พารามิเตอร์ listen ลักษณะการทำงานนี้จะเป็นประโยชน์ในกรณีของเรา
คลาส SocialMediaListScreen ขยาย StatelessWidget { SocialMediaListScreen(); @แทนที่ วิดเจ็ตบิวด์ (บริบท BuildContext) { var socialMedia = Provider.of<SocialMediaModel>(บริบท ฟัง: เท็จ); ส่งคืน ListView( เด็ก ๆ: socialMedia.all .map((item) => CheckboxSocialMediaItem(item: item)) .toList(), ); } }
เราต้องการรายชื่อโซเชียลมีเดียทั้งหมด แต่ไม่จำเป็นต้องสร้างรายการใหม่ทั้งหมด มาดูกันว่าวิดเจ็ตรายการมีลักษณะอย่างไร
คลาส CheckboxSocialMediaItem ขยาย StatelessWidget { รายการ SocialMedia สุดท้าย ช่องทำเครื่องหมายSocialMediaItem({คีย์คีย์ @required this.item}) : super(คีย์: คีย์); @แทนที่ วิดเจ็ตบิวด์ (บริบท BuildContext) { กลับแพ็ดดิ้ง( ช่องว่างภายใน: const EdgeInsets.all(Dimens.paddingDefault), เด็ก: แถว ( เด็ก: [ ผู้บริโภค<SocialMediaModel>( ผู้สร้าง: (บริบท โมเดล ลูก) { ส่งคืนช่องทำเครื่องหมาย ( ค่า: item.isFavourite, onChanged: (ถูกตรวจสอบแล้ว) => model.setFavourite(item.id, isChecked), ); }, ), รายการโซเชียลมีเดีย ( รายการ: รายการ, ) ], ), ); } }
เรารับฟังการเปลี่ยนแปลงของค่าช่องทำเครื่องหมายและอัปเดตโมเดลตามสถานะการตรวจสอบ ค่าช่องกาเครื่องหมายถูกตั้งค่าโดยใช้คุณสมบัติจากตัวแบบข้อมูล ซึ่งหมายความว่าหลังจากเลือกแล้ว โมเดลจะเปลี่ยนฟิลด์ isFavourite เป็น true อย่างไรก็ตาม มุมมองจะไม่แสดงการเปลี่ยนแปลงนี้จนกว่าเราจะสร้างช่องทำเครื่องหมายใหม่ ที่นี่วัตถุ Consumer มาพร้อมกับความช่วยเหลือ มันให้วัตถุที่สังเกตได้และสร้างลูกหลานของเขาใหม่ทั้งหมดหลังจากได้รับข้อมูลเกี่ยวกับการเปลี่ยนแปลงในแบบจำลอง
ควรวาง Consumer เฉพาะในกรณีที่จำเป็นต้องอัปเดตวิดเจ็ตเพื่อหลีกเลี่ยงการสร้างมุมมองใหม่โดยไม่จำเป็น โปรดทราบว่า ตัวอย่างเช่น หากการเลือกช่องทำเครื่องหมายจะทริกเกอร์การดำเนินการเพิ่มเติมบางอย่าง เช่น การเปลี่ยนชื่อรายการ ผู้บริโภค จะต้องย้ายไปให้สูงขึ้นในแผนผังวิดเจ็ต เพื่อที่จะกลายเป็นพาเรนต์ของวิดเจ็ตที่รับผิดชอบในการแสดงชื่อ . มิฉะนั้น มุมมองชื่อเรื่องจะไม่ได้รับการอัปเดต
การสร้างหน้าจอโซเชียลมีเดียที่ชื่นชอบจะมีลักษณะคล้ายกัน เราจะได้รายการของรายการโปรดโดยใช้ Provider
คลาส FavoritesListScreen ขยาย StatelessWidget { FavoritesListScreen(); @แทนที่ วิดเจ็ตบิวด์ (บริบท BuildContext) { var list = Provider.of<SocialMediaModel>(บริบท ฟัง: false).favourites; ส่งคืน ListView( เด็ก ๆ : รายการ .map((รายการ) => Padding( ช่องว่างภายใน: const EdgeInsets.all(Dimens.paddingDefault), ลูก: SocialMediaItem(รายการ: รายการ))) .toList(), ); } }
เมื่อ เรียก วิธีการบิลด์ ผู้ให้บริการ จะส่งคืนรายการโซเชียลมีเดียที่ชื่นชอบในปัจจุบัน
วิธีสร้างรายการด้วย BLoC
ในแอปพลิเคชันที่เรียบง่ายของเรา เรามีสองหน้าจอจนถึงตอนนี้ แต่ละคนจะมีวัตถุ BLoC ของตัวเอง อย่างไรก็ตาม พึงระลึกไว้เสมอว่ารายการที่เลือกบนหน้าจอหลักจะต้องปรากฏในรายการโซเชียลมีเดียที่ชื่นชอบ ดังนั้นเราจึงต้องถ่ายโอนเหตุการณ์การเลือกช่องทำเครื่องหมายนอกหน้าจอ วิธีแก้ไขคือสร้างวัตถุ BLoC เพิ่มเติมที่จะจัดการกับเหตุการณ์ที่ส่งผลต่อสถานะของหน้าจอต่างๆ เรียกมันว่า Global BLoC จากนั้น ออบเจ็กต์ BLoC ที่กำหนดให้กับแต่ละหน้าจอจะรับฟังการเปลี่ยนแปลงในสถานะ BLoC ทั่วโลกและตอบสนองตามนั้น
ก่อนที่คุณจะสร้างวัตถุ BLoC คุณควรคิดก่อนว่าเหตุการณ์ใดที่มุมมองจะสามารถส่งไปยังเลเยอร์ BLoC และสถานะที่จะตอบสนองได้ ในกรณีของ global BLoC เหตุการณ์และสถานะจะเป็นดังนี้:
คลาสนามธรรม SocialMediaEvent {} คลาส CheckboxChecked ขยาย SocialMediaEvent { บูลสุดท้าย isChecked; รหัสรายการ int สุดท้าย; ทำเครื่องหมายที่ช่องทำเครื่องหมาย (this.isChecked, this.itemId); } คลาสนามธรรม SocialMediaState {} คลาส ListPresented ขยาย SocialMediaState { รายการสุดท้าย <SocialMedia> รายการ; ListPresented(this.list); }
เหตุการณ์ CheckboxChecked ต้องอยู่ใน Global BLoC เนื่องจากจะส่งผลต่อสถานะของหลายหน้าจอ ไม่ใช่แค่เพียงหน้าจอเดียว เมื่อพูดถึงรัฐ เรามีหนึ่งรายการที่พร้อมจะแสดง จากมุมมองของ BLoC ทั่วโลก ไม่จำเป็นต้องสร้างสถานะเพิ่มเติม ทั้งสองหน้าจอควรแสดงรายการและ BLoC แต่ละรายการที่ทุ่มเทให้กับหน้าจอเฉพาะควรดูแลมัน การนำ BLoC ไปใช้ทั่วโลกจะมีลักษณะดังนี้:
คลาส SocialMediaBloc ขยาย Bloc<SocialMediaEvent, SocialMediaState> { ที่เก็บ SimpleSocialMediaRepository สุดท้าย; SocialMediaBloc (this.repository); @แทนที่ SocialMediaState รับ initialState => ListPresented (repository.getSocialMedia); @แทนที่ สตรีม <SocialMediaState> mapEventToState (เหตุการณ์ SocialMediaEvent) async* { ถ้า (เหตุการณ์ถูกเลือกช่องทำเครื่องหมาย) { ผลตอบแทน _mapCheckboxCheckedToState (เหตุการณ์); } } SocialMediaState _mapCheckboxCheckedToState (ช่องทำเครื่องหมายเหตุการณ์ที่ตรวจสอบ) { final updatedList = (ระบุเป็น ListPresented).list; อัพเดทรายการ .firstWhere((item) => item.id == event.itemId) .setFavourite(event.isChecked); ส่งคืน ListPresented (updatedList); } }
สถานะเริ่มต้นคือ ListPresented – เราคิดว่าเราได้รับข้อมูลจากที่เก็บแล้ว เราจำเป็นต้องตอบสนองต่อเหตุการณ์เดียวเท่านั้น – CheckboxChecked ดังนั้นเราจะอัปเดตองค์ประกอบที่เลือกโดยใช้เมธอด setFavourite และส่งรายการใหม่ที่อยู่ในสถานะ ListPresented
ตอนนี้ เราต้องส่งเหตุการณ์ CheckboxChecked เมื่อทำการบันทึกที่ช่องทำเครื่องหมาย ในการทำเช่นนี้ เราจำเป็นต้องมี SocialMediaBloc ในตำแหน่งที่เราสามารถแนบการโทรกลับ onChanged ได้ เราสามารถรับอินสแตนซ์นี้ได้โดยใช้ BlocProvider ซึ่งดูคล้ายกับ ผู้ให้บริการ จากรูปแบบที่กล่าวถึงข้างต้น เพื่อให้ BlocProvider ทำงานได้ สูงกว่าในแผนผังวิดเจ็ต คุณต้องเริ่มต้นวัตถุ BLoC ที่ต้องการ ในตัวอย่างของเราจะทำในวิธีการหลัก:
โมฆะ main() => runApp(BlocProvider( สร้าง: (บริบท) { ส่งคืน SocialMediaBloc(SimpleSocialMediaRepository()); }, ลูก: ArchitecturesSampleApp()));
ด้วยเหตุนี้ในรหัสรายการหลัก เราสามารถเรียก BLoC ได้อย่างง่ายดายโดยใช้ BlocProvider.of () และส่งเหตุการณ์ไปที่มันโดยใช้วิธีการ เพิ่ม :
คลาส SocialMediaListScreen ขยาย StatefulWidget { _SocialMediaListState createState() => _SocialMediaListState(); } คลาส _SocialMediaListState ขยายสถานะ <SocialMediaListScreen> { @แทนที่ วิดเจ็ตบิวด์ (บริบท BuildContext) { ส่งคืน BlocBuilder<SocialMediaListBloc, SocialMediaListState>( ผู้สร้าง: (บริบท สถานะ) { ถ้า (สถานะเป็น MainListLoaded) { ส่งคืน ListView( ลูกๆ: state.socialMedia .map((รายการ) => ช่องทำเครื่องหมายSocialMediaItem( รายการ: รายการ, onCheckboxChanged: (isChecked) => BlocProvider.of<SocialMediaBloc>(บริบท) .add(กาเครื่องหมายถูกตรวจสอบ(isChecked, item.id)), )) .toList(), ); } อื่น { return Center(ลูก: Text(Strings.emptyList)); } }, ); } }
เรามีการเผยแพร่เหตุการณ์ CheckboxChecked ไปยัง BLoC แล้ว เรายังทราบด้วยว่า BLoC จะตอบสนองต่อเหตุการณ์ดังกล่าวอย่างไร แต่จริงๆ แล้ว… อะไรจะทำให้รายการสร้างใหม่ด้วยช่องทำเครื่องหมายที่เลือกไว้แล้ว Global BLoC ไม่สนับสนุนการเปลี่ยนแปลงสถานะของรายการ เนื่องจากได้รับการจัดการโดย อ็อบเจ็กต์ BLoC แต่ละรายการที่กำหนดให้กับหน้าจอ วิธีแก้ปัญหาคือการฟัง BLoC ทั่วโลกที่กล่าวถึงก่อนหน้านี้เพื่อเปลี่ยนสถานะและตอบสนองตามสถานะนี้ ด้านล่าง BLoC ทุ่มเทให้กับรายการโซเชียลมีเดียหลักพร้อมช่องทำเครื่องหมาย:

คลาส SocialMediaListBloc ขยาย Bloc<SocialMediaListEvent, SocialMediaListState> { สุดท้าย SocialMediaBloc mainBloc; SocialMediaListBloc({@required this.mainBloc}) { mainBloc.listen ((สถานะ) { ถ้า (สถานะเป็น ListPresented) { เพิ่ม (ScreenStart(state.list)); } }); } @แทนที่ SocialMediaListState รับ initialState => MainListEmpty(); @แทนที่ สตรีม<SocialMediaListState> mapEventToState( เหตุการณ์ SocialMediaListEvent) async* { สวิตช์ (event.runtimeType) { กรณี ScreenStart: ผลตอบแทน MainListLoaded ((เหตุการณ์เป็น ScreenStart).list); หยุดพัก; } } }
เมื่อ SocialMediaBloc ส่งคืนสถานะ ListPresented SocialMediaListBloc จะได้รับการแจ้งเตือน โปรดทราบว่า ListPresented แสดง ถึงรายการ เป็นรายการที่มีข้อมูลที่อัปเดตเกี่ยวกับการตรวจสอบรายการด้วยช่องทำเครื่องหมาย
ในทำนองเดียวกัน เราสามารถสร้าง BLoC ที่ทุ่มเทให้กับหน้าจอโซเชียลมีเดียที่ชื่นชอบได้:
คลาส FavouritesListBloc ขยาย Bloc <FavouritesListEvent, FavouritesListSate> { สุดท้าย SocialMediaBloc mainBloc; FavouritesListBloc({@required this.mainBloc}) { mainBloc.listen ((สถานะ) { ถ้า (สถานะเป็น ListPresented) { เพิ่ม(FavoritesScreenStart(state.list)); } }); } @แทนที่ FavouritesListSate รับ initialState => FavouritesListEmpty(); @แทนที่ สตรีม <FavouritesListSate> mapEventToState (เหตุการณ์ FavoritesListEvent) async* { ถ้า (เหตุการณ์คือ FavouritesScreenStart) { var favouritesList = event.list.where((รายการ) => item.isFavourite).toList(); ให้ผลผลิต FavouritesListLoaded (รายการรายการโปรด); } } }
การเปลี่ยนสถานะใน Global BLoC ส่งผลให้เหตุการณ์ FavouritesScreenStart เริ่มทำงานด้วยรายการปัจจุบัน จากนั้น รายการที่หนึ่งทำเครื่องหมายว่าเป็นรายการโปรดจะถูกกรอง และรายการดังกล่าวจะปรากฏขึ้นบนหน้าจอ
วิธีสร้างฟอร์มที่มีหลายฟิลด์ใน Flutter
แบบฟอร์มยาวอาจเป็นเรื่องยุ่งยาก โดยเฉพาะอย่างยิ่งเมื่อข้อกำหนดมีรูปแบบการตรวจสอบที่แตกต่างกัน หรือการเปลี่ยนแปลงบางอย่างบนหน้าจอหลังจากป้อนข้อความ ในหน้าจอตัวอย่าง เรามีแบบฟอร์มที่ประกอบด้วยหลายฟิลด์และปุ่ม "ถัดไป" ฟิลด์จะถูกตรวจสอบโดยอัตโนมัติและปุ่มจะถูกปิดใช้งานจนกว่าแบบฟอร์มจะมีผลสมบูรณ์ หลังจากคลิกปุ่ม หน้าจอใหม่จะเปิดขึ้นพร้อมกับข้อมูลที่ป้อนในแบบฟอร์ม
เราต้องตรวจสอบแต่ละฟิลด์และตรวจสอบการแก้ไขแบบฟอร์มทั้งหมดเพื่อตั้งค่าสถานะปุ่มอย่างเหมาะสม จากนั้นจะต้องจัดเก็บข้อมูลที่รวบรวมไว้สำหรับหน้าจอถัดไป

วิธีสร้างฟอร์มที่มีหลายช่องด้วย Provider
ในแอปพลิเคชันของเรา เราจำเป็นต้องมี ChangeNotifier ตัวที่สอง ซึ่งมีไว้สำหรับหน้าจอข้อมูลส่วนบุคคลโดยเฉพาะ ดังนั้นเราจึงสามารถใช้ MultiProvider ซึ่งเราจัดเตรียมรายการออบเจ็กต์ ChangeNotifier จะพร้อมใช้งานสำหรับผู้สืบทอดทั้งหมดของ MultiProvider
คลาส ArchitecturesSampleApp ขยาย StatelessWidget { ที่เก็บ SimpleSocialMediaRepository สุดท้าย; ArchitecturesSampleApp({คีย์คีย์, this.repository}) : super(คีย์: คีย์); @แทนที่ วิดเจ็ตบิวด์ (บริบท BuildContext) { ส่งคืน MultiProvider ( ผู้ให้บริการ: [ ChangeNotifierProvider<SocialMediaModel>( สร้าง: (บริบท) => SocialMediaModel(ที่เก็บ), ), ChangeNotifierProvider<PersonalDataNotifier>( สร้าง: (บริบท) => PersonalDataNotifier(), ) ], ลูก: MaterialApp( ชื่อเรื่อง: Strings.architecturesSampleApp, debugShowCheckedModeBanner: เท็จ หน้าแรก: StartScreen(), เส้นทาง: <String, WidgetBuilder>{ Routes.socialMedia: (บริบท) => SocialMediaScreen(), Routes.favourites: (บริบท) => FavoritesScreen(), Routes.personalDataForm: (บริบท) => PersonalDataScreen(), Routes.personalDataInfo: (บริบท) => PersonalDataInfoScreen() }, ), ); } }
ในกรณีนี้ PersonalDataNotifier จะทำหน้าที่เป็นชั้นตรรกะทางธุรกิจ – เขาจะตรวจสอบความถูกต้องของฟิลด์ มีสิทธิ์เข้าถึงโมเดลข้อมูลสำหรับการอัปเดต และอัปเดตฟิลด์ที่มุมมองจะขึ้นอยู่กับ
ตัวแบบฟอร์มเองเป็น API ที่ดีมากจาก Flutter ซึ่งเราสามารถแนบการตรวจสอบโดยอัตโนมัติโดยใช้ตัว ตรวจสอบ คุณสมบัติและบันทึกข้อมูลจากแบบฟอร์มไปยังโมเดลโดยใช้การเรียกกลับ onSaved เราจะมอบหมายกฎการตรวจสอบความถูกต้องให้กับ PersonalDataNotifier และเมื่อแบบฟอร์มถูกต้อง เราจะส่งข้อมูลที่ป้อนไปให้
สิ่งที่สำคัญที่สุดในหน้าจอนี้คือการรับฟังการเปลี่ยนแปลงในแต่ละฟิลด์และการเปิดหรือปิดปุ่ม ทั้งนี้ขึ้นอยู่กับผลการตรวจสอบ เราจะใช้การเรียกกลับ onChange จากวัตถุ แบบฟอร์ม ในนั้น เราจะตรวจสอบสถานะการตรวจสอบก่อนแล้วจึงส่งต่อไปยัง PersonalDataNotifier
รูปร่าง( คีย์: _formKey, ตรวจสอบอัตโนมัติ: จริง, onChanged: () => _onFormChanged(personalDataNotifier), เด็ก: เป็นโมฆะ _onFormChanged (PersonalDataNotifier personalDataNotifier) { วาร์ isValid = _formKey.currentState.validate(); PersonalDataNotifier.onFormChanged(isValid); }
ใน PersonalDataNotifier เราจะเตรียมตัวแปร isFormValid เราจะแก้ไขมัน (อย่าลืมเรียก notifyListeners() ) และในมุมมอง เราจะเปลี่ยนสถานะของปุ่มตามค่าของมัน อย่าลืมรับอินสแตนซ์ Notifier พร้อมพารามิเตอร์ ฟัง: จริง – มิฉะนั้น มุมมองของเราจะไม่สร้างใหม่และสถานะของปุ่มจะไม่เปลี่ยนแปลง
var personalDataNotifier = Provider.of<PersonalDataNotifier>(บริบท ฟัง: จริง);
อันที่จริง เนื่องจากเราใช้ personalDataNotifier ในที่อื่น โดยที่ไม่จำเป็นต้องโหลดมุมมองใหม่ บรรทัดด้านบนไม่เหมาะสมและควรตั้งค่าพารามิเตอร์ listen เป็น false สิ่งเดียวที่เราต้องการโหลดซ้ำคือปุ่ม ดังนั้นเราจึงสามารถรวมไว้ใน Consumer แบบคลาสสิกได้:
ผู้บริโภค<PersonalDataNotifier>( ผู้สร้าง: (บริบท, ผู้แจ้ง, ลูก) { ส่งคืนปุ่มยก ( ลูก: ข้อความ(Strings.addressNext), onPressed: notifier.isFormValid ? /* การดำเนินการเมื่อเปิดใช้งานปุ่ม */ : โมฆะ, สี: Colors.blue, ปิดการใช้งานสี: Colors.grey, ); }, )
ด้วยเหตุนี้ เราจึงไม่บังคับให้ส่วนประกอบอื่นๆ โหลดซ้ำทุกครั้งที่ใช้ตัวแจ้งเตือน
ในมุมมองที่แสดงข้อมูลส่วนบุคคล จะไม่มีปัญหาอีกต่อไป – เรามีสิทธิ์เข้าถึง PersonalDataNotifier และจากที่นั่น เราสามารถดาวน์โหลดแบบจำลองที่อัปเดตได้
วิธีสร้างฟอร์มที่มีหลายช่องด้วย BLoC
สำหรับหน้าจอก่อนหน้านี้ เราต้องการวัตถุ BLoC สองรายการ ดังนั้นเมื่อเราเพิ่ม "หน้าจอคู่" อีกอัน เราจะมีทั้งหมดสี่แบบ ในกรณีของ Provider เราสามารถจัดการกับ MultiBlocProvider ซึ่งทำงานได้เกือบเหมือนกัน
เป็นโมฆะ main() => runApp( MultiBlocProvider (ผู้ให้บริการ: [ ผู้ให้บริการบล็อก ( สร้าง: (บริบท) => SocialMediaBloc(SimpleSocialMediaRepository()), ), ผู้ให้บริการบล็อก ( สร้าง: (บริบท) => SocialMediaListBloc( mainBloc: BlocProvider.of<SocialMediaBloc>(บริบท))), ผู้ให้บริการบล็อก ( สร้าง: (บริบท) => PersonalDataBloc (), ), ผู้ให้บริการบล็อก ( สร้าง: (บริบท) => PersonalDataInfoBloc( mainBloc: BlocProvider.of<PersonalDataBloc>(บริบท)), ) ], ลูก: ArchitecturesSampleApp()), );
เช่นเดียวกับรูปแบบ BLoC เป็นการดีที่สุดที่จะเริ่มต้นด้วยสถานะและการดำเนินการที่เป็นไปได้
คลาสนามธรรม PersonalDataState {} คลาส NextButtonDisabled ขยาย PersonalDataState {} คลาส NextButtonEnabled ขยาย PersonalDataState {} คลาส InputFormCorrect ขยาย PersonalDataState { แบบจำลองข้อมูลส่วนบุคคลขั้นสุดท้าย InputFormCorrect(this.model); }
สิ่งที่เปลี่ยนแปลงบนหน้าจอนี้คือสถานะของปุ่ม ดังนั้นเราจึงต้องแยกรัฐสำหรับมัน นอกจากนี้ สถานะ InputFormCorrect จะทำให้เราสามารถส่งข้อมูลที่แบบฟอร์มได้รวบรวมไว้
PersonalDataEvent คลาสนามธรรม {} คลาส FormInputChanged ขยาย PersonalDataEvent { บูลสุดท้าย isValid; FormInputChanged(this.isValid); } คลาส FormCorrect ขยาย PersonalDataEvent { แบบฟอร์มข้อมูลส่วนบุคคลขั้นสุดท้ายข้อมูล; FormCorrect(this.formData); }
การรับฟังการเปลี่ยนแปลงในแบบฟอร์มเป็นสิ่งสำคัญ ดังนั้นเหตุการณ์ FormInputChanged เมื่อแบบฟอร์มถูกต้อง เหตุการณ์ FormCorrect จะถูกส่ง
เมื่อพูดถึงการตรวจสอบ จะมีความแตกต่างอย่างมากหากคุณเปรียบเทียบกับผู้ให้บริการ หากเราต้องการใส่ตรรกะการตรวจสอบความถูกต้องทั้งหมดในเลเยอร์ BLoC เราจะมีเหตุการณ์มากมายสำหรับแต่ละฟิลด์ นอกจากนี้ หลายรัฐจะต้องมีมุมมองเพื่อแสดงข้อความตรวจสอบความถูกต้อง
แน่นอนว่าสิ่งนี้เป็นไปได้ แต่มันจะเหมือนกับการต่อสู้กับ TextFormField API แทนที่จะใช้ประโยชน์จากมัน ดังนั้น หากไม่มีเหตุผลที่ชัดเจน คุณสามารถปล่อยให้การตรวจสอบความถูกต้องในเลเยอร์การดู
สถานะของปุ่มจะขึ้นอยู่กับสถานะที่ส่งไปยังมุมมองโดย BLoC :
BlocBuilder<PersonalDataBloc, PersonalDataState>( ผู้สร้าง: (บริบท สถานะ) { ส่งคืนปุ่มยก ( ลูก: ข้อความ(Strings.addressNext), onPressed: สถานะเป็น NextButtonEnabled ? /* การดำเนินการเมื่อเปิดใช้งานปุ่ม */ : โมฆะ, สี: Colors.blue, ปิดการใช้งานสี: Colors.grey, ); })
การจัดการเหตุการณ์และการทำแผนที่ไปยังรัฐใน PersonalDataBloc จะเป็นดังนี้:
@แทนที่ สตรีม <PersonalDataState> mapEventToState (เหตุการณ์ PersonalDataEvent) async * { ถ้า (เหตุการณ์คือ FormCorrect) { ให้ผลผลิต InputFormCorrect(event.formData); } else if (เหตุการณ์คือ FormInputChanged) { ผลตอบแทน mapFormInputChangedToState (เหตุการณ์); } } PersonalDataState mapFormInputChangedToState (เหตุการณ์ FormInputChanged) { ถ้า (event.isValid) { ส่งคืน NextButtonEnabled(); } อื่น { กลับ NextButtonDisabled(); } }
สำหรับหน้าจอที่มีการสรุปข้อมูลส่วนบุคคลสถานการณ์จะคล้ายกับตัวอย่างก่อนหน้านี้ BLoC ที่แนบมากับหน้าจอนี้จะดึงข้อมูลแบบจำลองจาก BLoC ของหน้าจอแบบฟอร์ม
คลาส PersonalDataInfoBloc ขยาย Bloc<PersonalDataInfoEvent, PersonalDataInfoState> { สุดท้าย PersonalDataBloc mainBloc; PersonalDataInfoBloc({@required this.mainBloc}) { mainBloc.listen ((สถานะ) { ถ้า (สถานะเป็น InputFormCorrect) { เพิ่ม(PersonalDataInfoScreenStart(state.model)); } }); } @แทนที่ PersonalDataInfoState รับ initialState => InfoEmpty (); @แทนที่ สตรีม <PersonalDataInfoState> mapEventToState (เหตุการณ์ PersonalDataInfoEvent) async * { ถ้า (เหตุการณ์คือ PersonalDataInfoScreenStart) { ให้ผลผลิต InfoLoaded(event.model); } } }
สถาปัตยกรรมกระพือปีก: บันทึกที่ต้องจำ
ตัวอย่างข้างต้นเพียงพอที่จะแสดงว่ามีความแตกต่างที่ชัดเจนระหว่างสถาปัตยกรรมทั้งสอง BLoC แยกชั้นการดูออกจากตรรกะทางธุรกิจได้เป็นอย่างดี สิ่งนี้นำมาซึ่งความสามารถในการนำกลับมาใช้ใหม่และการทดสอบที่ดีขึ้น ดูเหมือนว่าในการจัดการกรณีง่าย ๆ คุณต้องเขียนโค้ดมากกว่าใน Provider อย่างที่คุณทราบ ในกรณีนั้น สถาปัตยกรรม Flutter นี้จะมีประโยชน์มากขึ้นเมื่อความซับซ้อนของแอปพลิเคชันเพิ่มขึ้น
ต้องการสร้างแอพที่มุ่งเน้นอนาคตสำหรับธุรกิจของคุณหรือไม่?
สนใจติดต่อผู้ให้บริการ ยังแยก UI ออกจากตรรกะได้ดี และไม่บังคับให้สร้างสถานะแยกกันด้วยการโต้ตอบกับผู้ใช้แต่ละครั้ง ซึ่งหมายความว่าบ่อยครั้งคุณไม่จำเป็นต้องเขียนโค้ดจำนวนมากเพื่อจัดการกับกรณีง่ายๆ แต่สิ่งนี้อาจทำให้เกิดปัญหาในกรณีที่ซับซ้อนมากขึ้น
คลิกที่นี่เพื่อตรวจสอบโครงการทั้งหมด