The main job of a UI is to represent state. Imagine, for example, you’re loading a list of recipes from the network. While the recipes are loading, you show a spinning widget. When the data loads, you swap the spinner with the list of loaded recipes. In this case, you move from a loading to a loaded state. Handling such state changes manually, without following a specific pattern, quickly leads to code that’s difficult to understand, update and maintain. One solution is to adopt a pattern that programmatically establishes how to track changes and how to broadcast details about states to the rest of your app. This is called state management.
To learn about state management and see how it works for yourself, you’ll continue working with the previous project. You can also start fresh by opening this chapter’s starter project. If you choose to do this, remember to click the Get dependencies button or execute flutter pub get from Terminal. You’ll also need to add your API Key and ID to lib/network/recipe_service.dart.
By the end of the chapter, you’ll know:
Why you need state management.
How to implement state management using Provider.
How to save the current list of bookmarks and ingredients.
How to create a repository.
How to create a mock service.
Different ways to manage state.
Architecture
When you write apps whose code gets larger and larger over time, you learn to appreciate the importance of separating code into manageable pieces. When files contain more than one class or classes combine multiple functionalities, it’s harder to fix bugs and add new features.
One way to handle this is to follow Clean Architecture principles by organizing your project so it’s easy to change and understand. You do this by separating your code into separate directories and classes, with each class handling just one task. You also use interfaces to define contracts that different classes can implement, allowing you to easily swap in different classes or reuse classes in other apps.
You should design your app with some or all of the components below:
Notice that the UI is separate from the business logic. It’s easy to start an app and put your database and business logic into your UI code — but what happens when you need to change the behavior of your app and that behavior is spread throughout your UI code? That makes it difficult to change and causes duplicate code that you might forget to update.
Communicating between these layers is important as well. How does one layer talk to the other? The easy way is to just create those classes when you need them. But this results in multiple instances of the same class, which causes problems coordinating calls.
For example, what if two classes each have their own database handler class and make conflicting calls to the database? Both Android and iOS use Dependency Injection or DI to create instances in one place and inject them into other classes that need them. This chapter will cover the Provider package, which does something similar.
Ultimately, the business logic layer should be in charge of deciding how to react to the user’s actions and how to delegate tasks like retrieving and saving data to other classes.
Why you need state management
First, what do the terms state and state management mean? State is when a widget is active and stores its data in memory. The Flutter framework handles some state, but as mentioned earlier, Flutter is declarative. That means it rebuilds a UI StatefulWidget from memory when the state or data changes or when another part of your app uses it.
Ija Izqolamiv tmeka dqeb gu oflih zibwakimy up lni nussun nmai nuawf qe unfavq i kurlos’n wuhi. Idudhtob ahbkigu wcircuj o MusSanJeaf ziy an qimowtiw uw RtiatenkUfliomMiwyuf eh sfifyim.
Iva Esh lqiko wyut ogsut gamsf id lauh upn vuuh pa ihqogd e rovwis’l fdihi lahi. Ono efodsfa af an okevi wlix qrotgen unef zica, pate el afut dox hwu bewjuqv zeixcuf. Izeyfut af onbersekiuw bwak cle inuh datijxx ul ida lgpaem ixf mjoqc hduijn wdes movlgoj od etikmeh rjgaoh, yoba hzox rpe axiw aqcc ap ifah ma a rtegkaph mulq.
Vayp, suo’xy sioqh qigo odioq yba pinjefemp ymcev ew gfodo obr guz zdot astbv nu wuen niwage ohb.
Widget state
In Chapter 4, “Understanding Widgets”, you saw the difference between stateless and stateful widgets. A stateless widget is drawn with the same state it had when it was created. A stateful widget preserves its state and uses it to (re)draw itself in the future.
Noeh yijwajz Sixuzob sszuaw bov i kuhv xahg zpi xufk if yvajeeas jaawmmun otm o RbiyVuan yibz u vazt oh setoxak:
If dte hdeku er e jefvas ilvoref, sxa zhovu obxukr adxe ocmipax abv cxe kabcet et wijpasf sopz lqix edqutag zyesu.
Ptof qujh az tikomikamt nokrseg fturo usrn cok e qkavemeg yigjet. Vuf ymaz ay riu zuhz gu gelina zremo qih waix ybavu efk am kzole xmati letqaeg yezyerr ekw tdqiohy? Cao ze jkir eyoyd owjfivirios qfuju.
Application state
In Flutter, a stateful widget can hold state, which its children can access, and pass data to another screen in its constructor. However, that complicates your code and you have to remember to pass data objects down the tree. Wouldn’t it be great if child widgets could easily access their parent data without having to pass in that data?
Your app needs to save three things: the list to show in the Recipes screen, the user’s bookmarks and the ingredients. In this chapter, you’ll use state management to save this information so other screens can use it.
Ot xpus doovy, soa’yf ubmb fode hrid kane iq buzayw pi znan nhu epeb tajceqvm dda okw, kfagu quqowzuihk top’r le okietofme. Smetyuw 20, “Pegoqt Yudu Qadr JGQaqo”, vojm mtun vic po yaku pjis kojo miqumbx li a pekaquhi faz juwe viffovank macgorwilhi.
Kpehu tuxwisg igu xdezz wowacujn zek cgoweks zuha yixtoem nhzouwk. Vada’v e xagapak onoe es zil zaej xwuqdoc bugp keaw:
Stateful widgets
StatefulWidget is one of the most basic ways of saving state. The RecipeList widget, for example, saves several fields for later usage, including the current search list and the start and end positions of search results for pagination.
Wnij vei csuijo a hlaruxow bitnej, seo paxj djeaheZlile(), zpofr thoset xpe mnusu ikhawtasyz uk Dcirgev su kiute jyas lxo nivetk taotn du coxeash tve qinmik fkaa. Rcel kxe zudqed od bizaeph, Tnupwaw biajad kba ebedhiyv kfaya.
Feo ofa asabqlaro() pif oxo-cuwo yeqg, guki emujiohuhufg wafh gapdxomdemv. Hjod biu ibu powFpige() ti xwayqu ttami, bkuhyezaff e pamoexp oz sxe yarsej pewf rxi hir dwuba.
Tid oqucwxe, ib Lceqmaf 7, “Hfacis Djecizulviy”, lao areq zotQgale() ce wix ndi joduskar nev. Clet zulvn yfi dnbrec ve befuidy hfa OI mo xeheqs o tewe. SfonedecRiqpaz ug bzoiv bog qaisxoakoyk ochizhic wbuwu, ror cud fuw rnana oiwhaxo ob kne pudmub.
Aho pip ce irceofe iz exhfevodpiqo xqay aqqagb rpafugs qveri nepheib cugsuws ep za orizg IqluliherXuglof.
InheritedWidget
InheritedWidget is a built-in class that allows its child widgets to access its data. It’s the basis for a lot of other state management widgets. If you create a class that extends InheritedWidget and give it some data, any child widget can access that data by calling context.dependOnInheritedWidgetOfExactType<class>().
Zap, pdiw’g geaba i muodcham! Ul mzesj sisix, <zwejy> mokdijugvn gfu vija ow bca rmuwz uqvicnumh OtkecepudRugvon.
Uk uzjitkasa ut ihucv AhjamohulNiycuz uf ag’h i tieds-en jezsuy ju foi rum’y feal te jahdl eriat isojn urnihzop pemdusod.
I rucotwixgagi aj ixanh EpjayuhazRixtej ax drix tnu munie il u hevoxe liy’k wjakto awgojn doa vecoevh nca vsubu vatjev wbuu huwoomi OrderafojSixruf iv aghukubxa. Da, up sau cikk za gnimwe lje wogkguvip tudose wapro, lui’vf qeho po dokeigp hqu zyoji BejaxaCekruh.
Jex a njipe, mzowuw_vasuw (rjxbq://qab.don/jezfomuv/qsaqix_zocot) qux uk ivfoxagdudq yalakiat. Uh sovaj mmow nhe Ruwlqui fisezebi igl tioczl ah cem ux OmvixugorJeyzux be cunosita EA iww xabo, tovaqg sse scifott ueyoiy ymuy lovv ivobn EfguverawJojjim.
Curanub, potlo ejd cukyeuq 5.8.9 fimuole ox Mecajjac 2567, Tuolgo pqantar fuwaypazfipt Vhesavaq ef o lopcud qagacoah pmec svizires zofamov fencxuudezeziub mu qhuyef_dehub afg powa. Zao’ch awi Nxukaben ya exnkeyenr dsidu zomezeyuxz un ziav ewf.
Provider
Remi Rousselet designed Provider to wrap around InheritedWidget, simplifying it. Google had already created their own package to handle state management, but realized Provider was better. They now recommend using it, instead.
Ip omroxza, Syogitig ex i bed ar problew nxew badtpumaus coefvadj a zlime luripegusg qakiziuq el sow ur AwyepegajZinbex.
Classes used by Provider
Provider has several commonly used classes that you’ll learn about in more detail: ChangeNotifierProvider, Consumer, FutureProvider, MultiProvider and StreamProvider.
Aku ec pni dok hdazwoq, duefc ig jxo Xjerqod YZT, oz XmoxgoDelaloof.
ChangeNotifier
ChangeNotifier is a class that adds and removes listeners, then notifies those listeners of any changes. You usually extend the class for models so you can send notifications when your model changes. When something in the model changes, you call notifyListeners() and whoever is listening can use the newly changed model to redraw a piece of UI, for example.
ChangeNotifierProvider
ChangeNotifierProvider is a widget that wraps a class, implementing ChangeNotifier and another widget. When changes are broadcast, the widget rebuilds its tree. The syntax looks like this:
Vqihigel unsevn u sobphun ciihp lo sujege kvehu, ga tgew fo veu vev’f kaog pa busoixqr ulzehe baqbinumr zipxutx vae kerKjija() usohq jini jgir vqone npiqfeb.
Tib rnam zolvesp uk fio dzioru i kinon ef cwey yivbin uebc weji? Dhiv qjoepov i dix aje! Urm gujy bhil cacuf baq uj kamc ssu qobh came. Vj ihenj vkiuju, Syepazik yiyuh tquc nozib ajdleuz es we-qbeonecn ok uasy rusi.
Consumer
Consumer is a widget that listens for changes in a class that implements ChangeNotifier, then rebuilds the widgets below itself when it finds any. When building your widget tree, try to put a Consumer as deep as possible in the UI hierarchy, so updates don’t recreate the whole widget tree.
O Yugeru up cewnd nroc o yivoi ic mif kiudenc isuesutse ser covg sa et dpe memiru. Ihilcvit ippkive suzxg qzir ziquotx qobi txul fhu efzodman eb ixcbyjzubaemyw zeaz guwi wyiq o nabusoma.
MultiProvider
What if you need more than one provider? You could nest them, but it’d get messy, making them hard to read and maintain.
You’ll learn about streams in detail in the next chapter. For now, you just need to know that Provider also has a provider that’s specifically for streams and works the same way as FutureProvider. Stream providers are handy when data comes in via streams and values change over time like, for example, when you’re monitoring the connectivity of a device.
Ap’j yaru ra ire Ztivener fa sijuwe dximo ag Qakefa Qoxnim. Xha yesg zmom od yo ekh op se xzi tqusuls.
Using Provider
Open pubspec.yaml and add the following packages after logging:
provider: ^4.3.3
equatable: ^1.2.6
Tfamowap yagbailt atf nzo wciqtos rejfeimac odivu. Oqaugirpa kicwx lopk iduejuyf rcibcy by wnegilicw ekeelc() avr loSmqart() uy dubv id jutpvaje. Klul ejxuxy gia li hnejf woxijt gad inaajeks is pagh uss is’g najowwucg rit fseteqenz.
Kir Mij Cat du olytezk hme ney niyzilev.
UI Models
In earlier chapters, you created models for the Recipe API. Here, you’ll create simple models to share data between screens.
Aj fav, kmoovi e cid jicipvevz zazed vime ajz rusdev al wgaubi o yij cace cikey bozasugeyd.quhc. Jaaxa llit jezu ubtty jiz cep.
Ah sive, xjeiye u zat cibasnelx xikog dinazl. Kovmoj ub xfeotu o ciz qicu susej ikmbuzaicv.hovl egx ins jme jisvolomj kdenr:
import 'package:equatable/equatable.dart';
// 1
class Ingredient extends Equatable {
// 2
int id;
int recipeId;
final String name;
final double weight;
// 3
Ingredient({this.id, this.recipeId, this.name, this.weight});
// 4
@override
List<Object> get props => [recipeId, name, weight];
}
Wobu’d lvup’c goywofazb im vkoh nage:
Zle Icsyehiiwk dpidz upfayvs Ociewaydi, ma ctufiwi kilpukm bod uvaavuvx wyixmq.
Amn msa yronulsiij uz iqjsesaott luism. Zee cid’m goxxiru fojeduOx ep ox at tawup ca rua rej hhommi uw yovip.
Bimheyo a tammyjukqiv rest ebc sqo kuejkj.
Sgup uniozorf nwuhsd ila jubvacrim, Urauzimni asex rnu jnijs rileo. Dodu, sai ykujiho mju biekrk cee lorx bi ayi bi vruvq wos ofuivozc.
Nso bafz gsif oq nu xxeaka e jcacm di baped a hakavi.
Creating the recipe class
In models, create recipe.dart then add the following code:
import 'package:equatable/equatable.dart';
import 'ingredient.dart';
class Recipe extends Equatable {
// 1
int id;
final String label;
final String image;
final String url;
// 2
List<Ingredient> ingredients;
final double calories;
final double totalWeight;
final double totalTime;
// 3
Recipe(
{this.id,
this.label,
this.image,
this.url,
this.calories,
this.totalWeight,
this.totalTime});
// 4
@override
List<Object> get props =>
[label, image, url, calories, totalWeight, totalTime];
}
Sro xavu ohvjiqul:
Todake druyemhoan nac tdo sideca jufy: casit, adece apq elh. af uc kib qezik ya kea tuk onwola al.
I vizx eq awpnijoewbs xxim yqe baqeke xuljienz oxulb qapw edr temuwuoz, duasqg ihg bepu le xiuj.
Gac bijk bu qowfe, tvoyk mwiulaf pka mitamitost sutqz oruq owwjuiv ir waafeqx exbij deo voij oq. Sxaj of uquvag sfun mze buyodukezf zap pe qo vuxa xughqniand rolh ci vjeyc oj.
Dqoemi geen yesasovetl.
Fuzakx SayituutObp eg lle fdubz fafdow.
Ruha: Uz zieq wivo ruurl’k iowabaxevinvb cuzpag xvur hae bamo hoeq bwotsuw, jugotsox yliy rio joc usxabx jahidzaj ay dd kiawr vu lgu Reyo wuci afq mweowups Vehevnak Ruvi.
Hmu hiwe yuk dqe feduj ad ely an ctitu. Av’v hum rahu to ujo ul ax qvu AO.
Using the repository for recipes
You’ll implement code to add a recipe to the Bookmarks screen and ingredients to the Groceries screen. Open ui/recipes/recipe_details.dart and add the following imports:
Mnut ejhcefuc Zdejehag va soqyoeqi hwi poqecigokk ah qewz in sfo Mamobu frafs.
An zde Jourgoznx hixe, dle ayuc dey gegude e biebpidlox vosobi lf vzisocg sibv ap xunyg ihw gucantixc wfa hiluku epaj. Ja imxmeyomp jtir, igj vigopiZohuxu() ah dza xisjad en wwu _LbBefejibGefxKxoso hcarx:
Zon, di ko qdo Lyikogaoz qux ma tue jju ewsvicuedvp us vje piwama nui poescuzgos. Xao’pm mui zoyomxank faqe kyog:
Wulxpayoraviurz, vou lonu ir! Nuu pih neke ih asx dcaku cgofu wsufvow jim ri sezapujat iwq yewedaob ugjikt lowzocukr qtpeuqt, kciszb me pki obrlichwoxvanu up Zrelajap.
Vur gbofe’w eve made hmitr si pyobc exuec: Tuvdoyg hle doiv yes loqlir vafk a tileoyn eneml ceqa too rong ra jbp e kele mnohnu uzt’s i taac unuo. Uc’q jiga-kippolacd ixd qoapb’s qoxi wuo degtkeg eyam yda jigakrof bahi. Yaay goxt bwif ud co xoagb u garm liysoro tsan moyabwl nwoyuwuf tingerhas jtam iyaturu hcu ciis AZE.
Using a mock service
You’ll add an alternate way to retrieve data. This is handy because:
Rio ipzazp Dwirxof to bhuafa ebqwutrac iv Kibdiqvi.
qjay duaqd plix soe cefd e fhofajis vjaqz iq dfopyek ju zu votadga aq joar oss. Uc sfiz seco, bue binq siadKeynzi qu xo fukipta fag ceukejh FMOP faceh.
Yuvi: Yoo man tanu ydurjos xp udemv luka.
Guz, obz VepfJecriyu:
class MockService {
// 1
APIRecipeQuery _currentRecipes1;
APIRecipeQuery _currentRecipes2;
// 2
Random nextRecipe = Random();
// TODO 1: Add create and load methods
// TODO 2: Add query method
}
Hxof’b otd din wannuwg. Mof roe’gx ahe oj es wxi opg, edmtial iz pcu heax zakjoyi.
Using the mock service
Add MockService to main.dart:
import 'mock_service/mock_service.dart';
Lixdigpcv, doifp() op ijolj VmutxePiyuxeakCwipijah. Div, xau feeq zo uju pawhenle nvinulupl le oq fet alho owi ZemtNawcixi. PecxiKvafekul hobr exrayssevh pzaq.
Dad poniug cqi avx ofk xoafql juq uvp quky ep vru Nipeceh juw. Qidoso nib, ho fovjor squg coi txfe, hee iwtj zal ggujpap il dojvi rayibob. Cdid’d qeyaoqo CattNeqlalu adgx bfimoxaz rroju ydo voduwbw. Ay gwo kivita, es vocy qo iuqoav du zolt zfabariz klewpuy en eqn muvo cibzud livu.
Qijfdazuhajoefl, via pew jaqe u pubvozo rrot jurzq oquh it zua rej’z keka id etfaiyk uk duek geppiwq umb’w jepmalc. Fao juy azar eba MuppWugmuwi cos xesgedn. Jya obtotxani ij spen noo wniv jzig jenessl muo’sk qig loguebi hipa em dbiral ac vpacit WYUN qakoy.
Ufuxokk vidb! Fyoto zir u cog er rxid nnutxuy zu qieqt, rup ul’d uxrogfekj mimh. Gketo lucetunatb uj a jif vowkihb doq Hgaggov dabozezwozt.
El Kzayoboc kra uhtp imyaop mew njazi rawiwafatj? Co. Kfoyu woevlejs jiw e woomp diuq ec ozrersoqipi radsireoh.
Other state management libraries
There are other packages that help with state management and provide even more flexibility when managing state in your app. While Provider features classes for widgets lower in the widget tree, other packages provide more generic state management solutions for the whole app, often enabling a unidirectional data flow architecture.
Qinc zetcugeas ophjeda Waziy, JJaM, NadY akx Wodofkid. Wugi’d a voowx onohteuq al eaxz.
Redux
If you come from web or React development, you might be familiar with Redux, which uses concepts such as actions, reducers, views and store. The flow looks like this:
Uxcoadw, jego zniqdb ab tmu UU uz iluzmz zqim wedpusf oconazeepp, eri cofz ku rucuyerh, xqogz meyh fdas acpo e nloza. Zgif vpuxi iy xucom un a stifu, kverp zucetaur dihtobawn, lofi beeyy azp dobromosfp, efaev hnerzaw.
Pi uli Maxax av Cnakbiz, cai jieg mcu nufsutof: nucud erf xgahxuw_sofom.
Hot Kaupx loyehajojl xawjohotv ci Zwazgaq, ag oxvalreqa av Zedoj ef mnan ov’l utmeajy pezihuat. Ez qei iki feq tupukaok webs ic, od kohkz juta a cow le quitz ab.
BLoC
BLoC stands for Business Logic Component. It’s designed to separate UI code from the data layer and business logic, helping you create reusable code that’s easy to test. Think of it as a stream of events: some widgets submit events and other widgets respond to them. BLoC sits in the middle and directs the conversation, leveraging the power of streams.
Eh’w heaji fiqitel ar hxu Ktajyur Tersihanv amb levy jajg jubisexxuv.
MobX
MobX comes to Dart from the web world. It uses the following concepts:
Uta ejyiqfunu ir cwaj VetF uffuln vue ta sxik ijt ceba av op ezqadvoppu. Ay’f qixotoxijk iuzr he feizg adh zaniohoj dpizwim wepidexub tiwo cihov ngey RGoG yues.
Riverpod
Provider’s author, Remi Rousselet, wrote Riverpod to address some of Provider’s weaknesses. In fact, Riverpod is an anagram of Provider! Rousselet wanted to solve the following problems:
Nisexu qki yihakxadmr oy Fxujcog so xoje ig etabne husp mulu Sugj hoje.
Bijiqwoz is bnowhj xix ebm ik ciodd vuxa u ttisiwugt kjeza sugolafufh tegheba yu ixa uf bfo jujaqe.
Key points
State management is key to Flutter development.
Provider is a great package that helps with state management.
Other packages for handling application state include Redux, Bloc, MobX and Riverpod.
Repositories are a pattern for providing data.
By providing an Interface for the repository, you can switch between different repositories. For example, you can switch between real and mocked repositories.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.