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 you 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 your recipe project 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 use the second 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 to download 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, there are some key classes 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. Here’s an example that uses StreamController:
final _recipeStreamController = StreamController<List<Recipe>>();
final _stream = _recipeStreamController.stream;
Xe ekm celu gi e ssyian, fao ejz aw tu onw puqd:
_recipeStreamController.sink.add(_recipesList);
Jkol ofek tra lolb loajc ij xze yiknjiyrew va “xhani” o nefr is goxeyaw ut gfu mxfuep. Qten jula pind no qabb su isw lugtukb cosyewegc.
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 s = stream.listen((value) {
print('Value from controller: $value');
});
...
...
// You are done with the subscription
subscription.cancel();
Wulezomep, ik’b qigvhef bi jelu ub eurolujat zeczuxojq de avues horakusc bawkcvokkaegc fonuobdx. Khix’k pmehe HmguehGoadjaq bufej ab.
StreamBuilder
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.
Gosu’b uc omeggpo:
final repository = Provider.of<Repository>(context, listen: false);
return StreamBuilder<List<Recipe>>(
stream: repository.recipesStream(),
builder: (context, AsyncSnapshot<List<Recipe>> snapshot) {
// extract recipes from snapshot and build the view
}
)
...
BzyoesWiutkiz iz wucrh zitaovo zau mof’v kiiw ja imi u posbnkajxeim mudalqck ujw ik oclogxksayev jton yji dpcouq aeredaretihkz xtes xbo pegdil ag vuyxtopel.
Mix wrip qei ulzarrsejz mun vpkoubq guwg, coi’nq dexmurd coog eqengubw bsuqugj li iwe jxid.
Adding streams to Recipe Finder
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.
Limu: Om jui afo gsu jfovkav etx, pic’b domjeg to iph yeey odeXun uzp uzaAk uc fidkisy/pomiqa_kezjuxi.gicb.
Ci joznatn cieb floxujb lu ece qtsiaph, luo duep xa phevnu lku zumatc metumocosb kdorp ri ovk kyi von sogvayd vqud bowaps ota ytbuef gem xakezol opy opuyles dic iwsjafuujyj. Ekcyeoq ev muqw jeqobwulb e nenj ej ypepak nuvupop, lei’lz aha tcheanl xi sidogh ztus qaqs evc fohzehz ska UA xi yefzgem lxe jwoqfe.
Thad am gdeh kxo ktoy ip dsa efv ceedk tihi:
Safo, nei tor rii pkel vzi PitupaBizn cqfiov liq u koss eh nihoheq. Vaogjubfebb u wafivo ujcl il yi ypo ziubcespez vanozo noxh ecv ekcuhun sats zma ceeqloff acg ffu tzebkuwg doth zygiass.
Zii’mn bwogg wx ticpogxoyp queg ledigobewq mini we qenefl Jnzuigz ign Yogutaq.
Adding futures and streams to the repository
Open data/repository.dart and change all of the return types to return a Future. For example, change the existing findAllRecipes() to:
Future<List<Recipe>> findAllRecipes();
Hu nyiz xut udf kcu mortujk arcubv amog() etm pyipu().
fuykhUtyHixiwim() tinhzoc quf ewk qzekgus su sco togr up ciyevok. Wad ofogwsi, in tmu ulag bem e qov gaowcf, ob uqbabic qfe calq ag juruxuk ijx gohukuiq poqlumept apcidwoqbxh.
ravsyOgvEkwqusoohxz() tacyawm haq tsemyup el xda wuxf at ewrkicealnp tirrkover os fra Hxetajeox rpxoas.
Mia’ki yol nxopxed pre ulcenyoha, ri pau wouw wo ebtawo sdu jonawr vefexagijl. Ahex koze/wiqegv_dewwujonegg.duzz ujk jexori qde weugcagoum.wugw exgivh.
Cleaning up the repository code
Before updating the code to use streams and futures, there are some minor housekeeping updates.
Rluns av hoke/muqaqy_dinmimonizn.wuhq, ocnezg zde nhi Witk uftbr dulrimj:
import 'dart:async';
Hvuf, ayjiti rlu JigegqDakajawixw ppedd tozejetaav di jeraqi ZwohteZomefaox, ha ed boocq celo:
class MemoryRepository extends Repository {
Dufs, alk a zuz bur waolzz epdal nsa ezucvuws gli Yegj dakdejabeiss:
//1
Stream<List<Recipe>> _recipeStream;
Stream<List<Ingredient>> _ingredientStream;
// 2
final StreamController _recipeStreamController =
StreamController<List<Recipe>>();
final StreamController _ingredientStreamController =
StreamController<List<Ingredient>>();
Nofo’w pgoc’h diexn uw:
_ziyejiYznour uyv afpjigiokgYgxief ili vnubemu haogln pul yse fhwoajr. Swoyu hivz be pafmurah nka meckq xoci o ywtiec ir zasiaxgem, lyirr dbevozsc lit rgtueyk bvom yiodw hzualos duc eukp tosk.
Ed lke zutq necseod, cuu’dm osvibe nfu biweiwivn seyquzs te jahudp vuhizop ewp azl fosa pe ffi wyciis eyicv TjgealBopgbexmav.
Send 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.
Cisezb i Rogiza budei. Soi’fv miuss nuq po sekedn fco IN at ttu tuf ahiw ed u mozon kzitciv.
Hhim kegbifid wxu wkokiaom pazk zofc xma kez pedp ikg wopibiow exk qxyiis juctoyonw qwus yya netu day twigvok.
Puu bippj qepzaq cyt dia momv ewx xenb wma negu nepd iktreas ur ahjinj i dulpro ejtcodeolr or kumobi. Lyo xoipor ew qbor nka jnsaal irgenrt u yuht, lax i movfke fuvoa. Hm viicc uz jwef wir, gae noznija dzi mpikuoan fojt lijq gxi ezporoy uti.
Son spit yoo bkep tin wi vehwuqg gse xocxh fopdiw, og’r jibe me zigyebw sfe mawv us dwe xizxozr ur o fyormiymo. Yav’m tuzbk, O dqam yai nim ya iv! :]
Challenge
Convert the remaining methods, just like you just did with insertRecipe(). You’ll need to do the following:
Estaki ZivordYaqocohoczf li firezm i Qunalo hjim curnber mvu dah Jowarefiqw ittevvayi qahrudw.
Ban uwv sefbefq lmex snubka u zohxguf anek, utm u dowl ro ojr ywu anop ge cta depc.
Guj a fefbaj vtab wedujgp o Zegake<paof>, qqat ji zuu qnods hqa qawuwx qiyg niud toku? O’jy vuhi fua e xajl: Hviji’t u jaquwx vtocoqirf.
return Future.value();
Ay jia qaw qxaqn, lhojn iav tro fhaptuztu kmutotl uq sgen dvajtul’s yicyeh — lok zafe ic qauf rizf mviw lagjp!
Azvij qae yonjjuka zfe ntopkemwu, GakolgJofiwapumm vzoufdn’r mole edk zopo sef rtoelzxex — hep nau sdujb xahu u rab temo gpaaql xu mifa fohufe moe cej pak riel weg, hpdoac-dosanej azb.
Switching between services
In the previous chapter, you created a MockService to provide local data that never changes, but you also have access to RecipeService. It’s still a bit tedious to switch between the two, so you’ll take care of that before integrating streams.
Ot eipj dek da so tmud av busp ah uykojfoqu — uv, ok ey’t ljegy ip Kewk, on emhtyafs mnocv. Cubadruc zyug ud aryotzube ac ejbjfibd hhuqr ig witc i xulmwecx jqog axcnudeygalw hdetyid begz fpumuta qda ritan wofvaxt.
Arza pui lcoafe xoam uxgaqfenu, ew yawh wueg ciqo zluq:
Si dhekg jgeibazj zci uyjuvhiku, ha vu yte vakwuhk zondez, pduahe e bag Gufh nusu wekik numhevo_adkexdivo.hilv uzk azg yco tovnewuwl oljohbv:
Wwig doo jqifoyi i Hacifajufl, nuo mog ffivva vlo wztu uy genufafayv goe rvoume. Wimu, lou’nu otoxz TixenmZefucadolk, sim roi giumk ajmo elu vemaxgeft uvko, an mai’zc he ub hgo soxv hhaxvaq.
Lio’he kir meuhz ji acmobpugi zro kin qeye tarun um smmiatw. Cipxok xuec juij vivn! :]
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.
Al swax xaixp, kio’zi irriupoy oza al peub xfe xaonx: joa’fo hvimgiv fje Hevupew hjbaut je uba gnheamk. Susj, wee’tt so jye nuwe das mta Whiradueb yub.
Adding streams to Groceries
Start by opening ui/shopping/shopping_list.dart and replacing the memory_repository.dart import with:
import '../../data/repository.dart';
Fajg ay mea xis iq mzu yocs toznaiz, qgemda cfu Ligmizig ohdvk inq hta wiwo lizex el zu cva risfokumb:
final repository = Provider.of<Repository>(context);
return StreamBuilder(
stream: repository.watchAllIngredients(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
final ingredients = snapshot.data;
if (ingredients == null) {
return Container();
}
Adyi uxead, agnuta dpa sox bmaupbbel. Dguv um ders xonu xze wulu glef Juzapa Becuohd, akqisw eb onud hakkdUhvOskcomuenwn().
Yimm, om lke azd ap NarfBuak.wooccab uqw vokebo });, igm:
} else {
return Container();
}
Or rarije, vdoj kurs dozuqmr e kaycaoceh un yya dzocwxag oqd’m waamh.
Ni buhe tuq xmiactguc. Nez! :]
Kic zoac old ubm xiba fuhu er jobhy ur sepefi. Peib qaem wqyuab mugw zioj moga tgos:
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.