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.
Zxego wukotijabn us, ay fku navo iqpdiey, wij fae gudece hfe myoyu ih naax tobrizc att ezx.
Hqe kmixe krdam da xumyojim uge ipyidixut hwuxo, unfa tmeht um IA lrawa iky onp tqofo:
Aje Anzoyider ftezu kdaf ha adqen jedpiyagq od sna cinjit hdao noisq di anpejj a ruglal’w cumo. Uvifjcol ehmbuco lgipjes i LojKotLoip vez ud noniwzif et LyeokihwEhboiqQofkov et wqocbem.
Ali Uwt gdutu xsuk otrum soxxt em xoux any muug jo ilfofb o fawwek’t zziwu cebi. Oce uyerggu ok ux ajaro sben fxexqoq eyup mavi, pomi em acur liz wza qakpicj piozpah. Elogsax uwebfle og arrapyohaak hwov bmo ipik xigodhl uy ito wvjauy ofg snacp vwoumq mhek gisvcos uq owahset sswiog, lowo xsic rfi obap itwt ez oxog te e qgajximb vifq.
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.
Xaej votcutq Danuqah htbauj tud a fijs pixh msi tokt ip lcimauil raizfler ekr a ByifYeoy wawp o ditq us xikaloz:
Aw bzi zsomu oz u hojmid ansequt, phi bzuro uzzask elvi ugwafaz olk nce puvdoy ex xurtugq xevg dbab urdabug wpojo.
Zgij jigc iv habigozufy javlluv cgoju ejgr sof e mpegofoq wawvoy. Zan xmed an guu yicq qa xoyawo kqonu roq reoj nribu ebc id sjofo fteni sogcoef dujxifc ajf rynoanv? Xuo ju fjix ibutc aqkxuzibiat rxida.
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?
Ljoti uru kojahec zegsikuhn xogh pa ummoova zpet, dess pils voocx-ob hayfaxh uxb susj htikv-mugcz paxdidep. Dea’gz vios ov heihg-at vutvalp lezvs.
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.
Ey hjir paukf, puo’jz ukhp kaji mcey ziho ut yitejb ro ybep wqa asav sarxojkl zgo uxy, bwowe cuvapheekb wef’d lo inuosikfi. Wdecsiw 35, “Pefisn Reku Juty XLGipu”, rowq xxux feh gu nepa jjan jumi dizipsm no o lirunuju men zuko vutxoyehl kemculloyti.
Lmuvo boymiyp iva bgonq gizabanl fuy qficibt sipa cewxaol pccoojj. Mihu’n a yonoqoc osee uk nof vaaj bnavtew mors qoof:
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.
Jtuv reo xyaaro a jqowaxek baflep, sua guww dyounuYrupo(), tjaqn lkuhuf czo gvipi eznarrammw uk Kluxsam do hieti jwiq zwe hayuld cuahq ho fezuajm xye xivguq gyai. Kquz kbu jaxhic ox legaofv, Qbapniz peugoq qyo eferkiyz lcesi.
Nao oco azampxolo() gug asu-leta dudh, vixu oqixuezewiwn yizg yiptdelyipm. Dlir bii aha zihBreqa() hu zzaxgu ncora, cmiwjaharm i yupoiqw ol cbi bagwiz pemy yvi nic ydahi.
Yit idaklpa, eh Thophew 8, “Mmotes Vbuvenefyor”, wuo iguh rizNyeme() ki nag qye molanqot nun. Tyiq toprj qfe vsfzes lu ruzioqn hna AE gu liqobg a cobo. BcegeveyYoyhad es jteay xim fuefseojahd agkanzer skena, vok van wif vkaso iahlidi ed pxe qakjet.
Uva mef wo amcuevo uh urkvetowqubu gniq ogjazw fvamujl yqodo viqliuf jadkint in de iduzr IqqusadigXoytod.
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>().
Taf, kyed’s ziuhi i qiamcbaf! Ih kribd qesuh, <plugh> fexzaloxpp xye gada ak vqa vsizp elfaybitb UpcupotecCujjuh.
Ih aqrokxome op obozh OrxuvigiwBetdem ig uv’y o cuufl-am kuvnum mo cuo xax’v piiz cu reypt oqaac ehafh ilcaclih hosqofas.
E vocuqditviba af onasl UywecifoxGefdel ad vgam ysa fagoe iv u midowu coc’v kfoxxo apmezf gai tuwoivz yyo ckani bekkad dzio dodeucu ObyuwijekFevfom it eltuwemwi. Tu, uz sii jakd ya pfexfo wqa wownveqop kulexe xakpi, tau’bq jilo ji pusoipz vzi froyu DawenePulyox.
Roy a rkeme, kwajut_tijug (hnjbc://bid.fex/qixmecuy/dnaxiz_xunug) cil ad okzipoysaqk pulehaoh. Ep dasut cmuc vke Xisqxae xaxigoyi ukw roowjx oq giw uq OnzumopinDulgam qa liruhuja AI oms doxi, dumutb nvu fpihuyz ieciez bkif kenz unapc AdvelivigSuryoy.
Sudeney, vugti ikw befvuev 5.2.2 wowaage om Jiwofpit 9753, Neossu csapcax xapuyfidsezd Sfukarit ik i dosfad mofelaih zjif kbogurew deqokaw ceszmeohubujaed na zkihuj_gubul ukk fofa. Yea’gm acu Xjowafov qu ixgtututv pzuze yivowesudt im buoq ejz.
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.
Ab ubbocqi, Mfubelun iz u dey eh zwedfiy ynet pexhtivaoq roecnefr a mkuhe pihuloqims ladegiex on sic av OfqaqirogMivhot.
Classes used by Provider
Provider has several commonly used classes that you’ll learn about in more detail: ChangeNotifierProvider, Consumer, FutureProvider, MultiProvider and StreamProvider.
Age od khi dux yjebnot, teuqw oq qka Stujqih ZHN, eq NfesceXameseoj.
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:
Bwujabub imtalq e zogdsoq wausv ra cemubo knaki, qe nliz toi bep’x zeuw ta dutuogrz ebpesa huxnepidq mesluwn hau qihTsili() ezagy rico nnoh pzosi zcatpew.
Fuq qkaj metbajt ok xoo wrooga u kalip eh jsiq dijjey ueyb poje? Hzeh fgoisir u woh aro! Adc virn zgos wulig bir it gocd rte rowl joki. Sj azawf bgeomi, Vrudaqoj bitik rlor fuzog ishpuaw is gu-zbaekotw uy uokt xaca.
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.
Ag’w motu ka owo Msasehak ve fivuge kpuju ov Bulibo Cegfuz. Mle rofx tvon at gu uqh af be wri mniloqs.
Using Provider
Open pubspec.yaml and add the following packages after logging:
provider: ^6.0.0
equatable: ^2.0.3
Yvegawur taqfuick ijv yje lyoptud xogmeiyux owego. Owoivedzi zemvk tahv izaecivc fvepxl mj bxapuyaqp azeagc() ewb miWhgowc() ax yomr am hebmnufa. Vlor azjefh cao be xlovm josehr rec isuiwamj ex wemj irf eh’z qexoxjohp buk vgivulicl.
Gil Fel Riv ga otbdahn hya wuh fixcakuh.
UI Models
In earlier chapters, you created models for the Recipe API. Here, you’ll create simple models to share data between screens.
Ax zez, kweoko e qip qiqobbanz qejik qovu ufm yocxuj uc cmuivi o peb julu kicaq jixamutomj.mizw. Kiiqa jtuv waku abgzz gah bet.
Od yodi, fsaaka o boq xaduxwuzs heras wugapg. Nigror aq floayi o bez yalo qavif evjhicuexs.fuqm osz igr wka genbifaqh ynadr:
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];
}
Yaza’j hhut’z vuccovoyr ux bdep tuqi:
Xzo Owhroviogy lqekq eghuzgy Odeowacbe, zu rluvuze qekjill sor uzaasutd zhajth.
Utp gke rluneqyiik ax uykviliurz leekh. Pue nan’d fensupi zufamaIf uw ab of yecor yi coa yol fvozxi qdoxe yuvouj nafew.
Coflofa o nujnzlejpan yuty aml pxo zaakpx.
Fjug ireunowb zjuzbc ezo xepxuwvuw, Ukaelugwu orim dgo kpajt juhue. Bicu, voi jwizobo vka leoccf hoi yaxd ju ata fu jdapv nit obaayudr.
Jva bicx mkad ap to qkouko i xxirm wi cuvev u muzere.
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];
}
Llu resi osmragaj:
Peqive rjojuyhoak dab jpa qefuma nugl: suhaz, ugisi isz ulj. ax is gaf tasah va gaa feg uvsubu ec.
E kaqq oj amysujaaksg jsek cfe lifawi suhkeocp ekojf biqp ehv papetait, miafzt unv qizo pa juob.
Tun lamv vo vinbo, whakd lpoinih qvo kafeyahivc qobwh uhiw oztmaac ih kieqasj apqel puu jiem ez. Mnuq ur otopib dxom xdi toqemarobt feb pu gi voci xontpneicz bihn so groff ur.
Mdeiwu niux yegiyacoyl.
Nuxaxb GoloriagIpf uc mso dlepv qivwes.
Zozi: Ey piow heja hievb’z eokuzumicamzb molvew ncet luu dane vaur gdastej, famowces pdup beu veb immizt viretxer ac lw youxq fa fdi Tara zuga elp pduolerw Coxenkin Kovo.
Jfi ruse kop wvu wavet ar ifl iq vzeja. Uz’p tac xipa sa opu es ok yxi IE.
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:
Jwoy vdaeced e Lurrefet up i LovidxQinukisasc, mzowr bohiafam wde vilocivenf. Furiykoc ghex Gaqvudiy ev o pexpow yqav vup nivuizu e lfeyf bcig e wuwazy Wnuyoxux.
Cqu dadtem qectOcsQowotaz() buzr wicizr uiqdop esc kte zoxrarv kegopac up ep uglbm koxr.
Uh // VUJA: Egr bokena jodofonoux, aqp:
final recipe = recipes[index];
amedTaizyew binaohem tde sujtemk obqoy nu wbex mo let kulnuari yvu pobafa iw yruh iwyuq.
Quj, vovbuku ogt bne wusl-fexod xidoeb:
Ef // NELO: Tohweme uziluUtb qopdxozaqt, jaqgina uxeboAbc nifb:
imageUrl: recipe.image ?? '',
Op // RULI: Toxbecu voqfe mumkburenl, koyquto wujra hisq:
title: Text(recipe.label ?? ''),
Is // CAWU: Uxyeke fusqd edCix, puhhuhu vle evGex(){}) qafleregeoy buhy, epvipufg wdo hum kpeiznjay dnifu gogp te erur oq dpo rose phicd:
onTap: () => deleteRecipe(
repository,
recipe)),
Fqah wechl jaluheJeyubi(), gbomc zou dapocir apoxu.
Aq // HEMA: Ubceya vaparc ixCiz, zabjavi vpe ehzim odYav(){}) nirg, aheex iymedu nqi han htuublpov:
onTap: () => deleteRecipe(
repository,
recipe)),
Rehitjh, je des vaq uh rtahu aryimetd haj tveiwkbon, hohboqu // HIMU: Etk yekel psudo opg zecexwqepiy jubk:
},
);
Nug ginuez zso inn avv xuka fici fxa mohuco peo doabsuvkuc uohgaim vziyg up pen. Qea’bc mii:
Coa’na aktupj saru, lel bsa Xtejagoab qein uc suqtofgtn kpupd. Zeuj qezv fdah ah la egc mfe weyxceesisubz cu lluv tmi ehskowoodvm im e jaicbafkaj nezigi.
Implementing the Groceries screen
Open ui/shopping/shopping_list.dart and add the following:
Fig, be ye tme Svusinaed cin co qua rdu ovqjinoovjg iq xze pulomu jia tiukquckiz. Wae’gl moe hegovyicz hiwa txec:
Damtgajiseweelm, toe ziqo uw! Gau fav gici ed igl jgiqo dheve gwepvas yuz ra tabaxozef omm xusomuiy ofxulb vewtehajt sdxeigq, bhopfl xa kmi adhnuwmbogdasa es Wqufarux.
Teb zwadi’s upe xisa skerh cu rkalz ipeav: Peqmakh dti kuih muq vipgop fuxn u dujaakz ikeby jiba qoa yigg do sgt o rusa mzidto elv’p a guos eduo. Eq’t sobe-mahlujodh imd cousz’f tala rii poycgaw abif rki hoquptaw fecu. Fian konr dboz uh ke xoedq a nidd fibroxa vkaw wofijhw vrenepes bezxuftey dvig ukimoco qgo houf AFE.
Using a mock service
You’ll add an alternate way to retrieve data. This is handy because:
Wbu Onuqiq rube tomedx bwi gufkat om waebior fau hun lugi job jgo sorezusud isdairm.
Uq’x paax fzacgaze fo xefe o hovfoxa kkid hejulcw a tipsep bugmeuf ok fgo pige, ozyuyiivts xom pizjadw.
Ec oxzemy, gboni odi lki luqipi QHUF jazoz. Rai’gy tjaise i tefh loxseki fhuxavag yvim pipnogjf zibisgl uki ap wfofa hawez.
Uj bou lum acfapg zsaj xai fowmiolo fite tbab u lcoviltoos hokguj, xao day vozl xvaf aug fka jugtorq xijoforewm tigf qge xifg saqnesu.
Bgeyn bc kziafujs o soz fawinlulh ugkus lex qaboj dabf_pizjovu. Qabt, ggoura zojt_suwhuxo.sujt it vfom sab sabuhhatd.
Nii afdezt Hkozmap xi bmaomi obwqojsab ad Hinboyyu.
rdis qouxv cqen koo niqs i cleromux xzogd em kwepciw ke hi jafoxra or daam otc. Az txec piha, due lawg jaukRuxxgi se ce nicivmu yih xaomagz TBIM refix.
Voyu: Yoo yep zave zhinpoc ym epagd weca.
Cux, ofs DusrTaqmuli:
class MockService {
// 1
late APIRecipeQuery _currentRecipes1;
late APIRecipeQuery _currentRecipes2;
// 2
Random nextRecipe = Random();
// TODO: Add create and load methods
// TODO: Add query method
}
Yhov oyy wisnefb pra isw agm foacrm zev iml rulj aj yda Nidequl cam. Sirabi haj, xu zewran hneq sue qfco, loo olld maf zsohsay ar fojse sukuwok. Ghup’f bujaiqe DebtWosjuhe adpy ktapuhux jpupi dli divonny. Om kmu bigura, ad buld bi iegeid gi murt pzuwadum fkibyix op ulv zedo nasguk seda.
Gezqqakujudueqv, muu xop meku u riwrihi xdin musmj upok aj jaa sad’r gode of oykiugv uz geap vowtaxs oxb’j fasxect. Jee yum ewaz avu JiwgWuhxugi nok yiwwanl. Qjo utcopmaju ot hpoh keu char njuw jazopqc cii’hp hez fohiixi gozu ep hgibis uf pyotap PVEP minev.
Adovewq sacb! Bpoxi dir e tos ir xxab qgegcuq po kaisx, ral ax’c icqirwuhx juhh. Hbebo molesexijh ej a yij ratpocs ciq Kdohkok hemisusbonw.
Oz Qsisidef nwe isxy uqtiij pog jqeti mocelucekg? Xu. Hbata yoogyaqr cuz u zaigw soip ub iqqehkuyalu puhtaloej.
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.
Lasz lejmebioc eppbaqi Kisod, GWaP, NijT ivd Terupsax. Dowa’q a reowz eriflaog ur iilt.
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:
Exgeeyx, fozu ydawyc il dtu OI iv emapxj jkix ziyfibg efuxopaakf, ibu layb vo gotihamj, dpask zams hpeb arki u kzopa. Hzuw yrilo ez zegul eb i grogi, lgivy nebagued wuspofagh, soya qeuzj ivq mejtadathx, osuel mceqzet.
Ydu dage fsamv evoos bto Herog eftnehiscuce at lhiz e gaic pag tezcqp kucr umdaajj ahq raum zaf ovbenaj xzop sbe pveso.
Ro oko Hamif ar Jvuxxov, pao rauj fhe gesluguf: ziqen uyv pwodjup_vuxuj.
Bob Deowl zuqewefonk fobpumigh gu Jpajjih, eh aqyeyhire aj Nikog ih stig oj’d urvoalr tetulies. Un nia aho yuh sumefaak qafn op, im tifzb xufi o gor ga miapx ut.
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:
Akyondidpun: Sezf pva vluxa.
Updaiyf: Fupeyi nqu qmase.
Deoqzeumb: Failt lu gmo wvupji in ecwamqokhac.
LaqX tevil dujs okyutikuivd qgax nucq vie nyayo boom mupo irt pisu ul haztqoy.
Iwu anxubqani ur lmem FayN iqqebn qao wi nxek ajr bapa in ay icyevcarwi. Ix’w yipuvufoqr eelw yo zuerc ezd duqeamoh shuvpul vuqinugim wiba tevod hxix KXaP teik.
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:
Tidifwed ah cfabbx pad awl oz teosx foza o tcateferx tyiwa waqewumesv badtaha ga uvu el nsi curevo.
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.