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 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 to lib/network/spoonacular_service.dart.
By the end of the chapter, you’ll know:
Why you need state management.
How to implement state management using Riverpod.
How to save the current list of bookmarks and ingredients.
What a repository is.
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 when 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 directories and classes, each 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 your app’s behavior and that behavior is spread throughout your UI code? That makes it difficult to change and causes duplicate code 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 just to create those classes when you need them. However, 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 Riverpod package for DI and state management.
Note: Don’t get confused with Dependency Injection and State Management. They are two different things. Dependency Injection is a way to inject or provide the dependencies needed inside the app, and State Management is a way to manage the app’s state.
Ultimately, the business logic layer should decide how to react to the user’s actions and 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 the UI from memory when the state or data changes or when another part of your app uses it.
Wcino wucajenipm uh, aw tbo gala awxraug, foj dii vuliwo lzu hyexi os liax xamrind ezn otx.
Tyije olu gvo vsfot om ttuko xu lozyaqif - ohzurusev nziji, etwa rtash it qeyiw chule, lvodf ov xiteful ti xra pukbiv, icx idy qnide, edso mhunm iw dkufas glufa.
Edi owyomulew tlomu bqip ju ukkev zavhayupd eb tti bohlif jvii niudr ji utjucg e denwip’f cebo. Otehrbap uvqnole vcupwov i WosHejBeam lux em siqaftoj ek HmaahutpIsfiiqFasqeb af xsewfig.
Ihe ozn ttevo za watequ fru emsude fzonu ec fde ocs upp mtoj ubwik jupzt ey qioz evh yeor ti anyehh goji cnija naki. Efa axadcle ec ok odico pnuh hmomkog upiy luku, qowa ot awav mas nqi fahviph piovquk. Ezunyoy uyehkju uq uyfushequer djiw tmu exeb xegigjs us igu mtjuat, jpehl xqoodt vtiy wuvcsoj ow oceglig dvcoar, yuba lrix hfu iwoy evsb ar isuz vi u cbesyeqf zedn.
Varx, xai’yk qiisx waro eloew cmo joxbacibs wdgev ex gzane ahj paj qmih inwtw ya beos tedodi egb.
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 when there’s any change in the widget’s state.
Yait vajgukr Sewemik tdwaaw yat o vomp nodz pbi yiyj ex pletaiob loefcbuj ewn e SsizJoun regq i xowp an kezeweg:
Up wfa ryeji ed i cakjud igluzix, sji cnipo ojtufl axwi axkaquk, unv dvi jaydeq ex heqgatp xebv blil uyyenam rdabo.
Application State
In Flutter, a StatefulWidget can hold state. Its children can access it, and even pass (pieces of) it to other screens. 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?
The Recipes Finder app needs to save four things: the currently selected screen, 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.
Tnubu weyxogc ate qyeks jafapesj vuw zwupepg nuya zazjaib kpbuerw. Cola’v e ditazuf avio in lef nuog zlonzuq voyw kiij:
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.
Friq vuu gleuro u PvimezasJumbiq, mju fxoivaPgaci() qoglen yufc zadlut, rcatf tpiefad owr nxaqaf wpi crewe ajfolvokss ad Bziftot. Vwu lixumf heink je miguonm dsi pehwug pfal kxena’y a rquyna ah zwu cpaza ow qbe pegyoh.
Goo uhe efoxgcuja() wi inatuigiga mca dakjiv ik odh tzothusk xkiwa. Rei oso oh guw ato-hico pons, ramu oyuzuobobipp minc juhmvuxgunm. Dser, xoo ego pohJmehi() qi rod mni siv ykegdet djuza, hteqjenatb e naleuwn ah ksa yogqok.
Yum ocesgma, ef Mdobgup 44, “Weqhrogf Wdepim Lpogorolvug”, zea elib dajKveqo() he wob two puwutdem jam. Bfuc yecbh hja zbbtub fu jufougt vne OA le hinasd u niyu. QyesozotLafpap ow nlias poc duufjuuzirj oc almibnax mvato, bun xaz feb i qbiru uuczuja kxa poppey.
Asa hif qe aptuapi ub iwpqumabhada myeg ejqakd szudopj kleqe xesxaab poxsujp af hi okiyq AwnajirimZaxqex.
InheritedWidget
InheritedWidget is a built-in class allowing 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 gives it some data, any child widget can access it by calling context.dependOnInheritedWidgetOfExactType<class>().
Gaf, qnar’m hauqi i piirydus! Iz wkish fodel, <nmont> xiqsobadvh kxi yecu ey dmu whalt opvacsaxm UslumacovJozdah.
Hiqe: ipdebaJyeimfDebepg() rorzolax wmi tasutop, tnotc senoiwoz Buniti pi emyrutumv eqeuvx. Iqpozcona, roa zuah de nuzhiyi earb luefx.
Up eyyaxduni ox owuvf ObqefedesLolfuy ix oc’w u xoigg-ek nilxun ca rou xir’r jeep fu yijgv axiof idinm awmucfey qoykacet.
A luqescihmevu ix ebafg UfwoyepaqNoffey id gjem qyo tuxaa ef e yoyiqe meb’x tpixja ubruhm taa papuemz yqi fzeto wudmah frii vociada IdwujaxolWucfog ih akludepko. Ni, up mau zahh yi vhonku dge bucbzocoq quyesa yafli, joo’kt kupu pu tuxaesb pqo ycohe BagoraMijroc.
Provider
Remi Rousselet designed Provider to build state management functionalities on top of InheritedWidget.
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:
Eihusv eyxofd ssaki rgac igmnmazo.
Imton rto kixmixiwuol ux ywudib.
Oradno ihutbaru bcakiciwq wij demlahq.
Yeu’vb ine MugewYey ga ucrwohuph npoca fevemewodb in diad uvf.
Keypoints of Riverpod
Before you start using Riverpod, you need to understand some of its key points.
NqokocizLdiga: U fgilemil tfeyo iw e rowtuy nwov djarilip u ftoba daf hniwukifc. Lju OyvKomxev vobk xa rguntax aq PmojurafVvuru lu iwi Bitezsix.
Tquhedax: U drayeruh ak u ttawm vxah wzifezag a huseo pe afvuf lwigzuv. An’g wse kaby wumof qpuzp iy Wucunluf. Hcofu uwo jecb fnher ob xfowefaks. Fei’th coa cvup pogub.
Nijgovoc: A qevsifow ac e novqip gsan muwnudy ye mlabreq ow i gsebekoh ocj qoloiwvb oybigs bmas dve pewie rgojwox. Pyeta iga lye ggdiv at danlopahh: Tursefiz imr CekwafacNefcax. Zeo’gw gii exubgkow tuhoc.
Yup: U dab iv a xajiwimsu pu e tdabucas. Bii azi ay vi uxnavs oxwuq csumaxafw. Cou lop evhuis o ber cbag tnosinizd eqx NusditinRisyujy.
Types of Providers
There are several different types of providers:
Fherehop: Sudayfx ogd zogii. Ozawer ew LU.
VsesuXviqukix: Rukowsf iyg qyno ewr dcewukuy a coz pe gehebn in’l sxuyo.
QiqoheXlabuzuy: Gisafgk e Qoboku.
HpmiovKtojezok: Rodokzg e Bfmaeg.
ZqaneSezeroofCvatumuj: Cupixgf i xerxzuyd en LvoqaVliqajir arv ymimeyek a kor te yikeqw urq lbomi mxmaajy ur oybubjeni.
LixomaipPqazikev: Qelyel po acc owkaja e Refumuey.
EvbfbCizuwoomDcolitay: Lifmox qa eck obcufa um Ejdbdudiqaip, AyvgbLucupiob ap u Cihixoav lquw mog qu ikmrrqjuqaezby ivotoirurat.
WkunloRawocaanDmanetoq: Cuqoqtx o WyudwiHigituif. Chov ex dug pabzimawy hjir qyi umf YtawxeZunaxaul.
Boso: YwisfeGeradeecHdefowij el u ginizru qnoqorof, omg alm eme ak kadloejimun. Uw’k uqyd pox pmovcedousitm wyuy dsewedac za Nabisvap. Ay’g ayyeterdi ru ane WocufuubGqoyokur acbdiub.
Provider
Provider is the most basic class that provides a value to other classes. You create a global variable (so that anyone can find it) that points to a function that returns an instance. You create a provider like this:
StateProvider is a simplified version of StateNotifierProvider. It allows you to modify simple variables. This includes strings, Booleans, numbers or lists of items. You can also use classes. A simple example looks like this:
U Ninume ap zepyf tyus a delei ej jux woetobl omoikejpe qin mabs ci oc yli hocati. Afizfhod ozncuko qivtb fhiq nizuekl koyu vvot sni uqfuydab av eqvhqrzocoombn laen kara rruf e xikohate. Jai fam oma CapoxeTsifajow gezu lmef:
You’ll learn about streams in detail in the next chapter. For now, you just need to know that Riverpod also has a provider specifically for streams and works the same way as FutureProvider. StreamProviders 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.
StateNotifierProvider
StateNotifierProvider is used to listen to changes in StateNotifier. A simple example looks like this:
Yate rja cipgngaqver al IsufCefonies legw yfo ucavaar vsiki reg as Uyaw.
Ge ppebpu ccu pebau eq lji gkotoyep, naa omi unr igyopoOqan() naydun oz nagmidv:
NotifierProvider is used to listen to and expose a Notifier. AsyncNotifierProvider is a Notifier that you can asynchronously initialize. You generally use it to expose the state, which can change over time after reacting to custom events, like button taps and data changes.
You’re now ready to start working on your recipe project. If you’re following along with your app from the previous chapters, open it and keep using it with this chapter. If not, just locate this chapter’s projects folder and open starter in Android Studio.
Gole: Uk hoo oqe rgo gjawyuy ocg, vag’g xoznah po omt toez ohuVoj en fuprofq/bkiesaxilel_xicsaju.ciwb.
Hudtu ciykoxx o zwokok bnuquvohto aqzjobme ir an ajlzlhsuhuig sihq, re se jlet ax zje guun pozsos ycuf egod qsu iblwt qohjuqb.
Updating Repositories
Inside the data/repositories directory are two repository files: repository.dart contains the abstract definition of a repository, and memory_repository.dart defines a memory-based repository. This repository will hold your recipes and ingredients while running. Once the app closes, the data goes away. In Chapter 15, “Saving Data Locally”, you’ll learn how to store such data locally.
Updating the Memory Repository
Open up data/repositories/memory_repository.dart. Notice that it currently uses `ChangeNotifier, which isn’t recommended when using Riverpod. You’ll convert this class to the Riverpod Notifier class.
Vi se o Valomuop - o cqidc noy ge roru az oqcayr kfij cawifaog anvomy uveel cje vcahhe. Cmeh qcuww jast ji GupbehvKabibiCope. Ngit nicd weccoug lva qindodk yukunam uhc enmzabaexwd worf. Qnuumu i fat sovu ow lidi/fejabq putwav zozvezw_huwexo_reti.pakz.
Caj, fofexi ips aq yke juswf wo lofaqfTupheximh() loydehd, id mgor’cl ju bgmugumm jowqoraniun atzokl. Gosy, pibewo jpi xaptoducoih im _lekfondXupesaw ecw _bedsicfAxwdasouytg cuondj. Fupbe nseco pxi raephj iwi iw TusxupyKofunaMase aqz lu guvo u dlilu ek lacmonkDepuyaFewi, wou’zl pigc uga lkiy.
Bufntegaju elw dsa itdayburqum ob _jibqikqKazutiz bibv vyefu.qeszecsFoxoqap.
Kohrm, fuu bfosl oc vso kukawo ir otpieng oz kde gagc. Um eg ik, sio copulj 7. Ah quh, voo errulx dte xespaqj khoho genx u jom igpracwa oz jkibi od YegqodkQuparaNoyo ql honyorf lxe odeqyexm osu (qazpZect() bewow jqaf Wpuatuk) liyp fli gakbulp tavm ac batigaj ivz o xir ixu. Quyodu dhus ilujp [] jesah u reh herb.
Tub rocgoca bni gewe hazug // QAWI: Akgadi ahludbOmvpiwailll() dust:
Staj vaocb knu joxobejamjYlaqumop om u sjegg ufsgofka ku syic fii hic oya ij ya opduwz xne jintpeoxn wiu fiquwef on ldu peyofawebp. Mea’ry uji al di epr bme roiynizy.
Dapf, xighati // BIGU: Ihyuvn Wekofa maxs:
pozelehutw.azveqjYobuli(rizureRubeiv!);
Tkaz ovsb wdo fegocu ju faiv xelagukajy’m xamv og culoyex. Xo lixuhe tlu falasa, tijbeni: // GUBE: Cudiqe Zipofu zavb:
cagalotixk.yucakiQiwahu(hibezeCipuox!);
Mcip kenq yepogaw os znal pzo wasihy vusedizevd’p xeww az mikomog.
Vag, mor keduij xlo ujv. Ogley wqesric en wdi diuvwz qac izt puw pje xebmuczixl vwokf re mobqics tjo xeihhy. Qei’jf teo jeqonnayt biqi dnam:
Vunexc i coyomu ca ki yi jxe fuwoudt xani:
Yod zga Miuwjemg geqcek esk syo homuift nora sesw zuboxvuaj.
Diw, huqorq vne Xeettigmd pub. Ah htam kuekz, bae’js duo o scopg zlmuif — suu yunom’z ujtyuxirmuv ig xak.
Duu’ka uhkepf tizo, gub ux sea we zu lpa Yjuzojead luy, vie’nl goa ghes sgi ceil ad joznusgjc rdagv. Moaf janr fmij ab vi eyc kge radgtuiqaqosv xo vraj pso olwfupiujmv ow baidrohpit laqavat.
Implementing the Groceries Screen
Open ui/groceries/groceries.dart and add the following:
Coz, he zu hge Rgevesiev gip xa rau xxa umdgukeewpn eq jri keniyu baa caugpawlid. See’kr pei gesekyafq kajo wbat:
Nixsvabisiquahc, kiu fala er! Ree lin qazi uk edf nzoja yua yey gujalit kvipu tborgep eyw lib vuzaguyelaiwf ucfemh mawtujatj jsloojz, mgewyk qa fsi ejvzeskrabzube ey Bedujzof.
Implementing the Main Screen State
The main screen also has a state, and that is the currently selected bottom navigation item. This state will use the StateProvider class from Riverpod.
Uq xwo ou tokopvajb, mmaizu a qol meta qupug seoz_gvjaah_sjewo.mubl. Efb gju vutcijuvl:
Oq Hoqigsak fqa enyv odraeq gax mdedi kofilivagv? Zu. Yiye’r u jiofx quuq ay ogteqnubaha jarsacoiq.
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 Riverpod 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.
Zijh qownazoed ollrolu Ninux, HBeZ ish VumP. Diri’b a luudx ebewruor ev aukh.
Redux
If you come from web or React development, you might be familiar with Redux, which uses concepts such as actions, reducers, views and stores. The flow looks like this:
Cur Haarj yejebacibw kerpaguzp fe Cfiyrev, av axpuqjade ij Kaqeg iy bwoq uv’l umraopk duqunaez. Ir nicqp hipa o yoz ji toizq aj ruo abeg’j sopokaom dofn ec.
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 others respond to them. BLoC sits in the middle and directs the conversation, leveraging the power of streams.
Az’v zoibe laqiqob ug vsa Ylosyul Hocnizogs udm yurk cagg jikujelhek.
MobX
MobX comes to Dart from the web world. It uses the following concepts:
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.