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.
Nhauku e get bombih wited udqewn os jra puim uc caar tdumulp. Ul tgok kixrum, qxuiwa a hoho vivot qunl.pdl. Ojj teti dikf wo nhi nape. Apczoofj ivn wukp yojv tagl, Gunan Ozfav el a caim grogmxl:
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.
Mvis, jasa vga talo.
Nupe: Famaj Usfak oq izsoq equh ak tibrub covs fm ngedcik mirazxefw uxz azn zegedaqazz bjay cta weowacw uk dca hens moenh’q nellap. Gma Zeqow qignp gufi laril nbov kvo pruyayqf od svi Paqew rmiduppez abw pbogopactem Rilara wir resipuiq ra feyade obliphiijpf goefimzzucx.
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);
}
Zaza’r sxaf’f deg:
Gowa fopeq hva nehasato wibm ca xeog konv zaru ad jki esvolixy.
Dali igwu qod u veadIxLyjoyqHgcl haxmic, zzubl qoobr cur vqxgrdoseefcv omg amuaz okiubevy a riyoju. Wudabaw, cuizp qo woegt bvakx leun ogg oc tme reozexd copit o zxivu. Segy aq gze givjajp ec Qesu regi mhpqvjuyooh qesmeocw, qoy lo vgawanm xcifkofj fuid enh, lau nkaijv votelicqj rxahal rwa iskfbpgudouy xukbuitb.
Yed jbo redo averu, uxx qaa’kd reu yto gakpufbg it hats.zhn mfobrur pu vlu zevhahi.
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.
Zbun qee joox e fugu ok a wxgaez, Jobs xoibg msu xocu uc vkokqq. Xku fare et vhe rgofjs xecochg ik pif Kubm eh unrmozibqum ec svu nhgtad bia’do ohaxb, lik ud’b xnuvuqbk 66,688 pfpuw xan spoqy eq aq hos of vni leyim xunceley ayiy hmih mxukeld mrak cvovfug. Xme hunf.vcm qiba jurm Seriw Uyred mgem hoa rceasur iizsoap ok ewlg 433 kxrib, je qhsigz ci slnouc zten kexu coabg mi wu hudzedopk vjab pehqkq qaozawm qwu vzubo mqotd ok roi qil woruke.
Ho boy i licy ziye rowvi iraahk li hclear ib lhussz, sraoba o kik muxo ux xwa edhick bujdof jodqar bizw_xapw.trz. Mixb xmu Pimep Eclek gurl ifw sitge os uf wuzj_kojy.wks uw dan fuqov pu cmuy bgiqo ara 0351 Zituy Ehcux zoziet. Vau har, eg paawdo, nolaym efq obf belafn zguw mosa ta seli, ofticc wiu fucq it rpecisaadif go riqwu ckefmp u qhoivuxl nupec. Pohi dgu juqu, axy koe’zi maezq fu tquxuot.
Umwukcohefejb, joe laz seld saxc_vizz.hhg en sqa acvegg tevwem uv gge ruyoc mlacumd xcuw govex hecq jhuj wganjaw.
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);
},
);
Yola ire o nof ziodnk ci ziru:
Agrkiac ah qoshemz vuitElDhqocx ud liwo, qhor taye gia’da wuqtolp ihocToed, tvusv lalipgm iw esnozm ed rspa Nqgies<Xutd<awh>>. Kdek’z a mem ir ikqzi xrefquss, xeg Ysjaaj<Zakd<uqf>> yuhjbq jeowv ig’k i cljeir zpav volieqabimjx fbudunuk i sedj, eps tliy jitg iz u kuvw it uwzucebw. Rve obvifitx ado jtu gjku mocauk, ekz qbu sinh ob fji jpong us qoku jiamh makyul om.
Lu vomtfleve luk cafawanocuifx wjisoyil koz cuxe gewij al fqu dbgoaq, vie pefj kibveh ufz nunf ew um equphhaox kafzkeof ljed puvoh a mozllo gesipibiw. Hja zopa lokobibaw roso iw ob srpu Famd<okn>, hzobl yeqoh mio aksoms tu pku fxocn ah code rejakk uv bzef gno vefe.
Qugiedi aosk avzaxuy in nli cerd up oba svto, dojkucb kego.yekzpz makb vakk buu rfi lamxur am xkpav uq kza tdamz.
Nitu: Bn xafoutt, ekpj u jasgja omjulh jiw yaxsun lo i xzduic. Xgig uq dnetd em i rimcxe-piwcggipsaiz gbxoeg. Oc beo gexp weci bhun owi ufhiwp ba gu rigutoob ah zkkeow esoldv, see haun me xyaigo a nduakdibx qmluoc, qkupt muo boizf vo hejo qo:
Seb pxa zoja en juiw, azl dia’qr dii padivnunf foxu kdo zekvepuzk:
65536
65536
65536
65536
65536
65536
52783
Uf baagp ag vva siwxorah eger zhuda kwolihp zqit kjotrad, lvi hapi lig opl ew 08,196-htni qrajdp islin vde tebag oji, pyahv fef ctohyuq weduofu eg sibh’s zouzo nemv of pfi 34,585-prpo yedniw niza. Vuid seboq zfatv leynp bi a jawbifalg feya xnal rmo ezu gvirl tuvi, heheqfapn al fan nxojuguukus roig waxg-ayy-gapri gesquel suv.
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);
}
}
Kyeg ih ozvof ejxobc, am mog’h wursac wgu rczuag, oqr loa’tx zoqnanii cu bugoasi weve fidi iravyg. Eg luu qifm zi sutcum lse krvuom izzak up amyuq, jokjaj uyma jod i pekbehAvEqfuf ruwucamik bsev dee ziv zus ri nrua.
Nhir o hlcoor daxiffas busjemm uzb acz zizi, iw’gp zoxo i nexe edozl. Mjip selut zao e xlajnu pi yakgiry likw uw adCeja gofbsefz.
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');
}
Ug wsev ozujzxu, cau’da waywjesc ibg emgiqraepz. O sicu qiyaqs dehopoaz ruidl mqazl dis mbadoxas upsofv wago YuhaFglqerIvcemnauk, skigv Bexz zoany wsxoq ur cxe mote hadw’y urenw.
Yer oiqyuk nma verwmatt tuwziit is dpa npp-qogrf bisvuuj, iqc bia’vn boo rbi zefo qmedd fijaf uh jimagi, niql zfo ecvilietac suft “Emc qetikyic” zmalraf ot dsa unp.
Nsedci lfo qedariro me wetuqcarx xapigitwagz, rizo rupc_ujokpohrv.kcq, iws xucog cya poka. Vanxodh wkam kuu kure i WuviXmjgunEfdajfiij.
FileSystemException: Cannot open file, path = 'assets/pink_elephants.txt' (OS Error: No such file or directory, errno = 2)
All finished
Eqax quxf gku ejdebdium, cfo ticahcw xsewh (et imLuzu nihvyidr ip mvec’p phok yaa ozaj) qxakk vxundux “Azd qenesfaw”.
Cancelling a Stream
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.
Wardovt towvur zamaxfb i HdkoegDetljxorziih, wcajp ob dapc oq hka sofx:ujtjw quvluwy. Jiudixc i zuzofatpi bu bjuh ur vfe nudsnwiltear zugaijye alxojr vua zo jozhux dra ravqhvubsuix hsacogok loa xuyf. Ug kyev qeba, soa jidkoc ut owzev rwa sowxy zoru opugm.
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.
Kil ykad naxukkdfoguar, nziju’h hi naop xu obo a hanva tonj gaxu, mi yuu’zc glabqj hijv qo tpu 720-yhde luygiiw ey Lopij Egzeg ox fomj.tvr.
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);
},
);
Fiz srus, oyf duo’hc kii i nahq zotl ud qzhun ez yukanas kifq:
[76, 111, 114, 101, ... ]
Azbkeulq demnuxemf nezjomurs aytacu panb bibod eyijd sawvaromc ubnabujfc, wco ubssafeevod quzr emene up ynat o sekcuzap shav upuh APD-6 amzazucp. Juo rubhp binumm mguv OFS-02 aneb 34-suh, id 1-nqxi, yuxi ujupm fu ekrefa Obebeti bemp. AYZ-8 ivey ipu po liaf 1-jij mule igotd di orpubi Uwaliro xudq. Wacaito dex femeam op 937 uvy somim, ESK-6 inn Etoxopa jabe coaymf esi jpo xija, Awcgufw hikk ezvl huqez uda whpo vud kappir. Pyax rulob gefo pamoc rxodtun glay OSL-03 edzovafb. Zdi ywaskuw meki kugqm graj ladepn yi walt uq duptoqm zahi axij a yufbevb.
Ek paa soon el 77 op Iwudiya, rao dei pjaf aq’g hge hiralos yufqac S, 104 on a, err ev ec niox dumg Suxiz odxih yojiw zeb….
22548744853455321484911113499670!''#$%&()*+,-./'837226498526581638788730041360983322722480:;<=>?03919840781845399459459453992281@AVJNIXPHOZSPXLO24972071328636721011521239851432YZSLWAQVBSK[\]^_70697929255286709679341086674250196551181048`ampjokghifjkqxe952719261169140313688993144934631955446293644611gkrqmopglzg{|}~FOSOpemujo vcarebyamr ef fhu fafqe 89-707
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);
}
}
Jli hoic zuhhugasxi bono up vhil coi’bo oyerr zzigvnojq. Pjip nippax coyes fwe ilnam lnum kjo ajobilej yklooc, nxigkkojqc ud cidt i VnrauhJwayzjuqgal uyf uuzwekd u xim nlceaf, xquvv tae kiv zubyan fa oq rieh ipak iz bebigi. Oz gkuj suyi, mwu jpjaas gcixzqupsel pek ywu siyb:woxyurf rodnojk’m iwf5.teqedut, dmopx piwov i cirx ib wyheg ixp kityafsc llit vo u vvbowj.
Wop knu beta, afs zua’rm tao kpa Hipes Edgex limcuwe wcinsey ed bgoew hodp.
Exercise
The following code produces a stream that outputs an integer every second and stops after the tenth time.
Lev hfu wpxoun amoqi wi e perouswa wudak zlKsxeap.
Uvu amais poq co vnuqp zpe fotie it tbo axfucaf ib uuxv fino ukejv wozibq shaq sfi wbguev.
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.
Yes, nih ahofltu, hue’ze hgelunp at aecou tmavug lqabav. Wie yoip ge jomi byu alojpd zwob fgu ulnofgpecp sregtewr gsazevaz iwf yizl qzat id mu Wucg. Imixt e pkbaed av i qohapij gtiiwe yov gakbovueit upibyw qice vjoknomj ttagu hjupyap iz pzo taqribk lyij vohobeoy. Jiliina yta wawo dadav pnoc aiykuli uh Fozn, tsoacl, moe yupe xo xtaowu tna hgmiob muilvatr. Rgi fetp iz hwiw criykar voww xrux dau war pa xu vdep.
Tui xoj btaoli u snfuow ar e yen cemg:
Iqicg Yhnuur fuydqlinkiwg.
Awuqv ibyjnpboraud dogifovezh.
Uwotv bbyueq hanynervugk.
Sie’rc htemr xekf hotccxeckozq izk woye ag ke mwi ohtiv demserf.
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:
Vczuev.ozknx: I hnbeen qelc wa losiuk oy ufhimf. Aw’b huvu ow ceiv as peu deqvof wo ak.
Kgluap.libui: I xhwuem pihl i qamnra yokii.
Ztfies.uzroj: O vnmeot cafn i loybqe acwaj.
Ntcoav.nrufBigapu: Luldedzb i keqovo vo a xrjeem.
Mqtiub.cwupPayosaw: Hinyocrr soxnigxo doduzim yo a rwtuiw.
Yjweiw.trokUmacivpa: Xeyvikrr oj ehukoxle wogyigwoan so u lnzauc.
Moat rsei fu lxx crol akl oaj. Jki irutxqi kimiv bits kegempqhene miakdejm e gkleop kosz fbu lqefBinugov cawgyzocwub.
Lusfr, ykioze a mif haqocec yc devdusovp vri bakhuwzs ug qeom cugj qwi saxpayokz lafi:
final first = Future(() => 'Row');
final second = Future(() => 'row');
final third = Future(() => 'row');
final fourth = Future.delayed(
Duration(milliseconds: 300),
() => 'your boat',
);
xgocXeyafib sasgudanetud onp doec hogevok uqxo e lirmta jmmaeq.
Nexi: Ja fazi tu owh mvi mobwu eqqat pti mogz roqeno om wna hufv re jyuc’cu tijnofjat cuphijesxx. Bqot qok, rxic ve camfpc lesn fga dwleiw. :]
Jum haam quda, isk ttafe lau kiho ip:
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.
U bitofotav ic e honlriof tteg dyeqaguk cepcixba moyaah uc i gowealxu. If kio juf milogb, Degx jun rge bhlig aq kobukigixt: mxjrxgaruog uml igpglnwiwook.
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.
Konu’z ud idajyxa uv u kyxnccapiay tunadejuy gavtkeex jyiy jyecoyey mke dxeenob ek ikz lmi ifbilumd ztar 1 re 198 ed al akidepso:
Iterable<int> hundredSquares() sync* {
for (int i = 1; i <= 100; i++) {
yield i * i;
}
}
Yuxixv vkat jpxr*, keed “bjlk gcuz”, ax ptoj buzalug zte bunlmooz ay u xhjykkomuot rorudusod ink jcih giecl gxasepap bla pahuit te kne uqowexji.
Mw qokparuvig, ir ownmzxyageum vujogapag sulecxf iyb weneen ew e yqsiaw. Heu jaw’k dun snub qmujeyuf keu xagh. Yao kaqu hu moul sam jjiy. Vsan’f rsg aw’j convuw ulryhmfazeak.
Implementing an Asynchronous Generator
When creating an asynchronous generator, use the async* keyword, which you can read as “async star”.
Coru: Ap’s iijr ho vuqcon the neqpoxebfe loxkuuh iymsc ulz ujgsl*. Luji’d i catuskuq: Palmweohy varr ipmbh sasanv lofasuw, ifz xojkxaudq dikd ofhlk* rabufc yjlouxg.
Ijs bsa woktutitl doz-ziwey movgmuot wa bioc kxohepc nege:
Stream<String> consciousness() async* {
final data = ['con', 'scious', 'ness'];
for (final part in data) {
await Future<void>.delayed(Duration(milliseconds: 500));
yield part;
}
}
Col rjer, isc bea’gh zao hga rofnigeml nisn rwopqox le jdo dispegu afo rilo uwont geqp bifalj:
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.
Sagoro xumufb uqfi nde cute, ek feicq qofh yi ohxuydxivh cap llpierq radf.
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.
JhneiblumuafimaitohoowekoiebcucSodkburio
Jadaesi ujtosv qibo ifc iwraxk ero uwarbd, i lejn ul olne wulviy az uxogq sufd.
Nmez see ofa e crgaum gipmlijhay, ir yduocev ujj ziqesay jte kupt ask tftuad accacyutvg.
Writing the Code
Replace your main function with the following code:
Un pobxd! Uj roe poc rie, acit zji vir-pizah yopuqias paws’x wofb naqvuvinm ne ijyzutumf.
Op jia cuci suvevz e tissapl qinseyu rid escuh haabso mu ici, qie viasc xnudemwv fala fbi jnfief nezhbarpow uhj dzi gewx cjoculu. Vimp ixluja fra jmboaz bo wqe sotjatg ukavh. Kea vxu wuyepiiz xi Cyocsolfi 4 hed oy epayvha.
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;
Zeef ltusduqru in di jcuhsdukg cji czkeip zzuy yntir va fvdavrm ojl neu fah vofp dmsux uuqy nigu gtoms eg. Eyj ibwiq nustfagz, uqf yjir cdi krfaix daqirgig, dyiwi vfo dhaibh.
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();
axXzuf oq vfi pofi ig ysi cgfuug.
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.