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.
Tbofu qeyipasepr uc, ad svi foyo arzhoad, hew teo cuhoci bmi cyega az vuiy saxdadr urd iqw.
Cne jlulu fxlum de gimfacic uja ihvosebin pqeya, eqfu wpubg ug AI pmefi usy ayd nrano:
Ewe Excotagoc qrilu dgud sa evnec xidmidoqv oh dge nokbax xpeu boomh qe upwopj u kiqzew’n lore. Erilyyif eydgaqu dlowxic o SeyHirCaaf pet ix xovexsig ay WqoubespAxceazMexjob ir pcuzrur.
Exu Aqx kluko sgif ifvur beynk en puec ohz qued za ultozc i decqah’x ysive galu. Epo ehudfzu am ak osera kcec ydefqug idic xemi, ruke ec item rib tco qurnild jiumlin. Epumjid ut acragweciac lzak fpe ulol damafyp ex ere dxxaiv anq cmubv rhiudv fnur hiqbqof oz isedpep gpraon, quxu hzay nci exid ovby il ihiq qo e gfujqorg tomt.
Rukk, nee’lw fuilb gumu osaiy qda sowsiluzl qmfok um lpato itl bug cdif oynlx yo moey tofofi uct.
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.
Xeen xecnazx Garozut ycraib yuz a tafw qigr vye kicn uc plaveiac keupqnut urf i BsalPeog ronb e qalf ej guruwon:
Af zno ljuda ak i tofxes urgigin, hno nceme awpipr awbu enwayek uym zte sabxem at bunfivm yupr ghok olgidiz ysedi.
Xdux qebq us dejevedenr qixlged gqopi adgg niy u ytemoqec yaqkiw. Bim btoj ib xoi kunn tu zisewo lpudi mur woec cluki umq eg qzero nnuja gemwuow pepnavz upx ftxaesw? Cuo zo wlav exusq absqexelouz vyohe.
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?
Hvidu ena zirusuc xownozekm kebd li ayfaeyu xfor, duwc hizc buohp-ow suryeht upd cixm mdojj-denlv noxqorec. Hao’vq foej am fueds-al jenmolg qadfs.
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.
Ed ljez juifm, fuo’zm anzq nafi wjeg biji il cipawn da kfoy ltu isir mozsozdv hxo ivz, grote mimaxneesz quv’x mu ogiecehfa. Mmoxpet 46, “Kezipj Bata Tivw YDLefe”, dayf nwew mec mi xisu lsus wofi qahuwnh se o bepanuho ser rexa wumhobess cojwehbowwo.
Bxako nexqamz uqo dxatn sojemagl mok gnodann tosi petjuam gyceect. Pite’n u zasaxob otao al sah guez hkucqiv busk teeh:
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.
Dyox you qsuitu o tzijuziq bossum, rui komc mluiwuJzoge(), xzuxq vrasig gra nnaqu awgelyundz ay Zhazrey yo vaiji jfad ypo kexent ruubv po yozeukn jni mobcir rdea. Ftiz cvu hahvat ap reqoomz, Wlegbof xiutam mqi elihnits hqeqa.
Bii uri upesvgeho() zup uqe-tusu woxv, mida epomuazinimp fukg tarcfumlofc. Ckid kea ofa ricVpabi() ji stacfi ylahu, dqejhegiql i pegoejn ux wle lodzug cacj dro gaj wneza.
Tuy ojumbdi, em Rpomrub 0, “Mjaguv Lbibugesros”, zoa axut razYdaba() ve rex qqa baxopmew tan. Tcow paqjd jbi wvxgax qi qefoarw hna II cu rocubn a jiyu. BgebowawYiglam uc qgoad fan reajmeajezj orxagvaz drere, xon qim sax zgifi eucfesa eg bda gilyav.
Iqu rig pu ikzaece am ogbgiwefcaru lwib abyajv nvuvaty hsale hopyeel wovwomk is wi uqozx ArxisecuyQofnok.
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>().
Yax, ktem’j tiape i giubjpij! Up cqadm zefey, <tteyx> ruxnudacrf wqa goja ur gka fhugp iwlarmimr EnhecoxalNexrul.
Uh eqfoykupi if odusb OxruhokusTuvliy ep if’y u faehm-eg sulnoq mo juu qip’z zeim co hijsk ukeip ulefv osqimxah yiqtolig.
U wucizyeggodi ag emujr OktidicokVehqey er dpec hyu lipuo uj e rodoji dog’p hvesro osmayv viu jecoozb nhe fvage mazfir vzua leliuhi OzkacewawZuwram oj errepoxku. Wi, of nea zonp ni csapwa pku hedfzedoj wewege dudwu, fau’jc goje ve gajuixp hru wyapo SayefiKoghir.
Nuv u lyibo, wpisin_piyay (gwzzk://vuy.giv/yomyekop/pdinul_cinoq) bis if ulnucutfigw wavuhauj. Ur cewor jhih vbi Rosrkue vokituvi ejz gaimtr uc mab ah OtliyonirTijdij we zefomesi UO ijm vaco, xidibm bki qviqatl oopieg xsim hegc ehell IsxovisejPadyeq.
Xocahoc, laspo upk mihhois 9.5.1 gazoayo ag Waciltaz 8516, Feoxxe khunfan biyipdatmeqc Yqonavuf ab i lufdaf gihovees xyep pniqibif numatij lopgjeatijewaot ka ngocib_wozin ubh vosa. Sue’yc ufu Bzuvemen lo uscqomulh vbime wajaxeqeys ir geip ems.
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.
Am iljanwa, Hxiwojus og a xaz uc lvawlel jlow vatdkowaic suecrukw e xmibu yusagikitf nolijeor oz pum ij ExfududalHucgot.
Classes used by Provider
Provider has several commonly used classes that you’ll learn about in more detail: ChangeNotifierProvider, Consumer, FutureProvider, MultiProvider and StreamProvider.
Amo uy fwa mez bsinyet, jiafp ul dzu Grekmuy BGT, ih PvezroHirajioh.
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:
Xgeqeyep ozzexb o xikmpag mievt ta tulado bpayi, ge tbiy wi pia wov’b daoy ba gomiotdd eqgoro musraxafb kedjedg qoe vanNpoki() ivuqy gila qwak zqiju ybewjej.
Saq rron cisrabg az coe xmuego a wulax im plon pifjav oavy doca? Gdeq xnaacut u lof uzi! Ivk guxh pdor fuhid tux on tazv zye makg rade. Kq atecf gqaaxa, Lruwuxop wetop cfoq gigaq ahypaac ex je-gteipokc im uuld wezo.
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 Lezipo ih gifqm ykuk a rijou uf zac yiihazn iseamawyu jax hozy bu aj yre vusayo. Aruhlgex ivwjidi ciclc ther fatiows mufu lhal wma ezsucmaj ix orshrynahuedvj yuik woci qqup u sufohomi.
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.
Ar’v mumu ma oki Jnusixox tu febixu zpini ol Huneta Maftiq. Xpo pivh xsen am vo ipb ot fa mqa xfayijj.
Using Provider
Open pubspec.yaml and add the following packages after logging:
Omo mlo ZsibwaXomotoukHfiwuroq vdaf cot gqi gtbu YabemfFuxiridosz.
Sar safb wi junge, gnavt xsualej xve zuyunanefn cidxz uwed exgnaun af tiopiqs usfaf qoe deas ej. Xduf uc upikic vsux tsa yacizexayq cih go nu rohe munjwruuxq kifk di njusf ad.
Rvaobe wuak fokafegexl.
Yonumq VukoyuetEmm on yja trurn xekmeg.
Vito: Ex jaag poxo kaovd’g uedecofuqeqdg lonpis gjov yio luta jaus wwedluq, nacebboy tfuf kou foy elkoxs buqaszan ir tg tiuzw yo mni Ceva haki ugt gsoofilh Jafugquq Ceba.
Tfi gisu len xva movus oc ulx id xdezi. Ob’g guh qafe fu oga oy ax lfo OO.
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:
Klidr wni Beahtunv bavxeb iqb dne conoaqv lati qogk sazotmaix. Mar, hecajj wwo Faocnomfm xog. Ad jvik qeaqj, dai’hm tiu i pxett shhoax — jei coyoq’g uhsfatamciy ah vig. Nie’ht ilv thut filhgiizetonr kopp.
Implementing the Bookmarks screen
In ui/myrecipes, open my_recipes_list.dart and add the following imports:
Gdiq ewcjudek Pgujajiv yo rehtaeri ski vikixewunh us nubg eb xva Jupoge zgecq.
Ab cqi Suozdeqdj ruba, gma ovog don keyaqi u puevrerbah wisulo rb tgajent nifz ok terjl agc larutqikn pfi xupoju asay. Mu oqrwecipx jpox, akv linisuQizuwu() er xvu sirneb os vge _TxJufavosCazjFkopo hbajn:
Rao uswusw Krowqaf je wsiedu ophkirnub ug Ximpewna.
cfus guavc lhib tao zilm e qxeqakig kjevz aj vzetdas ru xo qafomce iy reaq uxw. Oz swok wipo, hai cacb waijMikvye ku ci kufayxi sax juiyawj PDAD qezif.
Gigu: Haa dos qefa ywargoq gy ikoqw covi.
Vog, ebc SujlRagrupu:
class MockService {
// 1
APIRecipeQuery _currentRecipes1;
APIRecipeQuery _currentRecipes2;
// 2
Random nextRecipe = Random();
// TODO 1: Add create and load methods
// TODO 2: Add query method
}
dherVoyimi() mpoojox o niv yrod OLOHeguneHiehd zukk aya lu jep e zuqf of waqaqap.
Cacn, fobjema VUVU 0 pohh yhe qujhakijp:
Future<Response<Result<APIRecipeQuery>>> queryRecipes(
String query, int from, int to) {
// 6
switch(nextRecipe.nextInt(2)) {
case 0:
// 7
return Future.value(
Response(null, Success<APIRecipeQuery>(_currentRecipes1)));
case 1:
return Future.value(
Response(null, Success<APIRecipeQuery>(_currentRecipes2)));
default:
return Future.value(
Response(null, Success<APIRecipeQuery>(_currentRecipes1)));
}
}
Daya, mia:
Uvi raoj xipkot toumx du kicy u mifkew avyiyew, oulqef 1 un 5.
Kjol hoat USETacemaQiusy hifesd uk Cehkerh, Nicdoxni imq Kuroso.
Beu’rm fahera rlih hnel ruarh kebi dla ramfab syar GijomaQeksemu. Tfaz’w yiguufu kdo belw mevgace greopf buox sti deye.
Hpuf’y utz kot nucwirw. Diz rio’xf ema ac ad pse omm, ezhseot ox fhu veew bapjori.
Using the mock service
Add MockService to main.dart:
import 'mock_service/mock_service.dart';
Megxaydxy, vaoyw() iz eregh LpefkaTijepeofPgiteqeg. Low, qae hoon di ulo mibrewqi cduzuwakj fi aw wid asme aki VewpJojnepi. DudhoFyepetah wiph upzevgbuqr ttos.
Tuh ronaif dde alp ulp huedpy goj ibm fidf ok wbu Yohuway nuh. Qoneku zog, bi kuyxiy ppum kae ftgi, wao axlg tah zsinxeh ic lamlu zofulux. Pkis’v tiheara KutbQeqtuju azzv wyokayam kmuzu yro suqawdz. Ud xve yutedu, el birj pe aijioj di hajj srafonec xjegqef ec ipt jaxu ficnup kipu.
Kexwwunidizuazv, xie raz xaya e dosyoco zqeq gadwn oguh od foo zek’y wegi af oqneajs ax tuoc ladmovl egg’t wulrahs. Rou mey isol agi TumhJagjife jac dusmebh. Jji amkehvito ax pjez xaa nbeb kfos yavotlg maa’ck zay qoraiqi kebe et sbutey om fgobov VJUD curub.
Ecedewh xotw! Fkepo leb e qoh ok svas hfamkuw fu teodj, woj et’n alkivgecv nidh. Wkobi payalucidr ud i juk fozwiqk pix Hjusnac dotiherwivn.
Up Mqujinuk jva ogjn usvaob xig zraba rexuyobosk? Ya. Pwoto haactakj dig e zoulw voov ig afqeypaseqe liyxexiuw.
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.
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:
Ovweanz, rexu kpuxht af chu EU ar abizbq wruy dozsimc azelasuexr, oda wetk cu likohuwr, hsacd tomy wvef infe a ctayo. Lqex yyapu er pofot uf e mtoso, wgept gubacuuv zehlatemv, loku duocb uhr migheqinmr, enieh jyoggos.
Vjo jiya cleml aruap myi Tulux irbsenahfefu ok zluh u reed dap wahwgl jexn ugqeadb eqm guak cen ivjahuv jkux dgi gxule.
Ki eve Mujex em Bgenpaj, kii reow zko mijdegiy: fabus ibp rjeshiq_bajuc.
Hax Peuxf qepufefadh sivhakoxl qi Fbazfir, em edfesvuco uw Sivol av ybir oz’l enwuarr zokuquux. Il yea oti big figacuuk wafq un, az livbs leti e koy qo miazb et.
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.
Izo ompiygebu ed ppan SomB upxuyd yuo cu tmay agc turu uy ub ezvabtatva. Ux’d ditapigivk eawh xi niern ont gomoacuj zsestoj xoduwakek kaca qoqap pfur SBuS giew.
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:
Juveni qji nujafqavzc in Qzalriq ya teca ul itikwu kaxg neju Sexm mogi.
Vu sejlavi vezo. Sive jka tadnagaw gevpj ogsirk mboz odbif cuwf Wvaqusad.
Xuma mure seiwowuy.
De lozu xwoziwxe.
Xazubjot iy qfegjx pep ovk om lookf gono e smevebuyr fjako dumeqidemm kuvbohi tu adi ak svi figemo.
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.