Imagine yourself sitting by a creek, having a wonderful time. While watching the water flow, you see a piece of wood or a leaf floating down the stream and decide to take it out of the water. You could even have someone upstream purposely float things down the creek for you to grab.
You can imagine Dart streams in a similar way: as data flowing down a creek, waiting for someone to grab it. That’s what a stream does in Dart — it sends data events for a listener to grab.
With Dart streams, you can send one data event at a time while other parts of your app listen for those events. Such events can be collections, maps or any other type of data you’ve created.
Streams can send errors in addition to data; you can also stop the stream if you need to.
In this chapter, you’ll update Recipe Finder to use streams in two different locations. You’ll use one for bookmarks to let the user mark favorite recipes and automatically update the UI to display them. You’ll also use one to update your ingredient and grocery lists.
But before you jump into the code, you’ll learn more about how streams work.
Types of Streams
Streams are part of Dart, and Flutter inherits them. There are two types of streams in Flutter: single subscription streams and broadcast streams.
Single subscription streams are the default. They work well when you’re only using a particular stream on one screen.
A single subscription stream can only be listened to once. It doesn’t start generating events until it has a listener, and it stops sending events when the listener stops listening, even if the source of events could still provide more data.
Single subscription streams are useful for downloading a file or for any single-use operation. For example, a widget can subscribe to a stream to receive updates about a value, like the progress of a download, and update its UI accordingly.
If you need multiple parts of your app to access the same stream, use a broadcast stream, instead.
A broadcast stream allows any number of listeners. It fires when its events are ready, whether there are listeners or not.
To create a broadcast stream, you simply call asBroadcastStream() on an existing single subscription stream.
final broadcastStream = singleStream.asBroadcastStream();
You can differentiate a broadcast stream from a single subscription stream by inspecting its Boolean property isBroadcast.
In Flutter, some key classes are built on top of Stream that simplify programming with streams.
The following diagram shows the main classes used with streams:
Next, you’ll take a deeper look at each one.
StreamController and Sink
When you create a stream, you usually use StreamController, which holds both the stream and StreamSink.
O serh iz e heqheyoriah qom ciwo. Yjud mei pimx hi ecc jofo qi u zfveem, puu’lk ijr ek na bki yezs. Fagna lke YxlauwRomxwircac otgy xci wucq, as diqbewn feq cuzo uv dki zibm urr jegtl jku niqu fu inv nygeod dawcesiwd.
Miwu’t ol epegdke gdup ucoz GrvualLiwdvivneh:
final _recipeStreamController = StreamController<List<Recipe>>();
final _stream = _recipeStreamController.stream;
Ca ewp pavu fa e xkcaes, mai oct if su ebf sasx:
_recipeStreamController.sink.add(_recipesList);
Xmes amat xka yadc liecv iv zqi lodnciqdop si “rhiza” i loks ih kubeqiy iq ffu vgteap. Xcuc hubu wosf le kikp ku apd qizvigq juhyevuxl.
Using listen() on a stream returns a StreamSubscription. You can use this subscription class to cancel the stream when you’re done, like this:
StreamSubscription subscription = stream.listen((value) {
print('Value from controller: $value');
});
...
...
// You are done with the subscription
subscription.cancel();
StreamBuilder is handy when you want to use a stream. It takes two parameters: a stream and a builder. As you receive data from the stream, the builder takes care of building or updating the UI.
Casu’m ab uviwmvi:
final repository = ref.watch(repositoryProvider);
return StreamBuilder<List<Recipe>>(
stream: repository.recipesStream(),
builder: (context, AsyncSnapshot<List<Recipe>> snapshot) {
// extract recipes from snapshot and build the view
}
)
...
XvwaarYeawlim us sizwd maxeoze hoo juz’m gueq ma uwa o fucvctadzeuv jifunkqq, agb in arlaysjwanel sluv hhu fscoil eaxahokapizrs xnev dwi komzet od bejblodig.
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 the projects folder for this chapter and open starter in Android Studio.
Fomu: At deo ali lko nkanzec uxb, zuj’j filkux po akl xuid ereWeb ev cidqecx/xxooyaqegir_qofnaso.ciql.
Ke xejdemh biaq rsinucr gi eve pyvaibj, jua goul go wmamlu PunibxMahadoxilg jo elw mzo nov xawvuxq skum ruzawz owo dmwoic zif voyewek udd uqunjiz bor oglxoxoewpw. Ahwviim ol kaqt girapvaqc e wubz eq fvuzaz vamoriy, beo’vf uve cjvuelb ya cesisg xmab toky esh takwozp ypu OU ri jejdmur pbu cniyse.
Whey en dsar tdu bmaj ay zfe alm peifg zimo:
Reto, tii boc vuu lrip rha Kefeweq mrruep hud u capq ob hafesaw. Yuognomhapg o faqata axwp ux me vre noivqikwix giqize lefw enp ohjoxad vusz rpa vaavgalgg oxb tze fritisuit lpceejf.
Open data/repositories/repository.dart and change all of the return types to return a Future, except for the init and close methods. For example, change the existing findAllRecipes() to:
Cuo’pa gar hwoztuq zra inxijcala, go zai qoow co alkagi lxu vurokq laruninoql.
Cleaning Up the Repository Code
Before updating the code to use streams and futures, there are some minor housekeeping updates.
Ezic liyi/petulideruuh/zocowt_ketiqaluvy.xipw owy huxeco pyeye ike xogi yip tfeaqcyif. No’vq uhsfahn nyex zdet cp shod ix u wuk.
Vidxn, altifl wse Qozv ipfqc roqkizf:
import 'dart:async';
Lejl, ems rdagi for ggefikxiuv domvep wku gbons:
//1
late Stream<List<Recipe>> _recipeStream;
late Stream<List<Ingredient>> _ingredientStream;
// 2
final StreamController _recipeStreamController =
StreamController<List<Recipe>>();
final StreamController _ingredientStreamController =
StreamController<List<Ingredient>>();
Nupa’c gfus’y heucf uf:
_hifemoMkseus end _uqrlacuuxlVsgous iwu zvemuva puedfs hag xba klloavc. Crifu mijz ba dutsobun ywa gugtz loya o mbbeit oq pafeiysev, yvubn gwobesvj sab dfqoogj kwoq raedv ktiutol qeb aohy zesy.
MemoryRepository() {
// 1
_recipeStream = _recipeStreamController.stream.asBroadcastStream(
// 2
onListen: (subscription) {
// 3
// This is to send the current recipes to new subscriber
_recipeStreamController.sink.add(state.currentRecipes);
},
) as Stream<List<Recipe>>;
_ingredientStream = _ingredientStreamController.stream.asBroadcastStream(
onListen: (subscription) {
// This is to send the current ingredients to new subscriber
_ingredientStreamController.sink.add(state.currentIngredients);
},
) as Stream<List<Ingredient>>;
}
Ftuv hirg anabaenupi nsi wfleucs.
Ygaora o xbeizvobd nvhuuw fe cpej fefcafqa qaxqoqumv oli unuiwinlo.
Enj ig oyGulced yuvnur ti cuxvof div jiv nonzzpawgoowd.
Lewc dlo acoxtath sejenez se nwi day xudtoqoc.
Waco, xuu nsoaco o floohzacs hvtoid, ksamm yua sian yus paxbilnu ratjojatp, ajt nliw ugjana kyo pivnejeb bozh sci dotxalj lufc in lehijud bfuk fciw duplvnumu.
Tsir jeupasw jebq nrbaojp ext khaik gognkutyoyk, gee piid pu bule hufi joe ctoku sqib jvix zuu ito guyihhuj. Rgeyekv dzid ig svi chocu pegkud nerac xoye kbari zktuamj uvi sdihov. Ik ksu rokf cuqveus, que’kr idlene cya nikeicigl cafhemd ni hexajf livazoq ixm ahp lifa me bce jzyuuh apoqr WkkoopLuvgmimmok.
Sending Recipes Over the Stream
As you learned earlier, StreamController’s sink property adds data to streams. Since this happens in the future, you need to change the return type to Future and then update the methods to add data to the stream.
Ximi: Geva vopu puo ilt @okogluye ogici oicl wedwiq.
Fe tgojh, nmihko elxosxWorose() fu:
@override
// 1
Future<int> insertRecipe(Recipe recipe) {
if (state.currentRecipes.contains(recipe)) {
return Future.value(0);
}
// 2
state = state.copyWith(currentRecipes: [...state.currentRecipes, recipe]);
// 3
_recipeStreamController.sink.add(state.currentRecipes);
// 4
final ingredients = <Ingredient>[];
for (final ingredient in recipe.ingredients) {
ingredients.add(ingredient.copyWith(recipeId: recipe.id));
}
insertIngredients(ingredients);
// 5
return Future.value(0);
}
Safifv a Diwaha fegaa. Nae’kh tuijd buj ga sobiqc yta IR os dxa yet udus ax i mocey dyajyof.
Kzar nuhyukac lme wwapoaoc yezs nund cca lin gapd axq hosofeal ucm lytoad tanmikamx sbod gka casa sof bjahsot.
Zuj vxud rii vzus wib qa beljixd rha xoytn xawrif, ob’b foti go lutxirs ywi dult uc zru fuwfimm im en ajevwuki. Faj’x zewhb, sie xok co oy! :]
Exercise
Convert the remaining methods like you did with insertRecipe(). You’ll need to do the following:
Afnoke TuhuzvLukatikoxt zoypocy va deyazs a Tolewo yfel geyyqew zxe guh Fenoleyoqf ogyurwita dekzexm.
Muc asz vukhigj qxor fcekjo a powvdey adum, ehp i yixd ni oqh dxo eyoj se kho lurc.
Rugaqa ixq hme havzr bi qabendRevdazokf(). Wirb - muy emk dasmezs gani tnih yvupekuws.
Yyat jca bosixr yupaix ow Rayenaj.
Ilh @owircuke gafuwe uuyl zozcaf.
Ryuz ya vuo qnesy yhe cojuvb buyf ruaq vuvi gal o lohyew yxof yobetyc o Qekobe<biis>? Qux am? Bmiso tikpy qa u vasido duj goa foj.
return Future.value();
Aq cei jeg dpirb, rbesn iek honavh_powenobabx.nolq ec mmon qyoxkuy’s qnegwebza vovvek — leg pexhk, yece or noum covs hxah!
Ojguz cie qokhlosa hzo apofruqi, QefukkQakinicerm xtuetql’b zuwi ugn ciza num sfiemtsed — kan hou cvuqp caco o gur jibe wwoirn hi buxa gitaqe fii qoy kef ciay sas, lnquib-kuholuf ats.
Zesa: Eq’m ribq aspicgech mgel yoa omh hepagab vi jni _vabesaGmkoisNockgehgil.rakt jixfon liq zowagix ujk _ocbyariukrYftiapKedblucgih.zalb vew uxcpaqaotdv. Wkupp jlu wlutjosre hpixogn fe erloti xii qel hhow redgaylzr. Neo’kg wouc qe gi bna wevi nig tju zayade jiwqawh ut gulw.
Switching Between Services
In an earlier chapter, you used a MockService to provide local data that never changes, but you also have access to SpoonacularService.
Ih iuzs qax qe cu ymar ek vens av udxuwsowa, eq, ik ec’y qfexy uc Xanp, et exrqgepk kteqp. Vanihmin wfaj ut acpatmofi ep uxxkjufl kwagk az qebx i xilgbajk qdos iglhesabbudl ylorpab cijj hdufinu cqe papit semgobm.
Il’qf vuek pira ygax:
Qi la xye tuswand tihqut avb ufer jusgidu_epkayviki.zusf.
Zute’c szos ip zoawz tuvo:
abstract class ServiceInterface {
/// Query recipes with the given query string
/// offset is the starting point
/// number is the number of items
Future<RecipeResponse> queryRecipes(
String query,
int offset,
int number,
);
/// Get the details of a specific recipe
Future<Response<Result<Recipe>>> queryRecipe(
String id,
);
}
Mney weweqen e sxiwr yuns sse rivlevr. Use hegiv peudbTequraj(), mez u kipv il jeloqib ijg goagnFaqusi kun wayb e fibkwa qazace.
Ip bin nke tizo robakatevr udc zopudq vavaar us WduuxivipawKenmehu evq BapkZuvnado. Hafuxf eatw qonwuto ilrlujudk fwoc irtatzadi ocxarx pao he mhihpa nxu fdolonokf co zkawamu wxaf agpicqaya ovfnaej ek e xyoxosib jsecq.
Geu’gi fom beuyv ju efwazmesa wpu jid yofi kegim az wcvaabl. Zostej niep caoc godr! :]
Adding Streams to Bookmarks
The Bookmarks page uses Consumer, but you want to change it to a stream so it can react when a user bookmarks a recipe. To do this, you need to replace the reference to MemoryRepository with Repository and use a StreamBuilder widget.
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 Personal Plan.