Architektura Flutter: Dostawca kontra BLoC

Opublikowany: 2020-04-17

Pisanie aplikacji za pomocą Fluttera stwarza ogromne możliwości wyboru architektury. Jak to często bywa, najlepsza odpowiedź na pytanie „Który wybrać?” to „to zależy”. Gdy otrzymasz tę odpowiedź, możesz być pewien, że znalazłeś eksperta w programowaniu.

W tym artykule przejdziemy przez najpopularniejsze ekrany w aplikacjach mobilnych i zaimplementujemy je w dwóch najpopularniejszych architekturach Flutter: Provider i BLoC . Dzięki temu poznamy zalety i wady każdego rozwiązania, co pomoże nam wybrać odpowiednią architekturę Flutter dla naszego kolejnego modułu lub aplikacji.

Krótkie wprowadzenie do architektury Flutter

Wybór architektury dla projektu rozwojowego Flutter ma ogromne znaczenie, przede wszystkim ze względu na to, że mamy do czynienia z mniej powszechnie stosowanym, deklaratywnym paradygmatem programowania. To całkowicie zmienia podejście do zarządzania stanem, które znali natywni programiści Androida lub iOS, pisząc kod bezwzględnie. Dane dostępne w jednym miejscu w aplikacji nie są tak łatwe do pozyskania w innym. Nie mamy bezpośrednich odniesień do innych widoków w drzewku, z których moglibyśmy uzyskać ich aktualny stan.

Co to jest dostawca w Flutter

Jak sama nazwa wskazuje, Provider to architektura Flutter, która dostarcza aktualny model danych do miejsca, w którym aktualnie go potrzebujemy. Zawiera pewne dane i powiadamia obserwatorów, gdy nastąpi zmiana. W Flutter SDK ten typ nazywa się ChangeNotifier . Aby obiekt typu ChangeNotifier był dostępny dla innych widżetów, potrzebujemy ChangeNotifierProvider . Zapewnia obserwowane obiekty wszystkim swoim potomkom. Obiektem, który jest w stanie odbierać aktualne dane jest Consumer , który w parametrze swojej funkcji build posiada instancję ChangeNotifier , którą można wykorzystać do zasilania danymi kolejnych widoków.

Czym jest BLoC we Flutterze

Business Logic Components to architektura Fluttera znacznie bardziej zbliżona do popularnych rozwiązań mobilnych, takich jak MVP czy MVVM. Zapewnia oddzielenie warstwy prezentacji od reguł logiki biznesowej. Jest to bezpośrednie zastosowanie deklaratywnego podejścia, na które Flutter mocno kładzie nacisk, tj. UI = f (stan) . BLoC to miejsce, do którego trafiają zdarzenia z interfejsu użytkownika. W tej warstwie, w wyniku zastosowania reguł biznesowych do danego zdarzenia, BLoC odpowiada określonym stanem, który następnie wraca do interfejsu użytkownika. Gdy warstwa widoku otrzymuje nowy stan, przebudowuje swój widok zgodnie z wymaganiami bieżącego stanu.

Ikona usług programistycznych

Ciekawi Cię rozwój Fluttera?

Zobacz nasze rozwiązania

Jak stworzyć listę we Flutterze

Przewijalna lista to prawdopodobnie jeden z najpopularniejszych widoków w aplikacjach mobilnych. Dlatego wybór odpowiedniej architektury Flutter może być tutaj kluczowy. Teoretycznie wyświetlenie samej listy nie jest trudne. Sytuacja staje się trudniejsza, gdy na przykład dodamy możliwość wykonania określonej akcji na każdym elemencie. To powinno spowodować zmianę w różnych miejscach w aplikacji. Na naszej liście będziemy mogli wybrać każdy z elementów, a każdy z wybranych zostanie wyświetlony na osobnej liście na innym ekranie.

Tworzenie listy we Flutter

Dlatego musimy przechowywać wybrane elementy, aby można je było wyświetlić na nowym ekranie. Ponadto będziemy musieli przebudować widok za każdym razem, gdy zostanie stuknięte pole wyboru, aby faktycznie pokazać zaznaczenie / odznaczenie.

Model elementu listy wygląda bardzo prosto:

 klasa SocialMedia {
 int id;
 Tytuł ciągu;
 Ikona ciąguZasób;
 bool isFavourite;

 Media społecznościowe(
     {@wymagany ten.id,
     @required this.title,
     @required this.iconAsset,
     this.isFavourite = false});

 void setFavourite(bool isFavourite) {
   to.jestUlubione = jestUlubione;
 }
}

Jak stworzyć listę z Providerem

We wzorcu Provider powyższy model musi być przechowywany w obiekcie. Obiekt powinien rozszerzać ChangeNotifier , aby móc uzyskać dostęp do SocialMedia z innego miejsca w aplikacji.

 class SocialMediaModel rozszerza ChangeNotifier {
 final List<SocialMedia> _socialMedia = [ /* niektóre obiekty mediów społecznościowych */ ];

 UnmodiableListView<SocialMedia> pobierz ulubione {
   return UnmodifiableListView(_socialMedia.where((item) => item.isFavourite));
 }

 UnmodiableListView<SocialMedia> pobierz wszystkie {
   return UnmodiableListView(_socialMedia);
 }


void setFavourite(int itemId, bool isChecked) {
 _Media społecznościowe
     .firstWhere((item) => item.id == itemId)
     .setUlubione(Zaznaczone);
 notyfikujListenery();
}

Każda zmiana w tym obiekcie, która będzie wymagała przebudowania na widoku, musi zostać zasygnalizowana za pomocą funkcji notificationListeners() . W przypadku metody setFavourite() , aby poinstruować Fluttera, aby ponownie wyrenderował fragment interfejsu użytkownika, który zaobserwuje zmianę w tym obiekcie.

Teraz możemy przejść do tworzenia listy. Aby wypełnić ListView elementami, będziemy musieli dostać się do obiektu SocialMediaModel , który przechowuje listę wszystkich elementów. Możesz to zrobić na dwa sposoby:

  • Provider.of<ModelType>(kontekst, nasłuchuj: false)
  • Konsument

Pierwszy z nich dostarcza obserwowany obiekt i pozwala nam zdecydować, czy akcja wykonywana na obiekcie powinna przebudować aktualny widżet, za pomocą parametru listen . Takie zachowanie przyda się w naszym przypadku.

 class SocialMediaListScreen extends StatelessWidget {
 SocialMediaListScreen();

 @nadpisanie
 Budowa widżetu (kontekst BuildContext) {
   var socialMedia = Provider.of<SocialMediaModel>(kontekst, nasłuchuj: false);

   zwróć widok listy(
     dzieci: socialMedia.all
         .map((item) => CheckboxSocialMediaItem(item: item))
         .notować(),
   );
 }
}

Potrzebujemy listy wszystkich mediów społecznościowych, ale nie ma potrzeby przebudowywania całej listy. Przyjrzyjmy się, jak wygląda widżet elementu listy.

 class CheckboxSocialMediaItem extends StatelessWidget {
 ostatni element SocialMedia;

 CheckboxSocialMediaItem({klucz, @required this.item}) : super(klucz: klucz);

 @nadpisanie
 Budowa widżetu (kontekst BuildContext) {
   powrót Wypełnienie (
     dopełnienie: const EdgeInsets.all(Dimens.paddingDefault),
     dziecko: Wiersz(
       dzieci: [
         Konsument<SocialMediaModel>(
           budowniczy: (kontekst, model, dziecko) {
             powróć Pole wyboru(
               wartość: item.isFavourite,
               onChanged: (isChecked) =>
                   model.setFavourite(item.id, isChecked),
             );
           },
         ),
         Element mediów społecznościowych (
           pozycja: pozycja,
         )
       ],
     ),
   );
 }
}

Nasłuchujemy zmiany wartości pola wyboru i aktualizujemy model na podstawie stanu wyboru. Sama wartość pola wyboru jest ustawiana przy użyciu właściwości z modelu danych. Oznacza to, że po dokonaniu wyboru model zmieni pole isFavourite na true . Jednak widok nie przedstawi tej zmiany, dopóki nie odbudujemy pola wyboru. Tutaj z pomocą przychodzi obiekt Consumer . Dostarcza obserwowany obiekt i odbudowuje wszystkich jego potomków po otrzymaniu informacji o zmianie w modelu.

Konsumenta warto umieszczać tylko tam, gdzie konieczna jest aktualizacja widgetu, aby uniknąć niepotrzebnych widoków przebudowy. Należy pamiętać, że jeśli np. zaznaczenie checkboxa wywoła jakąś dodatkową akcję, jak np. zmianę tytułu towaru, to Konsument musiałby zostać przeniesiony wyżej w drzewie widżetów, aby stać się rodzicem widżetu odpowiedzialnego za wyświetlanie tytułu . W przeciwnym razie widok tytułu nie zostanie zaktualizowany.

Tworzenie ulubionego ekranu mediów społecznościowych będzie wyglądało podobnie. Listę ulubionych pozycji otrzymamy za pomocą Providera .

 class FavouritesListScreen extends StatelessWidget {
 Ekran Listy Ulubionych();

 @nadpisanie
 Budowa widżetu (kontekst BuildContext) {
   var list = Provider.of<SocialMediaModel>(kontekst, słuchaj: false).favourites;

   zwróć widok listy(
     dzieci: lista
         .map((element) => Wypełnienie(
             dopełnienie: const EdgeInsets.all(Dimens.paddingDefault),
             dziecko: SocialMediaItem(item: item)))
         .notować(),
   );
 }
}

Po wywołaniu metody budowania Dostawca zwróci aktualną listę ulubionych mediów społecznościowych.

Jak stworzyć listę z BLoC

W naszej prostej aplikacji mamy do tej pory dwa ekrany. Każdy z nich będzie miał swój własny obiekt BLoC . Pamiętaj jednak, że wybrane elementy na ekranie głównym mają pojawić się na liście ulubionych mediów społecznościowych. Dlatego musimy jakoś przenieść zdarzenia wyboru pola wyboru poza ekran. Rozwiązaniem jest stworzenie dodatkowego obiektu BLoC , który będzie obsługiwał zdarzenia mające wpływ na stan wielu ekranów. Nazwijmy to globalnym BLoC . Następnie obiekty BLoC przypisane do poszczególnych ekranów będą nasłuchiwać zmian w globalnych stanach BLoC i odpowiednio reagować.

Zanim utworzysz obiekt BLoC , powinieneś najpierw zastanowić się, jakie zdarzenia widok będzie mógł wysłać do warstwy BLoC i na jakie stany będzie odpowiadał. W przypadku globalnego BLoC zdarzenia i stany będą wyglądały następująco:

 klasa abstrakcyjna SocialMediaEvent {}

class CheckboxChecked rozszerza SocialMediaEvent {
 końcowy bool isChecked;
 końcowy int itemId;

 CheckboxChecked(this.isChecked, this.itemId);
}


klasa abstrakcyjna SocialMediaState {}

class ListPresented rozszerza SocialMediaState {
 końcowa lista Lista<Media społecznościowe>;

 ListPresented(ta.lista);
}

Zdarzenie CheckboxChecked musi znajdować się w globalnym BLoC , ponieważ wpłynie na stan wielu ekranów – nie tylko jednego. Jeśli chodzi o stany, mamy taki, w którym lista jest gotowa do wyświetlenia. Z punktu widzenia globalnego BLoC nie ma potrzeby tworzenia kolejnych stanów. Oba ekrany powinny wyświetlać listę, a poszczególne BLoCs dedykowane do konkretnego ekranu powinny się tym zająć. Samo wdrożenie globalnego BLoC będzie wyglądało tak:

 class SocialMediaBloc extends Bloc<SocialMediaEvent, SocialMediaState> {
 końcowe repozytorium SimpleSocialMediaRepository;

 SocialMediaBloc(to.repozytorium);

 @nadpisanie
 SocialMediaState get initialState => ListPresented(repository.getSocialMedia);

 @nadpisanie
 Stream<SocialMediaState> mapEventToState(SocialMediaEvent zdarzenie) async* {
   jeśli (zdarzenie jest zaznaczone polem wyboru) {
     yield _mapCheckboxCheckedToState(zdarzenie);
   }
 }

 SocialMediaState _mapCheckboxCheckedToState (zdarzenie CheckboxChecked) {
   ostateczna zaktualizowanaLista = (stan jako ListPresented).list;
   zaktualizowana lista
       .firstWhere((item) => item.id == event.itemId)
       .setFavourite(zdarzenie.zaznaczone);
   zwróć ListPresented(zaktualizowanaLista);
 }
}

Stan początkowy to ListPresented – zakładamy, że otrzymaliśmy już dane z repozytorium. Musimy odpowiedzieć tylko na jedno zdarzenie – CheckboxChecked . Zaktualizujemy więc wybrany element za pomocą metody setFavourite i wyślemy nową listę w stanie ListPresented .

Teraz musimy wysłać zdarzenie CheckboxChecked po dotknięciu pola wyboru. W tym celu będziemy potrzebować instancji SocialMediaBloc w miejscu, w którym możemy dołączyć callback onChanged . Tę instancję możemy uzyskać za pomocą BlocProvider – wygląda ona podobnie do Providera z omówionego powyżej wzorca. Aby taki BlocProvider działał, wyżej w drzewie widżetów, musisz zainicjować żądany obiekt BLoC . W naszym przykładzie zostanie to zrobione w głównej metodzie:

 void main() => runApp(BlocProvider(
   utwórz: (kontekst) {
     return SocialMediaBloc(SimpleSocialMediaRepository());
   },
   dziecko: ArchitecturesSampleApp()));

Dzięki temu w kodzie głównej listy możemy łatwo wywołać BLoC za pomocą BlocProvider.of() i wysłać do niego zdarzenie metodą add :

 class SocialMediaListScreen extends StatefulWidget {
 _SocialMediaListState createState() => _SocialMediaListState();
}

class _SocialMediaListState extends State<SocialMediaListScreen> {
 @nadpisanie
 Budowa widżetu (kontekst BuildContext) {
   return BlocBuilder<SocialMediaListBloc, SocialMediaListState>(
     budowniczy: (kontekst, stan) {
       if (stan to MainListLoaded) {
         zwróć widok listy(
           dzieci: state.socialMedia
               .map((element) => CheckboxSocialMediaItem(
                     pozycja: pozycja,
                     onCheckboxChanged: (isChecked) =>
                         BlocProvider.of<SocialMediaBloc>(kontekst)
                             .add(CheckboxChecked(isChecked, item.id)),
                   ))
               .notować(),
         );
       } w przeciwnym razie {
         return Center(dziecko: Text(Strings.emptyList));
       }
     },
   );
 }
}

Mamy już propagację zdarzenia CheckboxChecked do BLoC , wiemy też jak BLoC zareaguje na takie zdarzenie. Ale tak naprawdę… co spowoduje przebudowanie listy z zaznaczonym polem wyboru? Globalny BLoC nie obsługuje zmiany stanów list, ponieważ jest obsługiwany przez indywidualne obiekty BLoC przypisane do ekranów. Rozwiązaniem jest wspomniane wcześniej nasłuchiwanie globalnego BLoC w celu zmiany stanu i reagowanie zgodnie z tym stanem. Poniżej BLoC poświęcony głównej liście mediów społecznościowych z checkboxem:

 class SocialMediaListBloc
   extends Bloc<SocialMediaListEvent, SocialMediaListState> {
 końcowy SocialMediaBloc mainBloc;

 SocialMediaListBloc({@required this.mainBloc}) {
   mainBloc.listen((stan) {
     if (stan jest ListPresented) {
       add(ScreenStart(stan.lista));
     }
   });
 }

 @nadpisanie
 SocialMediaListState pobierz InitialState => MainListEmpty();

 @nadpisanie
 Stream<SocialMediaListState> mapEventToState(
     SocialMediaListEvent) async* {
   przełącznik (event.runtimeType) {
     sprawa ScreenStart:
       uzysk MainListLoaded((zdarzenie jako ScreenStart).list);
       przerwanie;
   }
 }
}

Gdy SocialMediaBloc zwróci stan ListPresented , SocialMediaListBloc zostanie powiadomiony. Zauważ, że ListPresented przekazuje listę. To ta, która zawiera zaktualizowane informacje o zaznaczeniu elementu za pomocą checkboxa.

Podobnie możemy stworzyć BLoC dedykowany do ekranu ulubionych mediów społecznościowych:

 class FavouritesListBloc extends Bloc<FavouritesListEvent, FavouritesListSate> {
 końcowy SocialMediaBloc mainBloc;

 FavouritesListBloc({@required this.mainBloc}) {
   mainBloc.listen((stan) {
     if (stan jest ListPresented) {
       add(UlubionePoczątekEkranu(stan.lista));
     }
   });
 }

 @nadpisanie
 FavouritesListSate pobierz stan początkowy => FavouritesListEmpty();

 @nadpisanie
 Stream<FavouritesListSate> mapEventToState(FavouritesListEvent zdarzenie) async* {
   if (zdarzenie to FavouritesScreenStart) {
     var favouritesList = event.list.where((item) => item.isFavourites).toList();
     wydajność FavouritesListLoaded(favouritesList);
   }
 }
}

Zmiana stanu w globalnym BLoC powoduje uruchomienie zdarzenia FavouritesScreenStart z bieżącą listą. Następnie elementy oznaczone jako ulubione są filtrowane i taka lista wyświetla się na ekranie.

Jak stworzyć formularz z wieloma polami we Flutterze

Długie formularze mogą być trudne, zwłaszcza gdy wymagania zakładają różne warianty walidacji lub pewne zmiany na ekranie po wpisaniu tekstu. Na przykładowym ekranie mamy formularz składający się z kilku pól i przycisku „DALEJ”. Pola będą automatycznie walidowane, a przycisk nieaktywny, dopóki formularz nie będzie w pełni ważny. Po kliknięciu przycisku otworzy się nowy ekran z danymi wprowadzonymi w formularzu.

Musimy zweryfikować każde pole i sprawdzić całą poprawność formularza, aby poprawnie ustawić stan przycisku. Następnie zebrane dane będą musiały być przechowywane na następnym ekranie.

Tworzenie formularza z wieloma polami we Flutter

Jak stworzyć formularz z wieloma polami za pomocą Providera

W naszej aplikacji będziemy potrzebować drugiego ChangeNotifier , dedykowanego ekranom informacji osobistych. Możemy zatem skorzystać z MultiProvider , gdzie udostępniamy listę obiektów ChangeNotifier . Będą dostępne dla wszystkich potomków MultiProvidera .

 class ArchitecturesSampleApp rozszerza StatelessWidget {
 końcowe repozytorium SimpleSocialMediaRepository;

 ArchitecturesSampleApp({klucz, this.repository}) : super(klucz: klucz);

 @nadpisanie
 Budowa widżetu (kontekst BuildContext) {
   zwróć MultiProvider(
     dostawcy: [
       ChangeNotifierProvider<SocialMediaModel>(
         utwórz: (kontekst) => SocialMediaModel(repozytorium),
       ),
       ChangeNotifierProvider<PersonalDataNotifier>(
         utwórz: (context) => PersonalDataNotifier(),
       )
     ],
     dziecko: MaterialApp(
       tytuł: Strings.architecturesPrzykładowaAplikacja,
       debugShowCheckedModeBanner: false,
       strona główna: StartScreen(),
       trasy: <String, WidgetBuilder>{
         Routes.socialMedia: (context) => SocialMediaScreen(),
         Routes.favourites: (context) => FavouritesScreen(),
         Routes.personalDataForm: (kontekst) => PersonalDataScreen(),
         Routes.personalDataInfo: (kontekst) => PersonalDataInfoScreen()
       },
     ),
   );
 }
}

W tym przypadku PersonalDataNotifier będzie pełnił rolę warstwy logiki biznesowej – będzie walidował pola, miał dostęp do modelu danych w celu jego aktualizacji oraz aktualizował pola, od których będzie zależał widok.

Sam formularz jest bardzo fajnym API firmy Flutter, gdzie możemy automatycznie dołączać walidacje za pomocą walidatora właściwości i zapisywać dane z formularza do modelu za pomocą wywołania zwrotnego onSaved . Przekażemy reguły walidacji do PersonalDataNotifier i gdy formularz będzie poprawny, przekażemy mu wprowadzone dane.

Najważniejszą rzeczą na tym ekranie będzie nasłuchiwanie zmian w każdym polu i włączanie lub wyłączanie przycisku, w zależności od wyniku walidacji. Wykorzystamy wywołanie zwrotne onChange z obiektu Form . W nim najpierw sprawdzimy stan walidacji, a następnie przekażemy go do PersonalDataNotifier .

 Formularz(
 klucz: _formKey,
 autowalidacja: prawda,
 onChanged: () => _onFormChanged(personalDataNotifier),
 dziecko:

void _onFormChanged(PersonalDataNotifier personalDataNotifier) ​​{
 var isValid = _formKey.currentState.validate();
 personalDataNotifier.onFormChanged(isValid);
}

W PersonalDataNotifier przygotujemy zmienną isFormValid . Zmodyfikujemy go (nie zapomnij wywołać notificationListeners() ) iw widoku zmienimy stan przycisku w zależności od jego wartości. Pamiętaj, aby uzyskać instancję Notifier z parametrem listen: true – w przeciwnym razie nasz widok nie przebuduje się, a stan przycisku pozostanie niezmieniony.

 var personalDataNotifier = Provider.of<PersonalDataNotifier>(kontekst, nasłuchuj: prawda);

Właściwie, biorąc pod uwagę fakt, że personalDataNotifier używamy w innych miejscach, gdzie ponowne ładowanie widoku nie jest konieczne, powyższa linia nie jest optymalna i powinna mieć ustawiony parametr listen na false . Jedyne, co chcemy przeładować, to przycisk, dzięki czemu możemy go owinąć w klasyczny Consumer :

 Konsument<PersonalDataNotifier>(
 budowniczy: (kontekst, powiadamiający, dziecko) {
   zwróć PodniesionyPrzycisk(
     dziecko: Tekst(Strings.addressNext),
     onPressed: notifier.isFormValid
         ? /* akcja, gdy przycisk jest włączony */
         : zero,
     kolor: Kolory.niebieski,
     wyłączonyKolor: Kolory.szary,
   );
 },
)

Dzięki temu nie zmuszamy innych komponentów do przeładowania za każdym razem, gdy korzystamy z powiadomienia.

W widoku wyświetlającym dane osobowe nie będzie już więcej problemów – mamy dostęp do PersonalDataNotifier i stamtąd możemy pobrać zaktualizowany model.

Jak stworzyć formularz z wieloma polami za pomocą BLoC

Na poprzednim ekranie potrzebowaliśmy dwóch obiektów BLoC . Kiedy więc dodamy kolejny „podwójny ekran”, będziemy mieli razem cztery. Podobnie jak w przypadku Providera , poradzimy sobie z tym za pomocą MultiBlocProvider , który działa niemal identycznie.

 void main() => runApp(
     MultiBlocProvider(dostawcy: [
       Dostawca bloków (
         utwórz: (kontekst) => SocialMediaBloc(SimpleSocialMediaRepository()),
       ),
       Dostawca bloków (
           utwórz: (kontekst) => SocialMediaListBloc(
               mainBloc: BlocProvider.of<SocialMediaBloc>(kontekst)),
       Dostawca bloków (
         utwórz: (context) => PersonalDataBloc(),
       ),
       Dostawca bloków (
         create: (context) => PersonalDataInfoBloc(
             mainBloc: BlocProvider.of<PersonalDataBloc>(kontekst)),
       )
     ], dziecko: ArchitecturesSampleApp()),
   );

Podobnie jak we wzorcu BLoC , najlepiej zacząć od możliwych stanów i akcji.

 klasa abstrakcyjna PersonalDataState {}

class NextButtonDisabled extends PersonalDataState {}

class NextButtonEnabled extends PersonalDataState {}

class InputFormCorrect extends PersonalDataState {
 ostateczny model Danych Osobowych;

 InputFormCorrect(ten.model);
}

To, co się zmienia na tym ekranie, to stan przycisku. Dlatego potrzebujemy do tego odrębnych stanów. Dodatkowo stan InputFormCorrect umożliwi nam przesłanie danych zebranych przez formularz.

 klasa abstrakcyjna PersonalDataEvent {}

class FormInputChanged rozszerza PersonalDataEvent {
 końcowy bool isValid;
 FormInputChanged(this.isValid);
}

class FormCorrect rozszerza PersonalDataEvent {
 ostateczny formularz Danych OsobowychDane;

 FormCorrect(ten.formData);
}

Nasłuchiwanie zmian w formularzu ma kluczowe znaczenie, stąd zdarzenie FormInputChanged . Gdy formularz jest poprawny, zostanie wysłane zdarzenie FormCorrect .

Jeśli chodzi o walidacje, istnieje duża różnica, jeśli porównasz ją z dostawcą. Gdybyśmy chcieli zawrzeć całą logikę walidacji w warstwie BLoC , mielibyśmy dużo zdarzeń dla każdego z pól. Ponadto wiele stanów wymaga, aby widok wyświetlał komunikaty sprawdzania poprawności.

Jest to oczywiście możliwe, ale byłoby to jak walka z API TextFormField zamiast korzystania z jego zalet. Dlatego, jeśli nie ma jasnych powodów, możesz pozostawić walidacje w warstwie widoku.

Stan przycisku będzie zależał od stanu wysłanego do widoku przez BLoC :

 BlocBuilder<PersonalDataBloc, PersonalDataState>(
   budowniczy: (kontekst, stan) {
 zwróć PodniesionyPrzycisk(
   dziecko: Tekst(Strings.addressNext),
   onPressed: stan to NextButtonEnabled
       ? /* akcja, gdy przycisk jest włączony */
       : zero,
   kolor: Kolory.niebieski,
   wyłączonyKolor: Kolory.szary,
 );
})

Obsługa zdarzeń i mapowanie do stanów w PersonalDataBloc będzie wyglądać następująco:

 @nadpisanie
Stream<PersonalDataState> mapEventToState(PersonalDataEvent zdarzenie) async* {
 if (zdarzenie to FormCorrect) {
   wydajność InputFormCorrect(event.formData);
 } else if (zdarzenie to FormInputChanged) {
   mapa wynikówFormInputChangedToState(zdarzenie);
 }
}

PersonalDataState mapFormInputChangedToState(FormInputChanged zdarzenie) {
 if (event.isValid) {
   return NextButtonEnabled();
 } w przeciwnym razie {
   return NextButtonDisabled();
 }
}

Jeśli chodzi o ekran z podsumowaniem danych osobowych, sytuacja jest podobna do poprzedniego przykładu. BLoC dołączony do tego ekranu będzie pobierał informacje o modelu z BLoC ekranu formularza.

 klasa PersonalDataInfoBloc
   extends Bloc<PersonalDataInfoEvent, PersonalDataInfoState> {
 końcowy PersonalDataBloc mainBloc;

 PersonalDataInfoBloc({@required this.mainBloc}) {
   mainBloc.listen((stan) {
     if (stan to InputFormCorrect) {
       add(PersonalDataInfoScreenStart(stan.model));
     }
   });
 }

 @nadpisanie
 PersonalDataInfoState pobierz InitialState => InfoEmpty();

 @nadpisanie
 Stream<PersonalDataInfoState> mapEventToState(PersonalDataInfoEvent zdarzenie) async* {
   if (zdarzenie to PersonalDataInfoScreenStart) {
     wydajność InfoLoaded(event.model);
   }
 }
}

Architektura trzepotania: notatki do zapamiętania

Powyższe przykłady są wystarczające, aby pokazać, że istnieją wyraźne różnice między tymi dwiema architekturami. BLoC bardzo dobrze oddziela warstwę widoku od logiki biznesowej. Pociąga to za sobą lepszą użyteczność i testowalność. Wydaje się, że do obsługi prostych przypadków trzeba napisać więcej kodu niż w Provider . Jak wiecie, w takim przypadku ta architektura Flutter stanie się bardziej użyteczna wraz ze wzrostem złożoności aplikacji.

Ikona na wynos

Chcesz zbudować przyszłościową aplikację dla swojej firmy?

Skontaktujmy się

Dostawca również dobrze oddziela UI od logiki i nie wymusza tworzenia osobnych stanów przy każdej interakcji użytkownika, co powoduje, że często nie trzeba pisać dużej ilości kodu, aby obsłużyć prosty przypadek. Ale może to powodować problemy w bardziej złożonych przypadkach.

Kliknij tutaj, aby zobaczyć cały projekt.