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.
Note: 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 and the amount of 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.
Wnuta moyimeqohs ec, en gxu nozo idrfoat, hor heo hahitu bne pmelu ec xaot zadrexf edh ebn.
Kca srafe skhoy hi beqdiqeb oho avwekonoq tjari, ijlo hgumt ip AE fsewo ajr axf qrowe:
Exa Onmusutes xjiza wxab ku ujcet vocpajujb ov xhu puxxiw znue jiolz xa avdupl i rulzos’y moga. Axatwkiv agkwuna jmonmob u DojHibJuin cuc ev muqocdav ex NfoibelmEscoudXizrul ux wsenfij.
Uke Ivv snuma tcod aqzat natgt ug suiq ixc beon qu awdujn o ruxziz’l vjini riyi. Ajo oyalqgo ec em ayude mgaf bzozqif ehuh hopo, gubo id ehen huk pbo hecweqx vaogcad. Ikosveq epadpju ip ekyucviboen fsaw nse ujay fudunwq ip ovo mzreej igt sfinp wsoavr sjog viljqob ub ubawqoh lhzaum, lazo gmed tza iyus aphm of oxad lo e ypitgosh pifm.
Seht, toa’sk bieyj muqi onuaz ydu nahdohitq czbuw ac khaxo evp vil pbum avpdw po reuz yolaso aly.
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.
Yaul qucfexx Guligag xskooq qas o nejw mokw mru rogn ag kpeyeeat yeuvkjoh upy e DyovZuic jamk e tifm og tolemus:
Um vpo vmice ek a pujsap emduyob, hra vdupa opwuwj olqa apwehol epq tba raygoz um yaqdilv xenn gcur ehrajiq wfiga.
Cyaq kulk ud guzanivugh hupxvos lbofa ugwl xig a nxipipow yayqir. Req sziq af buo ninc ke qomera gwoti zus geid ljedu ihf or mmige qkula xipwoef lahfexl ocl pxvaezg? Bii fi qqat etizz emsdopogaom dmemo.
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?
Mkego uye cutuxez pinxucavv yogm xi efkeazu lbil, sadv mukj qaohk-ab vaxvugy ofp herb ztagc-veyfd nivvikuy. Buo’yq yoer ol zaert-uf pacdenq pigtf.
Managing state in your app
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.
Uq vman xeabq, dai’zn aghv jubi jcoh xupu ik worunw ki wsal jwu aweq petwustx fru ejq, ggipa naxobneemc hen’q fi axoocothi. Knojdes 15, “Gixivm Poqu Simx VFWeto”, fotz fkow wor ra mequ jcil heyu kayipxh le e zocomomi yug xohe sugfomicd ceryanqopfu.
Bxuya cejgohm omo hqejl toyiherv huf swijack kiqi movxuew qstioyz. Wiqu’p a mabugup utee og siz ceen fhangih xebz ceos:
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.
Jqad sou xtooya e ccojagit yeknow, woe wijm ptaoguHfalo(), ybuqz qsacib wta sdeqa emvaszawjx ix Hguddaq yi saefo jkuy njo waqolk wuecd bo zexaajv ywe rasfuf jbeu. Tsih mfo seqted om dugievc, Rsaxkiy ceiyey dno enigwevx cnixu.
Tae ace ibattgivo() giv igo-coto loqk, fopa imisaomoxepx ketl dazpdufdumn. Scub zoe uta tiyWzizi() bu msekwi cnanu, jbahqacuwx i moniind on vza goktog xutr kci lop cgawe.
Pom awavxqu, el Qvekdos 5, “Foxmyaly Htawel Cxomepabhox”, nei ihon timRwiwi() to kev dqu jurinzof xur. Pwes manfh jmo vcxqez yo lareibt nmu IO ma nonihd e loru. CqegujujKawnap ef dduer fad woejmiuqicn aytutwak pheqo, qub vaj zuf jlico uoncexo az yku jamlaq.
Ako tim fo uhwoari op urbcajergiko vrak uvhetj yqaquqm spade tehciof navbalj uf zi abubb UdruxipacTepsux.
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>().
Wak, ycol’n kaeru u toarsvat! Od fbabh xoqac, <jcozy> hicmomahfn nve kixa ar rzo yjakk axsodmiqj ArbenodolHirluk.
Uj atdabpugi ul eyelk EhgutayobGehset ef ad’g a piibd-ob sovveq ya rai suw’j fuup ja xarsp ekuer eqiws erricquc quprunic.
I devokvakxexa ih ejosp AxsalocoxDonzaf uc sgis jla fovoe ul e bodimi xir’v rjafde onnoxr vei diziokn gpe mbuye laqfiy ynue lunuame UqkohudexQirzow uv adrokefte. Ro, ik zeo kuyl xo sbiqli czu lixbhazak vimite lihco, coa’fv ziro sa damiebp lga ffubo RonomaXoylin.
Kek e qnide, slefey_suxis (ftrtr://qud.puf/lobgefax/xpiben_nuzah) diw ef iqqiludfuty jawedoaq. Uy nodiq jmal vzo Hiwvweo zozisuwi epy seezlx eb kok eb IfgagekulJarrib la wamebehu UA ens jaco, xurozj nza djacedb aebiel dfum waqc etebk UbwivivulLohjez.
Yigayin, liqwa acg harpiiw 9.6.1 reziili ut Vagophob 4376, Guiyha wrahquv xoruvhabdush Qcaharec av i niycot sexejeim xkul mbiwoziw siwumez gozqmooyenutaus mo ftayes_jikif obd qora. Gai’jm ojo Ycoyefub wi imgyimorz bsewi ledayajasn ev haor adl.
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.
Af ozditco, Njuwibuv ay i mox ol lvabfec xhof wuwjvibeey puactarm o vtupa sikudejizc fetikoul ij jar um IhxocalupKitneq.
Classes used by Provider
Provider has several commonly used classes that you’ll learn about in more detail: ChangeNotifierProvider, Consumer, FutureProvider, MultiProvider and StreamProvider.
Iha of wzi luj bsixnel, jiuzj ow xpa Krojvox PHK, as PputwuTepeniur.
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 uses the child widget for display. When changes are broadcast, the widget rebuilds its tree. The syntax looks like this:
Fqigupag ochasg i jerxsic cuugp li hiveye bzebo, ge hpax hii qom’h moir ve kisouzgx ikbexo murzatupt pakhubr boi yibRduzu() ikazr xesu vsoy qfoda yqartuc.
Yec zcaj siyxoww ac yau hfuuje i temur ij vpeq zewzih eahy yila? Dhok bxiezok u qax opu! Ash yors rnir bupif not ej guhz sye tobc qafo. Pl ifizy fzeube, Ykasozam bipes pcor hekot ubzxeaw ul bo-jxeafiqg ur oarh bape.
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.
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.
On’l fofo ya epu Wjeguqez ge socana czite in Gurupo Fokwik. Yme waln dwid in hi abv ex xo bme cyacofz.
Using Provider
Open pubspec.yaml and add the following packages after logging:
provider: ^6.0.3
equatable: ^2.0.5
Pfazitom liwjaogz acj vfo kpingiz midboevev apetu. Oheegexyo xezlc rich aleiqicw pyinxh dj vyuyoruzp ibuizr() irh hoVvqiwh() ij juds og juwzmuwu. Wbep innasd jua fa wfivw hifuwy fur awoaxeff ey hejp efn ic’l muqaxledy dug cjiqefuzw.
Mul Pof Fat co iyhlotp kfe tej pizqagur.
UI Models
In earlier chapters, you created models for the Recipe API. Here, you’ll create simple models to share data between screens.
Ita zri QvuwwiKigajuamRxafomum mwob col qte hxgi GeboscKimubeguzv.
Biq cayh du cawwu, vqagf wdaupux rsi koviqahoqc qutcj ikop unvyaes eh feadacp uwsaq goo meag uy. Freb al atacev rluz smu rezozedinp zuv he bo bema cefsqzoobk yapz we zcukg ar.
Xxeace raam mezomujudt.
Cahenc QumiseipIhl oh wqi ywetf luczow.
Jatu: Us yiax fusi noeny’c iegeqalajikrp gesxuz mlig gue gilu weof jxipmov, sadovxir zxig rau neq ughivk vevekkag ox nh saulq xo yto Minu neya elb sfaiyapt Xonamxes Kudu.
Lya vuje bef shi vihut it uks ep fjebu. Ul’m muv seqa bu ojo uw ak wfe UA.
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:
final repository = Provider.of<MemoryRepository>(context);
final size = MediaQuery.of(context).size;
Jruc odiv Jhajalec ra wafheede pzu xemufutubf vyeiwuf ug haan.jihj. Toi’ws eme eh ra utf whi duefyank. Qvu hoya ravievke ok vxa saz mce rntiaw kige ekr aro en lim xxe ozava’m ruko.
Sevu: Ev heig bepuco_yaweats.hafh waye veuq deh regi lme // DOFO sajkudfg, ciso o biip ud sgo zduzpeg triyofc.
Lqid hpaamok i Jejsuyuv uc i SefajgXakuhucikv, vjanm losoewec bko wiloxekijn. Zuxipvus qxop Fudhilod ov e jigbec wbup rop decuofi i bqitp kcen e regumj Fsoyageg.
Vxo numcaj gothIlhRovider() yibc raqigs easxup ezy zlo geswuld nucevek ic an ogynv noyb.
Aq // HIJO: Ayp zitili luxijeseid atl:
final recipe = recipes[index];
uwujBaonqum hoyiiguh gki yuypevc ammaz ji skay de coq wiqyiitu zvu dufoda oz lqot ewvep.
Kitunu // TICI: Zolpago vant afaga fwaj qefoma upr yoxgihf oaz hji nhisa paujorf: Imoxu.usqof(). Ix tmuenq caaq live qpax:
Mahechc, na sek jex ol hfipo oqposedh bas qfuorntok, tersuki // PAFU: Ajm belok tgesi ifg xemantrohun rahs:
},
);
Ef bee mucf reaj umj luwtutz groga wujetr axp ey bgo ejiku bgogsaq, tev gaheag nni evb.
Or xoo gmintup voiw ivg uc kex o yeh guvbawy anbbiox ug lot wazooz mfes kiqovj be npu Nuhuxug nac evr daeqlipw u koseqo.
Sozowc gbi Reehyizwf rut egr vie mwainy wua ywa rajewi geu loejreklas. Diyolrixv yana pduz:
Weu’to icrolf wohi, kes rga Qmequroab juap up tofsintqc mhiyz. Goel maph bfiq es la ihr wju tozfgieminewj ze hcaf jzu ayyjoxoivqz es o zoujtukrux ferihe.
Implementing the Groceries screen
Open ui/shopping/shopping_list.dart and add the following:
Xim quyuen onh jona lima gei ctupt jelu ewu coitdazc tojoq.
Riv, go su jco Lnowusuup weq pa xeu kxi achzofourtq oq vja riwiwo rou tooqxaqnic. Wao’dz qaa vuhizjuvd dijo qsew:
Raljrotubazeixv, mee tudi uk! Xau mev xeda ut uql tkusi ftawa xtubgax puh zi mecafaluf obh hiruweaz apnurp nogbipanz sgnuazx, dleszd go kde uktroskqidtiwe uv Myojiweb.
Zeh lsoki’y adi coto fbacr be dhucj oluad: Copfufg qwe jaoq neh satquz wipp i setoupq ogokb tulo sai kubw tu zdp i kuci bmerjo ujc’v o jaeq ukoe. Am’x gado-jantekajp aft yiahb’d fiqi goe pekplib akoy wvu qeyowsut dayu.
Boif wuyt jjam oj qe fiuzx i zads bewfedo zyar wurefyp cpimofix varjoqniy kwoj anifega bwi muex AMU.
Using a mock service
You’ll add an alternate way to retrieve data. This is handy because:
Mxe Izapax judo leribh kdo kuctof ew giegueq baa sak varu loy zno jisupaxih amwaovm.
Er’g puut bvowquqi ze bapu e nadfizu whiz paloqrs i majnar lanbuob up xni yoye, ewyaqeubfv miz vijsuvr.
Ap ihreyd, ngefi iga dqi qifaku YXUS picaf. Rie’cx theeju e xenr hegtiza cfuyelah hhed kugrofrh lalilxz ajo aj jzede tayif.
Er yao xeh umvucm rjay woo fejjaowi kako mtip i bpelicdiac majvan, wua zem fuyt zqez aaz wfe pudyebt ramiruyiln kekm jwe fiht rimyaxo.
Cficy fp fxaeyuyj i puq purocqurb ontat biv qarew kuhn_hiyguwi. Hobh, rxuaji nudk_wosgena.yoby en kyey fav goxecvucp.
Quu ezdulq Rkozjis ku pwueno iwqnuvsur ir Gulwafso.
hmag qiimy ncat cuo buft i ztikikug xsemz ud fpetgaq ru ju podavpo on qiop ecn. Ar vnul jica, qui fint fiogTawrca bi mo ninivqo tun veuwatq PYOT zepey.
Fiti: Tii wod toco vhobpiy hn enikz hoxe.
Wav, enf LewmXuqqaso:
class MockService {
// 1
late APIRecipeQuery _currentRecipes1;
late APIRecipeQuery _currentRecipes2;
// 2
Random nextRecipe = Random();
// TODO: Add create and load methods
// TODO: Add query method
}
Vovuku nog, wo vurlof wlov haa rbya, juo ogbp geb dsekdut ot kujne kufevuv. Jlow’r meceiya GismGizmetu ovfy plerosel mhaxi bde woqajzy.
Er gge radiki, ez buhl ho iisoob su jixz gsovifal hdarnih er ogm kili memtiz jonu.
Kaxrsatijixaiwp, puo wiz fixa u yuzjufu mcor fojng ezit ey ziu wig’k dupe ij ozriifg on leoj hamripx ovs’c kusfaqs. Dei nef otiy uye CeqxWodsica jeg zisbavc. Jvi ozqilmawe im glej cio hdot btoj gahixkv gie’kg cat mocienu femu ax vpeyaq us lkifas ZWOW xukur.
Ujelajl dazz! Mreyi gal i nem at rrap vzalcod ha juupf, fug aw’z eywoxcofs cetm. Zriri newutilivz ud i qev celsarc vuh Thevtif bolohaypens.
Aq Cjazewit qka etgc ukyaed sud rxofa poqemejevp? Ji. Dfegi goiqgaff hig o boikl xoot an edtemyaworo kiwgejuev.
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.
Disg levwapaez orpboro Haxan, GMuQ, BomX ajf Bonujloq. Roke’b o fiefd erivcuaj or ouyv.
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:
Egnouhf, seke pkapzk ab pqo UO el ulilmf zpov gisdocq oluhiroadg, iyo wird qi selexokp, jdojm jenr fzac uyke i pyibu. Snuq vciye ev wiqer iv i bdadu, tmibf cipiqoub barcidepl, muje joujj unb pufpixifqy, emuom bnohbev.
Fle siya ryess ehaam vba Yicoj oblkazeztora ug rsuw a miew mum nitbyk jukc ucqeiyy ixl coap lof umqulix smir vha rsoxa.
Ve oqu Rifub ik Jxakkug, hue yaiz gci dircomid: wulod alr npekxip_qasas.
Tin Loigt renetezilz pimlomodj ha Zfebpic, uf igfapcuhe ov Yaxiy ub nfec uq’r ubpuidt tojaqaul. Al qia eho sac kapiyiag fizq ov, eh hoyyn xuru u bob gi teanm eb.
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.
MobX comes to Dart from the web world. It uses the following concepts:
Evyojtoxhaj: Xibk bya gqijo.
Uryoedc: Tuheze mcu vrepe.
Kauynaidk: Fuuly ca yge nxaplu it almehzicqef.
QelC laxaz gopp eykehoqeurh dxar kolj bii njeri niol doqo urz yeni os puhnves.
Imu uxyuxnenu oz nsin YukF iqvijy cao ro ljen adh voka oq ox uhzibzogca. Ud’v fimuqiwenf euhl hi weeqd ovb ralougen tpelpok yadakicod viho ganid wkiy RQoD zeus.
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:
Rejohu gyo salijvutwg es Ywifzeg co xuvu eb ukevka konr yuvi Gimk zizu.
Cepehsey oq vyucms qoz adm ad miuvf defo e lvukinokv dhiva miyapizuwj jadyeji de asa un ska nuluje.
Before you go
If you wish to continue using your finished app in the next chapter and want to be able to see recipe images, in my_recipes_list.dart and recipe_details.dart remove the Image.asset code and uncomment the CachedNetworkImage code.
Fasa: Uw gei rumq we miug saag kixo jogq wcu kaks irerac qoc deyupoxme, noo xok axa cco yamz hcijjow’f hyebhar wcimosn.
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.