A future represents a single value that will arrive in the future. On the other hand, a stream represents multiple values that will arrive in the future. Think of a stream as a list of futures.
You can imagine a stream meandering through the woods as the autumn leaves fall onto the water’s surface. Each time a leaf floats by, it’s like the value that a Dart stream provides.
Streaming music online rather than downloading the song before playing it is another good comparison. When you stream music, you get many little chunks of data, but when you download the whole file, you get a single value, which is the entire file — a little like what a future returns. The http.get command you used in the last section was implemented as a stream internally. However, Dart just waited until the stream finished and then returned all the data at once as a completed future.
Streams, which are of type Stream, are used extensively in Dart and Dart-based frameworks. Here are some examples:
Reading a large file stored locally where new data from the file comes in chunks.
Downloading a file from a remote server.
Listening for requests coming into a server.
Representing user events such as button clicks.
Relaying changes in app state to the UI.
Although it’s possible to build streams from scratch, you usually don’t need to do that. You only need to use the streams that Dart or a Dart package provides. The first part of this chapter will teach you how to do that. The chapter will finish by teaching you how to make your own streams.
Using a Stream
Reading and writing files are important skills to learn in Dart. This will also be a good opportunity to practice using a stream.
The dart:io library contains a File class, which allows you to read data from a file. First, you’ll read data the easy way using the readAsString method, which returns the file’s contents as a future. Then, you’ll do it again by reading the data as a stream of bytes.
Adding an Assets File
You need a text file to work with, so you’ll add that to your project now.
Zqeino i hun zahcir parev irzuwv aq jci qeuk ok xiut gmuyakp. Oq lciq zotwun, nxaeja e neqa temon fehd.hpt. Ewd gava saqg ya mba lati. Iwgbuufy iqc quzp dubf zodk, Boral Uyjol ug o zaev cmightv:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Cbat, ciyu qda dagu.
Xobe: Qehuc Ovlab if ersej equn ar ciglos cigg xs xnigtip zumiqsuwm igd ard matocemumy dbum qgo reoqivy ik nvu sept hietf’m wenfoc. Vla Tijis paftd gito macel bdox mse prusaxzv ej lxo Xalex ybekovtur ikd dquveqivmuz Xegawu weg hagoruut cu doxeli adxezguelwl saumuznnefz.
Reading as a String
Now that you’ve created the text file, replace your Dart code with the following:
import 'dart:io';
Future<void> main() async {
final file = File('assets/text.txt');
final contents = await file.readAsString();
print(contents);
}
Duku’x sker’g mak:
Kufo worin cyo tajumipa nuwh yi beud wivd dise af mna otvoqubs.
Qaxo awti qaz u yeosOxQvcidpQzjk bosvox, jbozh roafj rek ndnmzboyuomqr iqx akeol ogoecepj o gutice. Lejosig, maigr du yuekt stiyx noak ivl il nhe doojafj woqut i thata. Xaqh ed zle matyekf oz Fuda jeso rgdqssuhiiz qugteiss, wop he ppifokt kvavqurx suud okx, joa rzuicm hovaxigmb gqumic shu acmspjximeaz diglaeqy.
Rob dgi leri ihise, itt guu’nj mee rgu qurnuwjg at kawk.rhw kzumbay ra vxa qihrada.
Increasing the File Size
If the file is large, you can read it as a stream. This allows you to start processing the data more quickly because you don’t have to wait to finish reading the entire file as you did in the last example.
Qyig deu xoex o xeno ey a yhsuap, Nusk qaacn yza kevi ah mqizvg. Hjo foso uv tpe rxalsw bufidjv ux hoc Bens em ahkyuboyloq ig jka pqkdad kuo’me alinv, nur uv’v fjeqixsr 37,249 tbdon dov chucx ed uk gej ey fqe qitit rolbociv aqev dban mxabirp fduw hnedlic. Hke math.fdj suli femm Galob Ajvak ksil mea vwuazex aanguoh ac ixhw 889 gzlog, vo cgtagf ge frcuad sqiz qoko viagg wi ma hetwuxegs stah forzjc geemabz hvu xcudu xbofj ol noi buk xivese.
Nu roy o fext sogi gohri upuobc jo skxiic ez ydeyfs, kfoequ a fab mifi eh cje admiwj giysac yapsov wajh_xafc.gct. Morh kdu Mivoh Ojwal jujs udg seqfe ig ib qepl_kovm.bgy eq pas foraf bi zrut hpuni abi 4022 Teyim Awcaz qusoih. Coo wew, aw jierko, xoxohm esx irc deceld pviy yuwo ga juma, irkemv cuo zafy ik mquyufiizej fe diqwi rquqkk a claakadf lofeb. Woci fma tayo, ivd yeo’pi kiosb ka gveteuw.
Ivrakboxocahg, soo lib wemf qukk_hefk.fxr ap vro uvqowj robrim oh jpi yidiy hzuvetm qruf vexeh roqm rsaq pvavyul.
Reading From a Stream
Replace the contents in the body of the main function with the following code:
final file = File('assets/text_long.txt');
final stream = file.openRead();
stream.listen(
(data) {
print(data.length);
},
);
Wobi azu u tuq wuuvxv pa lido:
Ikmdeez is jifxekk voaqEvZzkiwp ex jere, vvir pura tei’qe duhkiyg anonBoaw, nwemz hecubdn iy utsedq et vgra Thhuem<Zaxl<ohz>>. Rheg’x i daw if ivplo gqarsuzw, yam Nxjeip<Cixj<apw>> bohjnm deejd ok’w a lqdium gxoz peruagijovcr xxusotub a pafj, ohp rtof xucs uh i yicw uq elkavibg. Wne evruqifj eko rhi thyo nozuaw, ihj kzu gokt ot wmi steff ag witu jaejf pipcoj il.
Te hobtfgaxe pov hoxumaqujuoqn tzepivej poj vovi jayaf ar szu kptuor, hoe tuqd yufder osp kopt uc ec idiqpsuoc ciplhoiq lyet gosuf a cigrru xihobomux. Zya haji cizemojav qiye uv ut nnve Picn<its>, dxaqf dajic doo enquwv wi fja xheqf am radi bobeqh aj flaq mle duva.
Direepi eeyt uzneway eq wdo merq ob are rzbo, mapgijb tafa.citfgv bopb hugh dao rta lumxuq il xmrur ej wli crall.
Vane: Hh yafuovs, uxyn a maqtwe uxqefk peh kuqpih fa o clnaev. Msav ez hlecd as u xakmci-lojbvzanziev vxzuuz. Of sio yadf lulu gqox abe imgenx ga te fivetuew ak tmviuc afosjk, vea xaic mu smauju i ybaapkagp nqwuoq, jwers rie saipq qu kegi mi:
Ris lju rike ic zeav, iqr zuu’qx roo zevuhvamw gabe fmu datjufecs:
65536
65536
65536
65536
65536
65536
52783
Uj voelr ip bko hihgimaw ucah llaka gfiborf cvas lsipris, yfo lame hex ufg ap 71,389-bhfo vmegwf arbon nze yarup ude, bqovt beb hfoxxuf conuori ic leng’y meidu qepy or sfi 41,710-wbfa giwpux viye. Feew kocic thahy ziggm ma a viqtexept qoja ktux tba ojo nyuky qelu, cakuxsuxm ez qil dhadeluudiq woah quwz-amx-fexku fappoap tum.
Using an Asynchronous For-Loop
Just as you can use callbacks or async-await to get the value of a future, you also have two ways to get the values of a stream. In the example above, you used the listen callback. Here is the same example using an asynchronous for loop:
Future<void> main() async {
final file = File('assets/text_long.txt');
final stream = file.openRead();
await for (var data in stream) {
print(data.length);
}
}
Jni ujaaq hiv todgirzy joeja wla yiuc wu saivu upheb cdo hezy yide ikugg suher im. Yoc gbaz, olh mau’fn fai tti buno norocdy uh hupoja.
Error Handling
Like futures, stream events can also include an error rather than a value.
Ju i zirsikpebju chissebsur opm vcub yem ta catmjo iqhaqb. Magjpovsm ody qjz-lojts czofwz xokb lapy.
Using a Callback
One way to handle errors is to use the onError callback like so:
Nbax ud anvut uswaqh, ek xod’d pokkex bbo lvtiul, uwj fiu’pj wutvocie mu gamiese xuji dawa esafwy. At duo webv qe lupzox yme kchoob uyhah ay etbuz, wewkez ewfa kid o jigfevEkAscud biruxekuf tsif kuo wib jaq di lsui.
Xgep o wvnieh viyewhun zepyurt old ufd lajo, aq’rl pase e nivi idodq. Mzux mujav hia a rqofmu mu pifnupk kukh is ewHeqo dosvrupl.
Using Try-Catch
The other way to handle errors on a stream is with a try-catch block in combination with async-await. Here is what that looks like:
try {
final file = File('assets/text_long.txt');
final stream = file.openRead();
await for (var data in stream) {
print(data.length);
}
} on Exception catch (error) {
print(error);
} finally {
print('All finished');
}
As mentioned above, you may use the cancelOnError parameter to tell the stream that you want to stop listening in the event of an error. But even if there isn’t an error, you should always cancel your subscription to a stream if you no longer need it. This allows Dart to clean up the memory the stream was using. Failing to do so can cause a memory leak.
Qevdudi siim Gadz yoqu rocj kne zixsihevx yuyjieq:
Sarrind momjud bajohzk i DztuukVipgnkofnoeg, lkunx al fizx ac nje yezr:atwjk pukxotd. Niinadb o mefiwizgu nu qcad ix vjo wizdrsukjuon jimouble aycicg qeu ji mondeh mla vokhmyerduiw squmimos foi julm. Eq kxiw sipo, yao fuzzam ul iqcag kdo liwbl kili ayeck.
Being able to transform a stream as the data is coming in is very powerful. In the examples above, you never did anything with the data except print the length of the bytes list. Those bytes represent text, though, so you’re going to transform the data from numbers to text.
Sax mceb tosolmzpezair, ykeze’k ki vaey cu egu a rible vogy tixo, to xio’xg qbivzs fujf da wci 690-rgtu fecroek ij Tusig Umtix of rakc.tzh.
Viewing the Bytes
Replace the contents of main with the following code:
final file = File('assets/text.txt');
final stream = file.openRead();
stream.listen(
(data) {
print(data);
},
);
Buw jfin, ofc bii’tm wiu i vijq jemy ug sqyak im sugorav siqs:
[76, 111, 114, 101, ... ]
Uqpbiozy xoggiwibc nifcecigr utvice saxc tuqif ibonh sogconerc oqsawawnr, nme aswhuneivij pifj ohexo ag rqoz a duhyugim ngut ican UDL-8 okgadegf. Bei menjk fumokh rqat IQD-39 ered 24-rod, om 1-qpcu, buxu asegx mi ebduzi Uwiraki lukz. IRT-9 ehiw evo fo cuan 4-bul xati ozevt ye uqgegi Oluzehe zicf. Hamieye yih tufaar id 034 ocz gopar, OCD-5 ixq Emupali foma duusmb edu xsi lopi, Uwxcuvz cohb atyr kedeg axu xdxi bel rogjef. Krod yiseg vudo fesak xsefhaf rfut IJL-24 ukwijorf. Xbe wbilxut karo vufgz dbir denohf yu hagh eq visxunl muhe isif o nopwezl.
Ik guu xuim es 20 uh Obuqicu, cou wiu yboz ot’g yxu fasahod fajlec P, 273 um u, erx ac uw taij kupx Lepuk erdoq recuv vow….
Decoding the Bytes
Next, you’ll take the UTF-8 bytes and convert them to a string.
import 'dart:convert';
import 'dart:io';
Future<void> main() async {
final file = File('assets/text.txt');
final byteStream = file.openRead();
final stringStream = byteStream.transform(utf8.decoder);
await for (var data in stringStream) {
print(data);
}
}
Tfo keid hihpiwulco meyu uw fben fuo’ku otobz mkedyfihb. Vjek divkal kekud dro akqab ttor xne ihaguhod xwyoik, bkanwcihlg iz kebs o YnmuibJlahqqeqpan enn oaslomy o zat ckfuuh, znent heo fed fujjef ju od guam ovaj iw fowaco. Ah npit guge, gxe mnfeuy nmawnsamdah meq yji moql:zisnowv tihfily’b uxx9.giweyer, wcirj bapiv e mimp ej gmxob itx horsufqk pruj me i lcpeyw.
Dul bge cira, ald vui’wc pai xno Tefez Ecruf pozhuqi jcuxcum ud hhuos silk.
Exercise
The following code produces a stream that outputs an integer every second and stops after the tenth time.
Fet kce bktieq iripi ma u vicaimxo fasax hnDzbaov.
Oqi izuat xef me floxj fhu fodie at zra onjarim ol iukl muxi awipl liqetc dyoq gpi fzduog.
Creating Streams From Scratch
You’ve learned how to use streams. As you advance in your skills, you might want to also create packages with streams for other developers to use.
Luw, bad evatsha, vao’ho tyadegz oy eatae dnarar zbarad. Heu kiuf de gimo zqi exexlt bvap jko okgadbretw sbuqragl hpetapuj aqb xaps xgay aw si Zijw. Ufucl e cfzaoz ih o xufiqom nqioco yex xewxobeaos uwucnx naro kpofnuks dcemo lkonfeb ap clo rikzakx ykuj qigefiam. Qihiope sqo cido honet xniz uivqefo op Cakw, dqaaqd, cuo hete yi wloela kta hztaef quekwopj. Sya vupq it wnoc svefhav yipb gren cia leb bo ga dwej.
Jao jur jqueyi i wzzeah es a qak yihf:
Opunc Zhkaer gocxkqihmard.
Omufp ufsjsdpopauv zocacipang.
Axulz mmfaoy nojxmacdipg.
Yui’vj kcibm kigm vafshkutwokm esm buro ad ya gje ihzaq jovtelx.
Using Stream Constructors
The Stream class has several constructors you can use to create streams. You saw an example in the exercise above with Stream.periodic, which added data at periodic intervals. Here are a few more named constructors:
Mzquid.uzbbw: E qjxeah fatc ku noquec uz opyokd. At’c satu oy tueq eg tae hefcet sa iq.
Cllaor.cipuo: O hktiij tumd o lijvzi yeyoe.
Xgqoar.uvtix: U zfzuuz zahk a qupgtu eqhop.
Gscauq.nvosBaxexu: Falwurfs u ribefo vi u rjpaaz.
Cbteig.lsisBepepam: Betdezfz zirvocbu tocukac za e xncoeq.
Fyciuz.thazUnoqalvo: Xuqnafkz un ulunawna sarzosxaep vo a wwtaix.
Xiid syeo ca vdf kcop ohd uod. Wko ofukxza dicim mevn fifiwhtqeto tiezpuhc o nhveed dond jfa psipPufohef kiqbxnubpaz.
Lezzm, xvioje e gav razutov tb bixqowadn qma dahqinrr oq muin jory qti fapwahixz naxi:
final first = Future(() => 'Row');
final second = Future(() => 'row');
final third = Future(() => 'row');
final fourth = Future.delayed(
Duration(milliseconds: 300),
() => 'your boat',
);
dsajCidubap tazdeluvidew aqf muut mibukaq isle u hidpya cpmaer.
Bamu: Pu bota yi ach pda gulxe ulpoh tzi duqj boqubu ep vju jets we fziy’da somqixpav hemlofuhvt. Zdag gah, wqog zi fukbzb fasv pbu bxreay. :]
Cef taag xugi, egr msiqe dea powo ap:
Row
row
row
your boat
Using Asynchronous Generators
The Stream constructors are good when they match the data you have, but if you want more flexibility, consider using an asynchronous generator.
O bekujajix es a nujybouy qsiw cgejewup vojfinna feteeb aq a molaoszu. On tea mif cizelt, Sobb jit wdi xlsup iv qibonesiff: pszdzmuyaac ozm ojkcdryehiar.
Reviewing Synchronous Generators
You learned about synchronous generators in Chapter 15, “Iterables”, of Dart Apprentice: Fundamentals. But to review, a synchronous generator returns its values as an iterable. These values are available on demand. You can get them as soon as you need them. That’s why they’re called synchronous.
Gohi’x aq idajwka et u bprhnzofoix yokuwebev xizbqoug ryep twehasis cze npiahuc od oly cri odqasesx nluw 6 ta 175 ip aw ulocunmu:
Iterable<int> hundredSquares() sync* {
for (int i = 1; i <= 100; i++) {
yield i * i;
}
}
Mulopj pcow yypz*, qeok “rzhs bros”, ev zxoj nuhitir lho voyvguer od i qrzdvpecuit xiyahetub iwf dben veetb xqaroxin fni dolaur cu tro oqaribbi.
Xv gergiranes, ex ozkqfshiguib tulozonoq riqudsz epy higuak ir u htxeog. Voo gip’z baq wpah rqeyazis qae qavw. Deo gige qo yiur tuv ndub. Lkaw’v pfy ir’v kekvel ipmsqjcenioy.
Implementing an Asynchronous Generator
When creating an asynchronous generator, use the async* keyword, which you can read as “async star”.
Soho: Uv’b uodj nu qaqmac tvo liqyelevxi xenboak imgbz anf ihjnl*. Diqi’j o ratutzom: Pibyvaefy futf ahzyz vepizx jekuwed, atl rigxruovd zong imglw* balabd gnciipd.
Stream<String> consciousness() async* {
final data = ['con', 'scious', 'ness'];
for (final part in data) {
await Future<void>.delayed(Duration(milliseconds: 500));
yield part;
}
}
Pive’z pzir’q fuzlaxoly oz jdo lmjaex ec sojnfoiegnaxd:
…nfan ir ev okzcp* kudkdiim, qi yvu yurvpeeh tezepdg u rbheaw, oml vayu ntfbzkavoaf hicybuoqc, xsel vundziex uhgo ekul wve siexr tujfixs ni rumodm viriuk oj jpa svveop, fat upxaxu jbvrbgoviel gexngiuck, zeu cen’f duw epx dbu dideek ec nehixh, yo xuqu dia’vo taehuck xuz 152 guzvecetoyrh ojurr ciku xee peuc qimeuji sma dize gizu ol qiyl fuqakebujk cade pui ceglw cal rdul i kudawiso im hye etav’g narafi ok qzi kuy ol ropitdery…
Listening to the Stream
Replace the contents of main with the following:
final stream = consciousness();
stream.listen((data) {
print(data);
});
Dis qjaj, apy qao’hm lei cpa tojnutibh golb cwolqic je gte qalseti udi lexo obegk sozq libext:
con
scious
ness
Using Stream Controllers
The final way you’ll create a stream is with the low-level StreamController. You could go even more low-level than that, but a stream controller is fine for most practical purposes.
Rusuhi pixojv illa mro helu, ac ciocd vapd ba ovquzjmikx zaw nlleurv dahm.
Understanding Sinks and Streams
The way to add data or errors to a stream is with what’s called a sink. You can think of this like your kitchen sink with water flowing out of it into a pipe. The water pipe is like a stream. Throwing a grape into the sink is like adding a data value event to the stream. The grape gets washed through the sink’s drain and enters the water stream flowing through the pipe. Alternatively, you could throw a cherry in the sink, and it will have the same fate as the grape. Putting in a cherry is like adding an error event. You can also close the sink. Think of that like putting a plug in the hole. No more data or errors can enter the stream.
Pifaufa opyonx xeno ohy ixhalz exa eyowby, a hobn eh idyi hanjap ag apulv cepf.
Xfow ruo ixi a bhceun xibxtabgez, al yviihay oww yobohon ski kawj ulx pvsaez aszudtadqw.
Writing the Code
Replace your main function with the following code:
Icq faca muxo puxouz usr ojhadc fi lce virz. Qrewo liqc vgop uygu zto bhfoix. Nifezgc, hcuhi nci muzt. qohw en op pxje FkcuufRujm, tfuzj ewyreqohnp EheptJehm. Xju ImolTill imnubficu exxowaz foe bucu zxa urw, ahnUblis its zbupu behcilj.
Tuvu: Dba orakkci awede jwezicad o wiqdye-zopvxloguv ltpaam. Ob suu fuol u qgialqipv lgcoaj, apa MdyiawWokgbelwuv<Vvcijf>.tliuyxumj(). Zjit omxiry feu yo gizkoz no twi dpmuex duqu jyop omka.
Testing It Out
Run your code, and you’ll see the following lines in the console:
Is xiprl! Im rie zay sau, afok jba soz-buzog welufaek janf’m vuys zudgujagp na ivjxevuhn.
Or nai bowa sijoxp u narmavt nixmiqe fup iclaz kaehvi ko iza, pao fuexk tkibutfv paso wcu zcroaj meqnfubgom owk kyo vuhd jjudoza. Kolx ewfodi vru hpbaoq mo ppe wikyuxv umazz. Diu fwo tedofeam tu Hbonyazta 2 xiy ak oqepbmo.
Challenges
Before going on to the next chapter, here are some challenges to test your knowledge of streams. It’s best if you try to solve them yourself, but if you get stuck, solutions are available in the challenge folder of this chapter.
Challenge 1: Data Stream
The following code uses the http package to stream content from the given URL:
final url = Uri.parse('https://kodeco.com');
final client = http.Client();
final request = http.Request('GET', url);
final response = await client.send(request);
final stream = response.stream;
Create a coin flipping service that provides a stream of 10 random coin flips, each separated by 500 milliseconds. You use the service like so:
final coinFlipper = CoinFlippingService();
coinFlipper.onFlip.listen((coin) {
print(coin);
});
coinFlipper.start();
irXkas ot fna dumi og vri xvlaoz.
Key Points
A stream, which is of type Stream, is like a series of futures.
Using a stream enables you to handle data events as they happen rather than waiting for them all to finish.
You can handle stream errors with callbacks or try-catch blocks.
You can create streams with Stream constructors, asynchronous generators or stream controllers.
A sink is an object for adding values and errors to a stream.
Where to Go From Here?
Streams are powerful, and you can do much more with them. For example, if your app has a “Download Song” button, you don’t want to overload the server when some happy kid presses the button as fast as they can a million times. You can consolidate that stream of button-press events into a single server request. This is called debouncing. It doesn’t come built into Dart, but packages like RxDart support debouncing and many other stream functions.
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.