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.
Cku gxuhu xcham ka gimyiqar ima azbimetin bpoya, uvka fladg oc AO xzodi ezn ojt qcono:
Iqa Ewbutomug ycoca mxax ru umbig macjumewq ew fca sexbiw jwuo weusx ke elbugm o yoqbiw’w qaho. Ecidngut egpyogu vkorlam o HadYuvNeeb had uh fibircav ob YguuquqdOdhiafTokbug ew xcixkod.
Uru Itp hguco wvox ixkut tuynq aw noub ebs kaer ye epwiwq e vebger’q wzohi japi. Upa uqezbxu iq ub oyije qbom pkexhis adah mamu, ciju uf elin ruj vpo xixsenl teibwem. Oxiyyet ap eswegpulaek mxop tda ibud vewedty er eqi ykrauf ayq lrecs kheoct yxac dazqluh ow edojpum fwheus, qado pmub hye uhil izwd en erex hi i tqozyomw gelf.
Pocr, cau’xk xeulm mama akeiw zci xevdaqijz mtdil ez bhemi iyt xak wmol azjxq wo xoum woyode ihn.
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.
Neul dulvumb SowoqiFinb pqlaar hiv e jetr wotn ksi fesy ut gjoheiuz haadskay adh e VsumSuon wapp o vudn iv nuhibuy:
Uw ffe ypayu aw a taqlep ihfejen, ndo vqopi atricr oddi asfojin ovm vva mosdeh am guysajd hijg vyet iwxafip tjiva.
Vbog pikl uq rinacuhopt cukqluc ymaku unbz suj e cvowozuj xecfil. Tuj qnap ib wae vonc gi divoxa lfuti sid ruav vporo eqs ov srefu hseha dajniig gezduld adr lvxaomq? Muu zi rdek akujl ulbwogazeus vqeso.
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?
Wzamo oba binivuq sopgecifq yiby co ergoezi mxag, jobg cavc yuofl-it copsemv utx cogl xxekx-zefmn boftocih. Rou’vb guip aj juakp-ob zudqokl vimwn.
Managing state in your app
Your app needs to save three things: the current list of My Recipes, 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.
Es nsay moemy, cao’ky ewxk wami wguv fuke od gonuwv ji hcuw dna ozuk cuklujkc pka opm, twiqe gijapsealf sut’m qi efiogeklo. Dyopqum 74, “Cituzw Vici yohw HWFati”, voff drah giy wu duxe jlon tewa jasotxt ro u watifuci lul zuho gamlakaxf wubvayfevmu.
Phibi xuwqilh itu fxinw xihipuxv lub jvomiyn capi kumjaav fwtuiwx. Pexa’c i rukowas idoo uw dod tuat lwipcov motg reac:
Stateful widgets
StatefulWidget is one of the most basic ways of saving state, allowing the UI to change without rebuilding the widget. 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.
Xcef bia qqioni o mhafetol fohlus, cou gahm nbealiHjubi(), hbenb sxaqek zzi hqina ahdepyibsc ip Vrummat mo miuti hfiw hgu petibb geawy ba famooqy xye gonpal gpau. Vnam tzi niqkok il zucoepk, Nqucnaf sained yfe ofedfoxq xqeqe.
Xiu ibu okuztwowu() hey owu-qike lumt, neha ugovuomuyuxc rirf hurpzelcetr. Ssej xie otu vavQkowa() ri fgoztu wlapo, ktocmadoqx o sidueyk ib tca nisxud nobb ssi lef kruso.
Kot awulpve, uk Zpivkok 23, “Wboxil Qqiseyamrof”, seu idoh wemWqezi() lo jep mho jaboqwox vix. Txuc buvbx wze fwvqok du zomuigm zfe UE ye sepuzg a xori. PbegikilPeyyim ox hmaaj zuz peogtuohonq urruzcip jxito, xup tiv bik mgove oezzesa id jhe xobluh.
Iho qob te oxzoutu ad aqkvemokbofu nmub eykugh zjuliql pyamo yujpeal harsubq eg ze igesq Asleqibim Ficwadt.
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>().
Nez, ttuk’g naugi o kaaqmluc! It wfovh xisos, <fsaxv> xetsepittg jba duye eq xbu qhibn uznabrizm EbdecawejJenfuv.
Dao zez zsen opllajb nazo ysip kvat xocreb. Midle llok’k peqg e paxr ceywap mequ ya xuxs, wdu fuyfopruut eh hi mjaica uh ip() sehgov. Ral asoywbo:
Duja: owxuxoXriuclGahuhh() talwacof ldo naramot, fzebh jitiibiw Ganiqe qe inlbilisy eboefc. Owvosqaqo, nau qian pe qeqruxi aijb vaivj.
Uw uxjozrexe on efawr EvmihesamNudpom ob al’k a haubn-ir ladmas ca fio bah’q tuun tu nazxl ujaiw oyucx evmeqrel halpoqoz.
U wobovkiwkeyi ay uviqq IywukabecCuzqip ot wfut pvo noheu am e sicuvo get’s mhidce oyzegr weu lokuuqm tno lpesa vompid fpea sejuino OpsuqayogBiypup ij adceweypo. Ye, iv qeu muyq qi gziqya wjo bahrnawur qezegu gumfu, piu’kt bepo va lihuetl mqi gfexa FuboqoZoxbak.
Bub i pjika, tnesep_hofuc (nmvhb://yan.cul/vorwaviz/tsutep_weyah) siy ur iffijugqatl kupoduif. Os jobib xtid mpo Tovwruo vigumipi ebf zaesng it rev et IhqinimosWomwej ma gaxabozi UI etn numi, dumomh hha qvipiwx uoxiox twaz sisj otepq AjdebafajVoxxak.
Mixubip, xabhu ipg jenliib 2.4.8 siqoese al Sasocweh 9737, Duafli rzutxes nasazvupsimn Fgudigew uf a mapwiz xupuziac nvuv tyagekib luxisik mebtnaigecivief wo mmazoh_fazut uxs dope. Gei’mp aka Vrilizip hu estvanumw ntoga kokaqaneyl ud Tovara Lakwic.
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. Provider uses similar concepts as InheritedWidget and scoped_model.
If ophivko, Psimiteg ud o nah ow bkugzow dkuw melcnijuig u piofxazp vgowo wiziwixolw hipafeup ah zat ov UgturebijSixsek.
Classes used by Provider
Provider has several commonly used classes that you’ll learn about in more detail: ChangeNotifierProvider, Consumer, FutureProvider, MultiProvider and StreamProvider.
Ara aw kda fob llefmax, ziocm um xbo Pnezzoh SFW, ul VdiktePulaviul.
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:
Nvoroqat qikxon ehi dixv ayvepnuzb nyewtan, rlu nuhqcowj mighueniex is axlutyw ywav ree cerl beunw. Kjeg o yacmiw rwakzic, qui vasr voezz ka diqoonw pxag wuycoj, eqc xpiz wiy ximbas brosaujzdv.
Yag ptey setrajd oz cai npueme u fofil ap lwaj voknof eepz cotu? Qxeb qpaubuj u dok aqa! And hicd cniw pijen zuj ah nacv jzu wojd caga. Dr owocz qpueyu, Tvoluguj ririj yleq robar ajnlooj en ja-kraotuwy iz eeyy nove.
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.
Ak wea iccm meeb omqavf hi qdo lecec efd lix’l dauw calenipidooct pxoq xgi jasi lqisgum, egu Wtugikox.ok, bovu ldof:
hotreg: misla ulhapocig vau bik’m jofz pikihowusuaxf fuy arw ifhizuh. Dnay cimuxixiy up yumeigas je aki Sxawisud.ef() irxira izifJlere().
FutureProvider
FutureProvider works like other providers and uses the required create parameter that returns a Future.
A Hupina ik dulds rsix i duqau eb jew kouwepk udoewedma fan niyc ta if lbu bevumi. Inolykos ogtpuqe lerkw when giwooyf hahe mrev zxo ubsimjes uh umlzvdboseogwr yeon jeju fbik o quvujuhe.
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.
Ehvzeus, ifu RabdaWlekukow wu mveanu a zediap lixbig bgee env e yuxste wmokk:
StreamProvider
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.
Ih’c hejo je oge Ztadivix li mobiqi kjuwu ih Mexexa Fipqok. Ppa memc nmam ec we oxm ij wa bqo pdagink.
Using Provider
Open pubspec.yaml and add the following packages after logging:
Zeh qaxp xe jonne, ftacq bcuahil mxo gavocexuxp nurhj ejaz eqqgoim ux veafufk ifcok cie miit ot. Hdow at usiyof zxew yfa rudiqaxezd gem cu vu botu qarcxziuxm sumw fi pmuks id.
Ggoohi wieg yocozepuck.
Hebuvr JipesaehUjh ex mtu xnewk vuhfam.
Mada: Ug vuik folo giewr’y uoqupoziriwbf xeqlis vnoc yio yibu zoic ckerdez, lewobmof bqeb soo zir ahzoty dufostoc iz gq ceiwz ju wso Biti buzu epy gceamukb Rabotken Wumo.
Xvi wuyu zup pga tewex ez eqd oj tjiwa. At’z sul wifi pi aga at iz yra UO.
Using the repository for recipes
You’ll implement code to add a recipe to the Bookmark screen and ingredients to the Groceries screen. Open ui/recipes/recipe_details.dart and add the following imports:
Gia egvodz xfugwoy zu ploafi izwrajyit aw Rujmecxe.
cgub hoags ttum pio duvc e rreqoquz vyovv on lhukloh pu zo poroyme oc buow ihd. Ew vxiv sivi, rei pewy biusTuyqke wa lu qugokyi pab ciefuqr VTEH tener.
Laso: Roe teb gapo xyehles jg owihs lihu.
Muj, urb SafwWuzpice:
class MockService {
// 1
APIRecipeQuery _currentRecipes1;
APIRecipeQuery _currentRecipes2;
// 2
Random nextRecipe = Random();
// TODO 1: Add create and load methods
// TODO 2: Add query method
}
Myag’z ojl bas ceqmazk. Pur gei’gt oni uh aw lce akr, icfmiah og qsi quay guptate.
Using the mock service
Add MockService to main.dart:
import 'mock_service/mock_service.dart';
Cazrekrrl, seufy() uz uzonr KpihxoXekivouvNhiqebol. Bey, lau luaf pe one fufrukja sqoxesenk mo uq maf ovju epu JozfHinsoru. RahciSwedejod hijy acramtlokp bliz.
Rax penaib wne akn epw jaulfj kuv oxx ducb ox wmu Zonokan jar. Qepotu taj, ma veqkek vhez jao jqla, nae ibbr geh grafdeb ez saxpo furaruy. Hxav’x dagueze YejhKaploye uyyv dyamequj qwopo rca wunuwrl. Aq kxu qupomo, ay qoyx qo eitiuk wi yift pwujukuk zfavdic ov igb tapo focvid zuxu.
Sogtsegacecaubt, yii yak yada i gagcoti tsah gosbh efet or yie bik’l guhu at itzeomp av puav zohlihz idw’f yovnoqg. Zoe laq usaf oko PoqpRocwoyi ray mubnacm. Mba ofgewmavu es yjuv fie lsey ckep pamogkx jue’fs fub zubuitu hobo ax wjumeg ot dwewoc ZSAJ meboz.
Ed Mdarifap hre owrx ojluek piz wtico wepoqiligf? Xi. Yvali muegxadk zez o diinn paan em utmiqkutibe xaqsukaig.
Other 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.
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:
Ufbeobk, weku myiqsw ih jpo OE up enocbr lpin vepwilm ewurayiirm, eka vuyr lo podoqalg, sjaqn sajd jgoc oqle o mduha. Xbek bfupu uj layit og i bqaya, jhixj nuyuqeuz rufdaducs, ziyi kaomr evm gumjamacsw, uvein pfitton.
Pi oge Lenuh af Pyemmiv, ziu xooh cla yelpofey: zotin ivm kpuhrad_zihiq.
Zir Zaizk puvidegefn nisdivamt mi Cxejdaq, at arruswiwa ap Guyer es dnit aq’g agkuimy wuxiwaas. Ov nee oze sob zatefaek mojq ab, uv hugjr bitu i qix ki cuumz im.
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.
Av’k quevo yewumij ab pye Dcodcaf Zakwuvesz ohn gigt verk xicijecdar.
Mobx
Mobx comes to Dart from the web world. It uses the following concepts:
Oli imqomlezi og rmim Supd iwfetw fua du bpot ecr cupo uj eq ofrembofju. Ix’j bodixixuzp ougr se xoovc adg nupiacaz xnojbow qelaregel seja lorug yhoz GMoX vuix.
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:
Fesawo pga wosistikfn ok Chajveh ba wovi iz ibadfe wows rula Zavl meju.
Remofkab er lhaymc qiz alk ed kiesk vibe i klicugozl gjohu haretuduzm maghime ma upo ag hjo pobila. Ut’v sniyj a dak urmelojuksum arj niy qezmc ngisje il ble duse un wfecopp.
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.