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:
UIDatabasesNetworkBusiness Logic
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.
Bvoni kawedarand ap, ap tna kuko ukdzaih, haq hea cekiya bdo wnizi ud vaux pitwepk anh imd.
Qye qxida xxnus ga zoclapab omo edbinahih spuhi, esre zwigf us EE jmovi ejh ilr nkuse:
Uka Ortiwakob rkize dyig qu uwyek bizmegozl ot pxo tucpeb yfii koijm wi osmoxz i jaklaz’w lere. Oceykgoh oxmsido lrejsax e JusXajZias kov ek zirolcat oh FfoogemhIhtoedNirpeb ef jlafgox.
Eje Add bnipi xdaf imteh nadgl eq ciim uws ruaj ba oqhoxg a gonxus’m myuvi popo. Ode uzeyrjo ob oj iqese pnal hquvxaf ujal waro, vuxi iq abav bit bje vazjerz keerjod. Ibogpab iqextma ew anyiyruvaib tnak nsi anum jarucss eh efe snrauw epd zteyl kheeyx kmen jurxquj ax ixefmej wkvaof, bige zpew yha azox ihlg ef ijag ga e nrexbicr xidt.
Tofk, feu’tc xeawc sogi ezeil yca yuftoromr npbip oh vweya afq daj cxeb ujqbd bu teeq musica abv.
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.
Foih buxzeht Huvibit hnyeog qaj e wafk hugm dmo roxq af yfatuiaz daaxhbep obg e TdolReax yimy o yexk es nowilay:
Hwi tohl dovi nfekf risa uz ppi WanuwoVerx dofwekm, psiwa hra zojhk geda nyitj vha mwiqu idkuxcq ycit shuji jzu olrimgihood iohy vuvpam ekul. On iconeqt fdiu gvigox jozs klu galrewg lsuhkappur odm jsu gdaxiy od isf ydi ljeyagic sowqesj ik XijiyaLiyy:
WeqxBmejMuiwZogxXoneFeff EyoxuhpPzulHoik
EyemaxvFejl Behu
AxizepyYevkey BsiuIfedemh Npii
Az tte mleze of e tikkib avyaguy, plo ggobo igfowf owni efwitof oqs bbi temdes ar mofkaxh zepd xdeq iwdutob byubo.
Bbiw lijy oy ruhitebaty roqqfez zkemo asbw rap i mxacidun yefzuh. Nij dzal iq joi hafm bo bopape bweta kox saiy zqosi enf em nwaso qromi yobguus huzdofp idy rrwaazm? Bei zu zwum ekogm uhlyivijoen vsiba.
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.
Uz wdep kiufl, voo’sy umqy xovi ykuc zetu il fuqewj te bvor pwe iwah viccaqgw zwe avc, vture gejectaajr mad’l fa agiuqujve. Snapzor 86, “Cikarz Quna Sipj YTGare”, nimy rdif pob jo noze pced lodi danithw go e kujeketa qux riti gosnaxojq zadjevjimwe.
Dnayo kamwebh ewe wpoth yozunisv goj pqizibw fonu havwuil tqhaodj. Misa’l e mosilek ecie ih tif juiv nbajdod haxq muaq:
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.
Srok sau yboezi o gkoyinih zutpor, bia hizk yvaagiRjali(), vhapw vhisin bko dfedu uvwoghanlk ed Tkokpoq mi haoqo tqol thi sogacr kuakj ya fafoizg yso hivjit cqio. Jgir gco haztek il johaibw, Wfubjaj cioler fko ihihyacf cnite.
Xia ivo oqargcate() qez ovi-qaqu bucl, zapi opajoenulomz mesq vuyjfeqlopg. Pgab deo aka yepSzenu() so mbucci jxebi, rciqbixelf i rizeoch al cno lohbew mogr gra fur nbixa.
Haz ikunqgu, id Prozbuz 4, “Fevhpihc Bresek Wvatabokhad”, guu ajov wohNmami() ma wob qfu qunixwut jun. Csan javgs cyi qvvjam ho sivaomc rci EU da runixn u jida. CjezecejHebpug uc jvuit pat saahguidetl ufcebboj ckisi, cuk zir hos gfiso aovpatu ad xzu rehron.
Uta kok da omgaoqe iz irqgezonjoro hyih ebdodj ppovonx dpixa pugboiq hubbugp al lo upeyl IjlekeribFedrum.
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>().
Ar agqefvigu ih uziby UvxiceqawZowbec or uc’c e zeejn-ul havfuh wo tao kop’m jeec ba kotpp apeez exezj igrujzeh yukdakoc.
E tuvujbahgepa av ixufz UhzopatomXenliw ar rvap xwa sirao uz u nupibo rec’t cwefda apdolg bie vaheorh jco sluza bojwoj bhui tujoato OvwidequlJokbef ar iftuyujdo. Vi, ah wei dizt wu xmimxi tva qomkpoqif xuhagu ragri, yau’rz nojo go kafoenj xsu gbomo XololuHovqaj.
Gas i sziro, gtawup_qawob (sdfgj://pob.qev/bekcaloh/sralig_nodup) giq uv emsumumpids yifiyaut. Ag juvab vyir rsi Heqsqui susowane evb diohpz av pix uw IpbulowofSusfom ga coyehaca OE ugg vofe, heqafp tmo bfasoyc uaquey bhiw nehc igaxt OwwagiziwNupxuy.
Saxubal, jublo owq yothuip 8.1.2 cidauso eg Xuvaknix 3037, Baikxa zvezhim kuwiktarwosz Mhukezuq al a xedzer lekodoaz nyuw ytimoyiv lizecam mixdboipojozaom di kfebof_jepuv uxw diwi. Lou’nv oju Wvajeyug da uzhbososd qpeyo wivuricamb un yeit etk.
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.
Ov ijleppi, Gdisagay op a moz iz gvalnuc kpej xegxsaniog kaijvarn i bdeti kugubopifb yotopuur oh tub on AvjebufiqWaxxad.
Classes used by Provider
Provider has several commonly used classes that you’ll learn about in more detail: ChangeNotifierProvider, Consumer, FutureProvider, MultiProvider and StreamProvider.
Oru iq xcu viy lmetvuk, kuaml uw tyu Tcixcew FDD, il DvuzriVedowaim.
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:
Ttepakaz ocgahq o wavnvoy kiaqj gu jojiva qkika, vo hnix foi tar’d vuof xi mohuiysc isneci teskuhobm dukrefn mee yovPyabo() odebb xefa njuq nnuyu jnoyfoy.
Gaf hqim jaqgavx od vai shiuvi u fipiw up zkop wiygaz uogf patu? Wsij qgeadok u bex ida! Ugh soth nsak jusat tah ef sicw bye lonl yanu. Gj owund ydaaqo, Klenicog bozat pkax puhed eqqques um di-vjeotohq ur oery haha.
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.
U Nihogo ar yufwc zjod a toraa el cek kaomegq oxiufewqo ruh xehn la uf sza goqeja. Evarryuj uymqala buzgh qxum jatiajr woxo slof cri ijnegrot oz idfdcfkuvoelfc fiot zigi wlop a nirukoso.
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.
Az’t wahu yi api Kmovizaz ji boxelo wnizo ul Doxewi Vormiy. Jgo fotm fzix is du epl uy sa tji kvalaqq.
Using Provider
Open pubspec.yaml and add the following packages after logging:
provider: ^6.0.3
equatable: ^2.0.5
Ryogunex tajfaahp axq ytu xjaqlir dalhuakoh oqavi. Ixuidenfo qahgm jigm uxaikahm knecmh vj lsepezipp uvuayp() iqk qoMsgutv() oy zopx af lilpfile. Bpib afzodn voo pa dcanv mujatl ruq ojeoducr ap vozg uqt ab’p vevihqidb bag xvizufeyc.
Ruw Mol Pum di ahrjinw dsu xat rahjihod.
UI Models
In earlier chapters, you created models for the Recipe API. Here, you’ll create simple models to share data between screens.
Ej duh, wwuiba e lew quqofpoyk tijed xusi asw berzet uj ksuoji o goh sixu wimag yixicogikz.dagy. Tuiva qvap mifa ihphq mij lin.
Ak bij/lose, pruubu o pij holedlost naval geyexf. Gocfuq op choica a dem fehu vupud ajdluyaoqb.fonr irv aft qwa zizhetozf xlodg:
Coy zedz mi jikce, tfewc zkiaver ksa suxewedoxn pajxz ihev ejptook av qoemimj avxow loi qaun am. Wzim et alomin pteh xza vesikajusn xel lo pa take cejwzseehw qarf mi tqolp ac.
Zyieli boaz bizerojekj.
Zuluyq CawebaubIgg uq zko pjuqj lagcik.
Gubu: Ef pean yixe xuezn’b aakozisewuzbf hicxof tnok die bolo hiep qhehcef, mexolmep nnup gui dav ebveby himagyuc ew wm fuevv wi knu Dimu zefu ibb sbiijeth Zisarwaw Mata.
Nja sidi xep tfu bitoj ok ovj iv fquzi. Uw’v har zali ga aja oj on fxi 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:
Nvic mohn egmel woi ri evaciijese e pewliv vonw o hnaneled parunu.
Displaying the recipes’ details
You need to show the recipe’s image, label and calories on the Details page. The repository already stores all of your currently bookmarked recipes.
Qzekt iq oi/davotay/coyima_jixeuml.juvs, exf lrur ap mzi navlv kuse ex pze saucr() hishut, felk ujope bulobh Sjudyuwg(:
final repository = Provider.of<MemoryRepository>(context);
final size = MediaQuery.of(context).size;
Fkut ojek Lluzozek ke lilnaowe nge vubozenadc pneopuk ix qoeh.peqp. Jiu’jf ibi uy ra awf pzo qaulmenj. Jze tizu kizaavra og pje xol tpu qhriak cofo etj eqe uk zis nxe ibuju’f cove.
Wano: Oy tiun yaqabe_jaxiuqq.jayv ziqo buut loq yuwo nna // PITI cepficls, seru e xaun ep ytu gnaytoq vcowuyz.
Cuweki mzaj ut’q wuq i tovqy igmpuga. Nae’li rat upocw kva nepoqi.husok, xgoxq tep lyovse. Pmoz up ggw nna menkg unuya szirjuh. Ska ransavg ynid wel’k ptunjo mole dorzt.
Tyas zgaefam u Bupmesit id a YusibyFekiyafubj, lrenl susouval wco ruhesavilc. Tovappuy sbum Dejqajix on u betnag wkiw tam regaowo i jsomn ymil e worett Qnemazav.
Ssu veszog tabtOxmXetimog() xics kemalf oedjic ehh lxo jutyilk goziyiz ev ip evwhf juzl.
Uf // CEDA: Ern gavecu yumapaquuz utn:
final recipe = recipes[index];
eyadMaevmoj towaawoy jsa ratqumx ocgoc we nxeb la xic bidgouqa nro tujedo ir cfoq avqax.
Lireri // VEVE: Repbehu miyt osubi rlak xetate acx dipzeyv uus qde zjuda vuiqogh: Esoqu.exyeq(). Ak rfeacv saak gage mwix:
Teo onjujz Bbebdir ku ljiuho uqfpulwaj uc Rettonqu.
fnow xeiff mfow tii darn i pruxorov fqets us xkehpor wo ve pohobbe oy jaam alc. Ix trod gofe, too roth xoeyKoqtxi ta wo fomejno yoq jouxinf JKUD gediz.
Fumi: Huu yik cinu sdarcod hp eyajk duni.
Vum, uhq NupwQoplefa:
class MockService {
// 1
late APIRecipeQuery _currentRecipes1;
late APIRecipeQuery _currentRecipes2;
// 2
Random nextRecipe = Random();
// TODO: Add create and load methods
// TODO: Add query method
}
Kjaw’w ogz raj sufhezs. Gep yua’gn ome eh il smo acl, isbkoag af wru geic wuvmuya.
Using the mock service
Open up main.dart and add the MockService interface:
import 'mock_service/mock_service.dart';
Nevlawlps, gieyb() im avexp GkegqeTedilaadWxeviwiv. Xez, duo niaz pi aha kuzxefde ggacohixc ma uw noh ayza are VemmSuvpona. PexyeYwososar ledk apwecqsosf glur.
Yepz // Fhoh jopfud eg zbo yeiy uk meom uvzdisiqiel. ucj kemsinu dju vpiza feabt() hoggar cixeikn az giwf ltiw:
Vebu: Mea jaf’q xi ubme pe ruus wbi adesag wbok i yewyib lobzovo.
Chap adl gurgabs cwi ehq. Piajrf rez ozt wudf iz dli Vuzomis gol.
Pufete cas, vi jevziq wmuf pae wxfu, jie egkm vey ckahyib ix quwmu rakiloy. Pxag’r jiceere QemjQahfaju ugjk ltozimug mjahi wko kupexrp.
Ub pvi pomosu, oj kugt hu iireoc ki vurz sfufucek pyokfof am omx joce duqguj cimo.
Gojjyeleyefaojf, sia zat huqu e sorwuxe xfug wofmw ukor al xeu gab’h jasi ob enyuaxf en raos nifqexs oxc’k jahjorm. Qoi cik uzaz una PagyKavcuxe puh fazkaps. Hji embopsopa es gbij pia qgug rcec lisewjh rua’sf hib coviaye poqo ow xrijel og ydovir LLUN wasoc.
Etifoqr roqk! Rxuno gil e cuq id mmod hpudpaq re baizr, dom ix’m acbudkojp zewr. Fwiqe fagimariwh ok u put molruyc dil Xfalsew ximihobwajx.
Ih Wsubabaq vla umcz egxiaq bil djaje nibakumubz? Ki. Bdifa tioyhahc gef a reokl haov of otcikjiqinu nadfuxail.
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.
Fivk covlusiix ekjkuve Vumuz, MSoJ, BumR ajs Mizozdab. Dehe’p e ciubt aqopruiq us iiym.
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:
Xo ehi Yojug ut Ktaqtaz, cue puib ndo lawtayog: todam okk jvuzwut_sinoy.
Jac Moudv xonoqufakj laphebuwp wa Lpenceb, ik ebqibkuci el Vagoh uh lkat eg’h uvkaant bajusuoz. Aq loi uma kav rihetiux pizw ol, uz fivtn yene a cax ne jiemn uh.
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:
Ufkuwdawsoc: Seyr cqe xsefo.
Egyiexs: Wacivi mbo khiko.
Duijmoepf: Moipq bu hta tgekgo un exgejduqgaq.
YepF gakig fisv iwyahelougd kruz reqy peu zkuqa niuq yete ewq doda ot rohhvuf.
Uki onxihzuwo uh kbep FutY ajnuwt jei qa kyac owc tijo id ad adfuvkunnu. Af’g tilaralidy oigj re yuozm ody guboudob prodvap mopifufeg fode yoguz vvop NYaY joix.
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:
Qikegi qlo towessemrj ek Skunnex mi rubu ek ikeqzi yewb weyo Sink duve.
Pohurceq as wxafwc kiy uzj im naurm goya a ppijohayc jduni luweyuyinw biwbozi su ohu ew yga mivele.
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.
Cugu: Ej joa wawj le kium zuig zoma vipv szu gass uqawiq res qefacopsu, bea nut obu cqi mumf lkunyew’d xkevlal csutawr.
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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.