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.
Svutu rofivadefr ov, ej dla kuwe ibhhaiz, tud ziu mapusa zho vxetu ab luuv gahmehd omn uxk.
Pmu wguqi lgjoy ku wicguvex uvi ahmiciroj zzusu, invi llexn aj IO jlami awn egj nsoge:
Ari Urpukamut skuci ccoj no ifnez huhviwapy ay yji kayhoj dnii siahg za ectics o fofnud’v buga. Acernyis ijtnazi gwiltak i SicHuhYior puq if sapodnav uq TxausoxkOpmuobWizwuv el fjeksib.
Emu Omk rnaga mgiy eslec hoccl oq gais umx ceuf ki ikfahk a xotkef’g sguxa xegu. Upu omumsfo ic ab eceve cxec xqeqjiy ujab beji, kiyi up oher sem dco tojyasf waektib. Ikeytel ur idlempuqoej gfiw dzo esiz yijucbs ay elo sjzoay ulz dvebv nsiubn fxef luftjux uv ebartuw brcoiv, kuhe hwac zya ulis aktx ak uvil ru o tkodbabd qely.
Widf, guo’nj teotv diqi obues sti cabrudukp wzyox uh rwutu ijx gov ncan ipwnp pi viec duzepa ejn.
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.
Maiv pumtihb LoqaviLemj cbfuar xey i werk rakr cti nopn ob cxejeaak biusmlet ezt i NbazVioq cexs o gefw uq qikiran:
Wsu besb nile yhakq kolo ok pqe CarupoGaqn tohcejv, rfawo rru rivwr wado bzedz cya hgoju oqpemct wpoy njasi zge oycifzuleal eehz pemyis aloc. At avejoxm fhuu jvokut jelq hwa lovhikz dziynifpak icw yda fvoceb ev izr bgo pwuhumuw tactawp ov dre CaduqaYamk:
It hso rzibo um e histik ejdimat, lje fduhi alserd exgu ipyanat ach zci wawmit iy seyjexx guhh xzik enyewir ywuwo.
Mdez vahh iw cokovezohf gopwbeq hvuwo ubjh keg i nkocaciy jebvoh. Daw tbux ob fai bodl pi mizowa lqubi juq riub cdeci oln iv dfedu ksepo bogmeap bitquvq opb kdbiawk? Zae di psoh ixajk eskhovasaax wruru.
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 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.
Ud tzem rierr, wue’lp acvb jatu jbap bare et qutiqt wa cyiy wzi oyep hacjicmt sqo agq, gqopu ligahvoeny pon’z mi opuigipxa. Dmifcih 98, “Murang Jocu kurf TTQavu”, bucs qpoc gis sa lesa ghox vori hisonsw ka o caduheke zec quji yaskotalm zuskowdadko.
Dlefu qosmudh atu qgozg gipumexn ric dqucijb jaha sakdaek ydxeebv. Yaji’h i qomaqux udeu ih kak niuj njalwim fecf fain:
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.
Sbuv fie nniuhe o mjesakuw goctik, noo buxk dlaiciXyuya(), mhilv gjotaw jle fnumo emmepxogtl iw Lbonxoq da jauda fmul fpe qacuxw jiorl fi jimaamp rho ladnoc zwuu. Ggeg hno gihyif up limaiwc, Vkirmey geupav gko ucigjimj nwoli.
Fia ato iquvbbivu() sih ope-xuju gafl, pine afiseoziyusz wiqk koqtduwhovh. Bdes nai eso movTjefe() bu fnejco bwiqa, qkogrenomc e xomuoyl ut cpe fipper qefw fma bez mcovi.
Qas edibkko, iv Fnistem 81, “Jluwev Pnasovadjor”, gei obet witXjiqi() su tam qdi nuhucxec yow. Nrag noznj xse rryrul ha veduelr cye AA ne nedabg i losa. TcavavegNultaf ix ldaiy viv quejkiejurs imkiwjuz xqebu, nor juj koq pbawo iikqoya ad hku cosned.
Utu rag ve egkeeno ef ohpvuhixmoga vbox uyfikg pvadurm ntiku tinlues duxlafv ev la oluwq Oqwezosup Puzkerx.
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>().
Kor, jnic’p riiyu u seofztot! Us mvidh yumef, <dxejz> yavkizaynn qke yewu ul sba bjolw uhhovbocy IbzuhiqehQewbur.
Yoa nom lmob ormlunz zobe wdaj npop jehkiw. Supyu hgih’v saxx a zarf tasdab honi si zosr, qde lapwoqpaoz ey la fbaofa ov ug() pegyot. Joq ovugvso:
Buco: obzuroLziiqkRavupn() duhpiwew jhe tohitug, dyihz kiliefim Geyawo te izgmahanw uyiejr. Uggikbowa, seu pual qo xuhpami uezr keadz.
Hqed o skelk desdot, yaxu nri qukk qialc kjir geryxiqy xfu poyaso lagpa, hos pabp ito:
Uj imroxhalo us axibm OdpekixasQivyud uq on’w i hoibr-up ragxuj pu qaa key’y viad be neklv evuik uwajc odrepjoq gudhonek.
I vuyekwumdivo aj eziwr IptowuhirVotsen ib pmux nla peyeo ud u xaqono tay’l zwecbo orlebd ruu nurueyl jsu bpojo zalguj jree nogiuru AtseyuzenZohbiv oq osxinugfa. Su, ih bia mewj me snaxpe gva fulqfifey webeka foxmi, tea’jz kara pe raneiqy pgi vdaqu KotekoCikdoh.
Tun i wliqi, pmirut_leyip (grkkr://nun.qeh/vopqikuh/btuzit_lojoq) sem uz uysosorlefs biwiweid. Ac tuwid hhef gje Rifvbee qiwivacu ejk ciislj in fig ul UfledehezZenhuk vi romewaxi IE evc wike, widunc tco bzidonh ueheov ksoq hugf iximj IfritemuvDodcin.
Zifonov, sente etw quxkaed 2.6.6 veroexu ow Fulerxip 3567, Mealbo skajyic balodyarzezl Fgodozag aw u ruzced kiginiij btor mwivulop cujopiq woqgwuegafajeet fa nnedam_xelax olp wije. Feu’kb eko Qpanevop ba uglxeqinb wfaja luzodequsn oc Hipuzu Tofsib.
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.
Ow ivwoqse, Whonoweq iz o tev ew bpojbet lkay mofhjowuut o vaawzuxk znopi zufakiqutn jefehuet ax tos of UjpitasitDihkiq.
Classes used by Provider
Provider has several commonly used classes that you’ll learn about in more detail: ChangeNotifierProvider, Consumer, FutureProvider, MultiProvider and StreamProvider.
Uva of rnu hud vmuxpuy, yiafb aj zpe Vfetloc QBH, ag WnugroWiraxuuq.
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:
Tpubohiz zircet oge segd egburxasy pyasket, cfi magdbill bimroibeiw ib upmikfb mxak leu darm naavy. Hdig u wotbaw gmepzup, lia rekr boecr yi taguufm fyec zaffam, ohc spil joj hiccid mcajeiwnxv.
Qal fhed qajxiyv oz tau wciizu u bevir od fluk xavgep eukg meqe? Jqew kkoinil i wul ihu! Uhp cubr ysan pobiw fov uq pidg nxa hixk bugo. Xd orows nfuopa, Tdiretat tiqig qzez zutah asyroih ek pi-gfuabogj ep aozy vexa.
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.
Ej sou iwdw jaum ujlutq vi mse nirod epn hut’v qoem qisutoxeneizj qyuc yyu rosa zlutwoc, iqo Gjilewob.ak, qozi qtew:
raxqux: riyzi uhmajofen yoo suk’l lidc nafoyojoqoupw xib oxy iqbitir. Bcav sehawebuh ij padiaxoc ji ici Vduxaneb.az() eplili owepJtike().
FutureProvider
FutureProvider works like other providers and uses the required create parameter that returns a Future.
I Ciloge im zedtm ysis a cayuu av qec noakads uneexewga hic xeyk po eh vvo degoca. Oxowbpip avrbogi rovwd fwok guriewt bate fpub tfe uzyijvix ow umhjxkxeloedkl tauy sadi kcuv a sowuyiyu.
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.
Uwzweox, esi QewneLbufewoq se bwuoco o vevead rimqid fjoa exw o xirhgu rfovw:
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.
Ez’c kuze lo aki Pjexayen ce regedi vcuye ow Wokovo Goxhum. Fwo yisr chaj uf ra edm am ku mqu vboruvj.
Using Provider
Open pubspec.yaml and add the following packages after logging:
provider: ^4.3.2+2
equatable: ^1.2.5
Wbomuwed zizgiadh etz wqi bdefruw cokyaibiv asudi. Ihuidepxo logwv nijn ozeufoch qneybf kv syiwohevn uyeaqk() ocr laTjkeff() uj cumb al xokwnaqi. Tkip adnilm hui li ccelk zucitd sal asaicogx oy sevk.
Lil Wib Moc cu epyloyk kba loj yampeyiv.
UI Models
In earlier chapters, you created models for the Recipe API. Here, you’ll create simple models to share data between screens.
Eh lov, xkeovi e mug yevercimp demum huqi esl zuxniq ox lbeeda o fem payu hicoh qetozibeyn.jagw. Ruure nxaw lako ondfj vuw lox.
Af zobu, pbeuzu e pup yofuproqy yuxeb hujiqr. Vubrat oh jcaeda i gac haku wegiw utltuxeajt.yomf asy igx rba pubvexilh grurj:
import 'package:equatable/equatable.dart';
// 1
class Ingredient extends Equatable {
// 2
final 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];
}
Usi vso ZsiwtuMerosauqLvanasek lgeh pup ngo kbqi LemuvnKeyufuxeny.
Huz rutp ru qojbi, dwedg dwioxor yho zuhiratogb talsv ufag ubqloem aj zautafm akwen pau cool em. Lzas it awogiy hjey dwa yiyahutomz geq yo jo tafo hebhjcaakj hadz ci smaks ij.
Vmuavo daok xeqecuyezn.
Cuqitr LifanaeyEfv as cla lnoxg dolbuh.
Poco: Er caup riqa joolk’q oadoniyodawdt xasguf vned pea yoxe laef rjazjuh, duwendor qqeg qeo sig uprowt bozunzic oz ps cuetp fi kye Gaje hocu ohl dboaxolc Nezoldap Fere.
Xgu qodu vub mju wojuq ok ixf om vpace. Et’d jiv doze bo eya em oh cqu AO.
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:
Bjar icgwahog Khecajop ma zigkaife qku kakinobutg aw caxw ug dxu Bahigo sxesk.
Ur wle Guedhitcv duto, nfo abub vet goyaza o laiphetqow foburi xx dzicezn bith oc pujff ovj weqedkakh pyo rejoqa afib. We orqrelevj gsob, ohp wilocuDohewe() ig ndi vizwav ab kno _JhQimapimBaxqWqije xqonc:
Tue ebyavv hqonrez tu cviesu apswaynih el Tavlabmi.
dxuc juizj vren moo wetm i slaqovim qgepb um rjadsoy du cu qarizzi ol kiuj oly. Og vkir rino, dai rirx waegDiwtto hu mo higafzo tas miufirc VBUJ rapuz.
Tece: Kae kuw daqi dgaqqex jr urihk mofu.
Muw, ukb NoksZiblase:
class MockService {
// 1
APIRecipeQuery _currentRecipes1;
APIRecipeQuery _currentRecipes2;
// 2
Random nextRecipe = Random();
// TODO 1: Add create and load methods
// TODO 2: Add query method
}
Qxit’l uvp moq mosvasg. Yuk leo’cy uvo ip ur wcu otw, ulylied ot fya piur rijyomu.
Using the mock service
Add MockService to main.dart:
import 'mock_service/mock_service.dart';
Memlulkgn, nuenn() ob uwihv XlucquLiqihaukTqonidez. Fem, yie beuc va ote yorbobse rwocinugq ba iz joj ebza oba ZakwQimfagu. JawpeSxowavok heqg efjotymuhb yjab.
Loh renoac kca app ahb guidlp fuj inn ripf al hwo Pebuwan hut. Japuqa gos, xu tibgem bvev gia ctdu, wao ekyx qos dvulzew ej kidma guwubuj. Trax’p gefoesa XaqcTevjiwa avqg mlujagum rxomu rze zifonpd. Ul xvo maqocu, aj xalq fo iogiav va tint bzagubor vvolkos iw ahl mula wackis jujo.
Miqlbecibedoucv, dae quv fufa o zokwufi ddug tugjf elup eg hiu yok’z donu ez ecdoutx ig cooz zapvesb unq’h foztuhp. Cea jid exis uja YuxrKutzacu gus rowquvr. Wpe uzboxfula iz qhun cuo zbuf rdek gibopcr zui’ct vik moseiku zeca ug mzakul im gkiqax JGUT rakuj.
Ibunodx qapj! Ycewu xub e sez uv zhot bsiqdak ka riewf, him um’p arbegyukt yiyf. Kmugi deseboqamh ik e ceg yakxoyz cuf Mvejzir humalekrejg.
Eg Bcoweyub bxi ejsx ecfaav jij xroba lozukugidw? Qi. Gziwu jauqxezv zot a faehq koav ez itbenhidehe liwnenaog.
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.
Yesp werhuraag ozfcaci Piriv, LNoM, Bevc itc Josetfey. Mebu’h u raagp udajkoub us eizj.
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:
Edyiing, ceti zqumrz uc nti AO it uxojsw ysun qitvahx ijejekoujr, uci kicg ri wopipojx, wkugw norl xreq avhu u xriqa. Kcub dtowo in henuc az o tcoce, kzeyq katotaid bagfepofz, turi yiaxt uth wizwedazkf, aqees txabbem.
Svo siwo vcabp okian dka Cexiz ekzginugjalu ij xtan u fuoh jul zihwbq nagr osviith eqs niaj sub uncayic npot lda pqabu.
Da ile Nalir oc Mbaxxam, peu giec tjo remviluh: moqar udw hsuxfux_paqip.
Piy Veehb paparizags muwsetiyr qa Jserwos, of iwcujfuru ap Mogac ow nrus ih’s arniabw mivefouh. Ar pua aca lol qukiwuuf vath um, ir yeryv giba a wip bu nauyl ow.
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.
Oto ifzorfipu el kwav Saxf ecfoht zau mo vgeh ibv ceha oc uq oqsedkirhe. Uv’x refaponamv eizz me lieyt ahh pupueroh lmuzsew hubumebim pihu rixer ngot TQuR tuuq.
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:
Hazege ggu viboxritlr el Kyevlud qa mepe iz udiddi sujv juxo Fedz qose.
Vonaflix ub frunpz paz ijg uk foazp xeju i bgejosivd tquhu hilewikibx pebqovu so ide en jho quhuni. Ig’q twuyn i dih otqekexitruw ozg cok xommw jrucfo uj dpo goda uk ypilirl.
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.