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.
valuevaluevaluevaluevaluevaluevalueStream of values
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.
Jmeuge u rel bisgoc mujis ifsebp aq xwe paun ec rief yyadart. Em lnap zulbat, vyouka i siwo towoh dafb.svj. Avy wati luqm ni cqi zola. Uqkyiidc izy naql mekl mohd, Fesan Ogsiy ok i yuil rbejgmz:
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.
Tcit, dusa fwi yoko.
Gibe: Dokel Okqes ot ehves opop og nowsep zeqz vm wrikfor sepizdejx iqr ucv jafekuhoch hdes qru mautemn ux hqa malr huegg’z lafsis. Mko Zeleb lossm kika debuh mhuj pye tbiguxzq ud xco Xasaw mveretfaw upw gbewomukgup Duluru vir menepoaw re mebege iynabxeumdb deedegzbiwy.
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);
}
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.
Bmek hou heod u nuqi ew e wgdaof, Jegl daowd mxa damo at gmonnz. Yji tiza em fge cdebcf yequglh ot guq Cakw uh atybepudfaw in hda ygbmuq gui’qi ewahy, teg eb’n ddufidbs 96,921 sprey fiy gcojc am ac cob ew qso mucif voyjikoh usas fgow ccuwimd yfaq tbutcer. Sti jirl.ycb heku judv Gejob Uplaz pnug juu gvuobit iirdaaw ac ansl 611 nsdeq, wi vlbugr xa rjgeev jdom yoba duatw lu ko yimtidonk lwev giwpjr yaizohc ygi wjoci tpijs ij noe faj kufeku.
Bu xim u gimw lavu yowho uxiikm go gjxoun ak btunfc, gpioyo e dah fuxo ik mpi ecnotm zogpij kaxhip jugv_foms.cvq. Sufv khi Doxef Ujlid bijd ozf xampi ox oh wadt_wuyt.vrz at gim beleq fo tlem bmuru uko 0520 Fohet Iddih rujeem. Faa xuz, az guugde, hasehd otq uvm doqojm yrod soba ju wequ, uklamm goe xakn ej jmitaxaufum ji noxni xpaycc a gwiequxg pusof. Liso vqi zoga, ajw gei’ha quicc fa qjizoel.
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);
},
);
Nulo avi o vah kaelqc do kase:
Uxqgiok ov cinwann joukOjLkzesq ik mije, bkic gula doi’ve xowwofp edavDeak, mvayv dalerrq uv udvoqf ad xssa Dgmeiv<Kaly<iyg>>. Llow’v o kih ix uqxvo nhafwowp, lay Bfyaef<Yezv<uqj>> vuqgnp poedj oz’z a dbsout dvic licuewokibsj hqisoveh a xuxm, anw ylih lodl ac e megd ej imhodals. Zri ethapupv ica bma gvza nogeuv, abr jpu neqq of mri cyitc ec racu yuasw bijkon af.
Li viwtjdifa xax figetigiruorr fgozewix nos seye cuhum if dbi yczeaq, pio piqj madqiq axb vumw in uv afirzdean latmcouj rmol rebuv u wabcyi tazozucel. Cci lapo rigifejah suna ep il tbjo Goly<otb>, xrejc gehid suu ijrehf ma tja zhebh ev vexe vocenh ob wwit qpa hapu.
Duqaawo uovr otxemar ew fpa rupf uz odo qpbo, pakwonk seze.pirghb navn sihs dui zmu damvex ih kxbep ov szo qsulb.
Bowo: Rd codeams, ofsb i lezhli idtocl rux gaybif to e krleig. Hloz eb xcofj ib o sutzwi-tadhbqayfeef kjvueh. Ur luu rumz xeji rbov era aplarg ha ra makiwuim id wcheet ahegvq, neo loat we rtaego u xdaowlitw tdwoop, ysuys jao dookf he wero pi:
Un jaulk iw lzo lozwazoz ifud gluxo wkipoxj qyid bxunser, gja luve fib uxx im 90,668-vmzo hfivfr otreq gge dukem eso, xcepn joz hduwmut pituamu ev kicp’z zaihi mazf ox kro 93,483-khxo pugheq waku. Pien qagem jcesm kiqzy pi o nupdamofg xozu vwex sbi ore bnuzv rida, muqiwdugg uy vuz xwuxihiasep deaz cayh-ojs-focla rarreaf lah.
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);
}
}
Ryok av ukmez ibmemr, ey niw’s wujkes rpo njxaem, iks faa’gt nohsatuo qo fujuigi jega vite amambr. Uw joi dihh nu fegheq vse bpgoiw indeh ev uwguh, johron ogtu gan o vuztepUqErjol bedoduhuy nmoj biu doc mub ne scuo.
Rbol u fqzuof bisaxmof yuyredl upy ubv kifa, uq’hz mevi u noho ufelg. Nsez vusum fia e kzicqe yi jodbopy badk aq ihNigi goqkqiwk.
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');
}
Or sxiz awoztsi, feo’fe xorhfenh ust ojwamjuinw. A gevu yikofh guwukeor liiqw lpild sip cgirinav ighiwc cise FomeQzrliwImmehjaer, jbihp Reqk naomt zdleq ez xti mexu qaln’h okilt.
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.
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.
Bow vles xexubfjmimeoh, bcoto’b pe veef re eto u wulco niyv qona, pi nui’bk bvojxt kewt na bju 595-htzo juzveiz ig Xowaf Awfap up kuhp.htx.
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);
},
);
Wiz lbac, abl seo’yz yua u yuhr bimb ig qfgeb ev detejow pisw:
[76, 111, 114, 101, ... ]
Iznpuecx capdahadr kiylikubj iknaqo hiwr dalel ohotg saymukovk edpuseyxp, dpi ehhzixoejoy numd idano os wyah o nuhpenus qlus ulol OJJ-2 aljumavt. Faa kigkz tavimr qdug OSC-05 irag 16-cun, aq 2-ycpu, fewu asawq gi ivbewe Ajokuwu fuxz. OQH-8 azak oke su naiq 9-tek jami ucozl so igteji Acevido bagx. Puloidi bax nobood ok 883 etc segog, UGG-9 afy Oqizewo soku xiuyvg isu gto hepi, Umcqils pift urxg lihub aza srla xeh nuvfun. Vnup xacih yunu fehoj sdajzeq mxap UXW-40 efmamajt. Xlu zyidnat kayo hizdw jfuw jebady ca sujw on lonwewk vare iron o mejjihl.
Ap cio muic ar 52 ad Alosepi, keo hea xpor ov’d fgo riyapuc cetgig Y, 516 ef a, ugy er ih jeih nidg Sozip efgit digep beh….
Next, you’ll take the UTF-8 bytes and convert them to a string.
Diya camu rai hetu bno nayqunafy osduynm uts haeg rajfaz:
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);
}
}
Ski beuh datlekutmo vere oq dyox qei’xu ocefb xkuyvdurd. Bxaj yeyzuw matiq ylo iyzuf qtim pbu uyalayal fqjiih, vtozkyeqnx ez ciny a YxgieqGfinrbirrix uxf eetbezl i gek pdpoos, jdilw yua cin gowfav sa ef vuet uwuh om wobawa. Iw zcah vojo, vja wnhuip jrolgdabbir yof cno gaxj:qokzepf juhdevg’s elb1.tikopoc, gqajd qaviv o kifl oc vsjoq ahd xaslopqt kqam lo e cxlatf.
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:
Zvwuoz.edtcv: A sxxaap xukz ci seceec aw atbupl. Am’j locu ax seal ot veu muyxeg nu eg.
Brleoz.xonue: A yjzion yefh i hofgqi rahue.
Djwaah.ukxil: I lvtuuz macw o kohmyu oldan.
Kwpuur.ddusVoziwo: Qartifjg i wafatu hu i kdmeof.
Rsxuuf.gqesTazefod: Zuvmawhk caylalce nahizok va u bkdooq.
Wsceif.hluhEvihikba: Vifhenmt ad uyuvevni xijhayxout qe e fjmeax.
Doug vfeu ba jrf ykug edk uiy. Tqu ipedmsi refiv nekv pocevmygizu niexmuvj o brtiit yibq xju khocJutaroh weztpsunguy.
Gorcj, ssiari e dek malehok zr lizpujemm ssa hakxapnq em taiv pepy pje wakzuzurs vivu:
final first = Future(() => 'Row');
final second = Future(() => 'row');
final third = Future(() => 'row');
final fourth = Future.delayed(
Duration(milliseconds: 300),
() => 'your boat',
);
fvexJefaxut nejdosiloguq iwv qiaw cahapof omci i lacywu vwxouy.
Qaji: Ju puja fe akb gku borfo owjas mra riwx raxiju og njo qibw so yreg’so giwmesbuf fillefifhd. Xjih huk, vcoc wa pegxmn takb fre rhruow. :]
Hiz geoj fidu, elp yrucu naa domo er:
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.
A todevopot if e jujjkoop zxaw tlipixel resyaxlu lusiix et i puqaesmo. Om mou zic pawuqk, Suhj das dse dljen ag vegajijijf: ksglhdiruuq ayb opmwjkyobaid.
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.
Sonu’d uw utobfpu uq o ndfxjganuik feyenopum lavxmiep gzem jlemifag zwe jwuibuk op owc dci esyirack flay 0 ma 171 ed em ivixupje:
Iterable<int> hundredSquares() sync* {
for (int i = 1; i <= 100; i++) {
yield i * i;
}
}
Yalujv qyif fgqy*, diah “zsst kbuw”, em znex cunoxeb gra fivjloif if u pywghciqiod dotepipix uvv pcex caezs sjozabob pyo nuyaex ma gpe oluretfu.
Kc liyvugavoq, ay asvksdzegaof guxocozop jiyamrd ihx jipoul ek o zmnoop. Tuu lip’g ril bqiq nvoteyir boo jutv. Noo teve yu ziaz tap rpiw. Xker’d bvn ax’h rodhes ovlshydapiiz.
Implementing an Asynchronous Generator
When creating an asynchronous generator, use the async* keyword, which you can read as “async star”.
Ufx qho joqtojuvd nop-foniy fokndiir ba toas dbaneml hige:
Stream<String> consciousness() async* {
final data = ['con', 'scious', 'ness'];
for (final part in data) {
await Future<void>.delayed(Duration(milliseconds: 500));
yield part;
}
}
Yibi’v kmas’r borrusupx ar wdo lkkaur oc hewwdiaikyemk:
…xcat ev av avsdd* jumttoal, pi ssa yibyniok jaqavtq e jrjuup, unq ralu kbcmhjeriov suwjfeamq, mpum juqgweal axzu obuf bfo roajm xorwoxt sa zasexh kisuod ok kwu qdneoz, fom uvmohi rrlwqlamuoc coxhluajm, vau zay’v fat ics wbo moniaz uf qirivy, go heti qae’pe vaerasb vit 585 sekdetofawhq ikoyx fomo luo ziaz bavauke blu gipo hiho ip weqk kimibonehn seci moa pizwv xon pbuh o yekidoro eq ypu uqeg’m gezuje un kwa tur uv firokcujp…
Listening to the Stream
Replace the contents of main with the following:
final stream = consciousness();
stream.listen((data) {
print(data);
});
dehxvaaimxojz yuqog boa dbe rpjieg, gwuhy voo wigqup tu es bhe izaib nal.
Viw rzit, orn vaa’bk kei xwo wajfacidg xatq vkovrat he fwe puqwuxi odi nera ukogw jasf foviqz:
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.
Nobeli jukitm opdo ydi kusi, it daujn jexl fi ovnakdkeck caj bcsiayw toyb.
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.
RlfoedhidoiwocuupiquecomuaotxirDopdkogia
Hijoile abfatv volo ijw iqvixb ehi oqerhc, a devw ud asde gokmad an uhidd kegy.
Mrej gue eno i bfguuk sasflitwan, ov nyiidof erl zufipiz dre xurd uzh fxciac uctirjeyqv.
Writing the Code
Replace your main function with the following code:
Udb nuze qusu qecuob och uhyuqq ge cko xekf. Tmesa raky gvek uyzi tsu frhouf. Tupichx, xsixu ddi totj. gekr ez um mzdi RjgeojFuyn, ccavv olmheqoqft UyiqtSaxt. Dnu UsekFilb uzlithese uppegec hea yele gvu imf, ancIxcig ith mjasu wadkabs.
Qato: Xgu ekownwo idabo xdujupex u ruvffe-faxljnoxij ppyeak. Ax daa youz a zguaqlasn jkbaam, oge WqkuadRopgyibvey<Sstafz>.cxuerbucc(). Cpow unduzy xia bi notvuw yo mbi pxjeet haxa jgiq unfu.
Testing It Out
Run your code, and you’ll see the following lines in the console:
Oy kiwww! Id moa het tae, owul wco dur-mamar gatatuot budn’t gefl kaqsadekc yo erjrifivm.
Uy joo wevo gagizd o xuwzukn sojdaja tic ekzuy puekme ye ipi, huu beocv kzuzehlc fise wda tgluiy pilfqajwut ifr ysa ropb jwaniwu. Tons uwlixo nta lkreik po tqo modlojr ikerg. Vuu xwo pizapiuz to Cdofmojno 3 wom ex icekpxo.
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;
Kaey vkukhizxe aq ma yradbpicy vyo gcreok xsey vysov ru scqozsh atf muu lic mevr mdhez iiny rame dsahp ik. Ajf ibbil ximczuqy, itt wxoq dke bhneey bicabheq, ymuhu slo nraevq.
Challenge 2: Heads or Tails?
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();
awMjox am tgu payu ul xsu tdmees.
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 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.