Reactive Flutter: Stream Architectures for Flutter Apps
CC Attribution 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Egan, Brian
Interested in Flutter, but love RxJava and MVI? Curious about how to structure Flutter apps? If so, join along as we explore how to architect apps using using Dart Streams! We'll cover the basics of Streams, how to beef up their power with RxDart, and how to create a Reactive Application with these patterns. This talk will cover the Bloc pattern introduced by Google. It's similar to MVI, and helps layer your apps, allowing you to write business logic that works flawlessly across three platforms: iOS, Android, and web. Come learn a bit more about Flutter and the architecture patterns that can help give your apps a testable, reusable, and cohesive structure!
 All right. Thank you so much for having me today. Let me just start my timer so I know how long I'm taking. All right. So yeah, my name is Brian Egan. And I'm here today to talk about Flutter apps and reactive architectures for Flutter apps. And so just a little about myself, I'm currently an indie dev contract with the Google team to write the Flutter cookbook. And previously, I used to work at SoundCloud here in Berlin. And before that, I also actually worked at Zappos for a couple of years in Las Vegas as well. So my experience kind of comes with working from medium-sized teams and the types of architectures that fit well for those teams. So I think basically what, if you're kind of interested in Flutter, you might be like, oh, this is like a whole new thing. But I think we can actually take a lot of the lessons we've learned from Android and the best practices we've learned from Android and actually apply them to Flutter. So if you sort of think about the architectural goals that we've seen that work on Android, we've seen that layered architectures are really, really powerful. We've started to see the power of modularity as well of breaking our apps down into smaller parts so that independent teams can work on those parts and then sort of assemble them back together as a larger app. For us at SoundCloud, at least, testability was a huge deal. And I think that the architectures we create and that we should be thinking about for Flutter, I think if you're doing a prototype or something, maybe this isn't as important. But if you're getting an app that's got a lot of users, millions of users, maybe tens of millions of users a month, then testing is, I think, pretty crucial. And of course, we want to sort of follow the solid principles that have stood the test of time. And it needs to handle all the normal bits. We need to be able to talk to databases and web servers. And we need to respond to user interactions. So I hope this stuff doesn't sound too crazy or too foreign. And I think we can actually apply this in a reactive way to Flutter apps. So if you've sort of taken a spin of Flutter, the building blocks that Flutter gives you, pretty quickly you're going to play with the stateful widget. Now, this is actually just really the example taken when you do a Flutter create. It'll actually generate like a Dart file for you. And it'll have this exact class in there. And so all it does is you have a stateful widget and it takes in some properties. So in this case, it's going to take in a title, which is just a string, and then almost immediately it just gets out of the way and creates this companion object called a state class. And this is really where you implement all the interesting stuff. So we've got a home page state class. And inside of it, it's got a few different responsibilities. So it needs to define the data model that this component or widget will actually need to render. Then you'll have some methods to update the state as well. And so in this case, what we'll do is when someone calls this method, what we'll do is we'll just increment the counter. And then right after that, we'll also wrap this in a set state call. And that'll tell Flutter, hey, some of the data for this widget has changed, so please redraw it on the next frame. The third responsibility of this state class is that you actually render the UI itself. So in this case, you've got a build method, which returns a widget. And it essentially consumes the data that's been passed in, so it can consume the title that we've passed in, as well as the local state that's contained within the class. So in this case, we'll have a text widget, and it'll render the current counter. And we'll have a button. And when you press on that button, it will call the increment counter method that we just showed you. And then Flutter will redraw, do its normal thing. And so yeah, we're kind of rolling, right? You could also even go ahead and get crazy and start to put network calls in there if you wanted to. If you're breathing into a paper bag, just don't worry. We'll get there. So yeah, let's quickly challenge this and see, hey, are state classes a good idea for this type of logic? So at least to me, it felt like there's a lot going on in this state class when I first saw it. And so I thought, OK, let's just see how testing works. So what I was hoping to do is write a pure Dart test, just saying that the counter can be incremented. I was hoping I could just instantiate the state class directly, call the increment counter method, and then finally say, OK, I expect the counter to be one now instead of zero. The reality with Flutter is actually that if you want to put business logic type of things inside of your state class, it can get a bit trickier. Because state classes are actually bound to the Flutter framework, this means you actually need to use the function that Flutter provides called testWidgets. And this will allow you to essentially build the widget. In this case, I'm building the entire app, and that's just for convenience. Then you need to look through the widget tree, and you can say, OK, let's tap on that button. Then you can say, OK, Flutter, I know some state has changed, so please rebuild the UI. And then you can finally say, OK, can we look through the widget tree and find the text that we expect? So if we just kind of return to our architectural goals checklist, I think it's pretty clear that we're not layering stuff very well here. We're sort of putting UI logic. We're putting this actual state object itself. Sorry, not the state object, but the sort of state that this class needs all in one place. It could be considered modular. You could have your login team do their thing, and then your search team doing their thing. But it might not be the most modular. It's testable to a point, but if you need to do more intricate stuff, it's going to be difficult. It definitely doesn't follow solid principles, right? We're shoving a lot of responsibilities into one class. And you could do all the normal bits, but maybe because it's not meeting that other criteria, it's not as great. So my personal conclusion from this is that actually, this type of stuff is really good for UI or view logic, and it's perfectly fine for that kind of stuff. But it's also really cool that widget testing is built into Flutter, because it's actually a really powerful way to write unit tests for your view layer, essentially. But as I said, I think we're starting to mix a lot of logic into one class. We're mixing our UI logic, our domain logic, and if I had gotten crazy, I was even going to mix the data logic in there. And so I think you can start to see where testing more complex scenarios. So if you have interesting list transformations, for example, this type of stuff is possible, but it's going to be more difficult. And it's going to get even messier when async code is involved because of the way test widgets works. It actually does some interesting stuff to override the default async mechanisms and Dart, which make it really a really good fit for testing widgets, but maybe not testing collaborators that are async by nature. It also, I don't know if this reminds anyone else of this, but it feels like a little bit like a God activity where you're just throwing everything into a single class. And I think we've seen that over time that's really actually been a bit of a pain point for some of us as we try to refactor our apps if you're just putting away too much stuff into one class. So let's introduce some layering then, right? So this is just standard stuff. As I said, let's apply the best stuff from Android to our Flutter apps. So let's break out our UI, our domain, and our data layer. And if we want to break this out a bit further, we can do the traditional diagram where we have a view talking to a presenter. The presenter talks to some use case or interactor at the domain layer. It will talk to some data layer, in this case, a repository. And that will be responsible for fetching the data from the correct data source. And then it'll pipe all that right back up to the presenter and to the view. So if we talk about Flutter, then, what we can talk about is we can say, OK, the view layer is really the widget layer. And I'm going to be discussing a concept. So rather than a presenter, we'll just call it a block. And this stands for a business logic component. And this is, yeah, it's funny because I'm putting it in the UI layer. The Google team has called it a business logic component. I think we could debate that. But I don't know. I really see it actually as very similar to a presenter. So hopefully the stuff we look at when we get to code won't look too crazy because you'll kind of understand its purpose in the hierarchy. So if we dive in a little more, since we want this to be reactive, we want to be working with streams. And we'll talk a little bit about streams in a couple of minutes. But they essentially just represent values over time. And if you are familiar with Rx, it's just almost the exact same thing. So you can stream data to the block. And then it can go ahead and fetch any data that it needs from the use case or interactor layer, the domain layer. And then it can pipe that straight back up to the view, maybe do some transformations if necessary. We also heard a few talks this morning. And so for folks maybe watching this at home, I'd really recommend checking out some of the NBI and the Mobius talks. They're really interesting. But I think you can start to see this will actually work a lot like those architectures. So you can actually use a block in a similar way that you would use an NBI presenter. So let's take a look at how this works. So as I said, it's based on streams. And streams are, I think, really a great abstraction because they're actually baked right into Dart. So you actually get language level support for them. So there are these functions called async star functions. And they allow you to sort of create and return streams in a really nice way. The other really big benefit to this, and this was actually one reason that the Google team invented this, was because the adswords team at Google, I should say, invented this, is because streams work on every platform. So they actually share about 50% to 60% of their code between their Flutter apps, which are iOS and Android, and also they share it with their Angular Dart apps. So they're actually able to share a huge amount of code in between those platforms, similar to actually what Jake was talking about this morning with sharing code. The other nice thing is it's got deep integration into a lot of core libraries. So you're just going to run into streams in the wild. And as I said, if you're working with observables, they're really similar to observables. They are a little bit more limited than what RxJava or RxJS provides. So I've been working with another collaborator on the RxDart library. And this essentially brings more power to the stream class. So we just extend the stream class and provide additional operators. We also provide an implementation of subjects. But just to give you really what we're trying to do is give an API that really feels a lot like RxJava or RxJS with everything you would expect from a stream implementation. So of course, just like you can subscribe to observables, you can listen to streams. You can pass through callback functions. That should happen. So if the stream emits a data event, you would just do something with that, or an error event, or a done event. And you can do whatever you need to do in there. So at this point, I'm just printing stuff. It's pretty unexciting. But yeah, this is just to give you a feeling for how similar it is to observables, really. You just subscribe to these certain events, and you get going. You can also create streams in a few different ways. You have your stream constructors. So this is like if you want to create a stream from an iterable or a future, which is a nice async primitive that's also baked into Dart. If you want to get more advanced, you can do concatenation or deferring streams with RxDart. And there's quite a few other more operators or constructors that are useful. But this is just to give you a sample. The other cool thing, as I said, is you get language level support for streams in Dart. And this is a really interesting pattern, actually, I think. So in this case, we've got a function called search, and it takes in the search term and the form of a string. And what we're going to do is we're going to mark this function as being an async star function, which means that we're required to return some type of stream. And so in this case, I'm just going to make the example really silly and say, OK, we're returning a string. But I think you could imagine you could return more complex or more interesting objects from this type of function. And the cool thing is that once you're inside this function, all right, so once you're inside this function, you can actually use the yield keyword. Nice, it's working again. So every time you yield a value, it will be emitted to the listeners of this stream. And this is a really nice way, so you can write maybe a little bit less like crazy Rx code and write maybe a little more imperative code. So this search, what we'll want to do is we'll want to emit a loading event so that our UI can show loading. And then within a try catch block, what we can do is we can actually say, hey, let's go ahead and try to call our search service and assume it returns a future. And we'll just wait for that future to complete. And if it completes successfully, we'll yield that value out to the listeners of the stream. But if it fails for some reason, we can emit an error event. So I think you could think if you've worked with Rx, I think you could model this in the same way with Rx very easily. You could do a start with loading event and then on error return the error instead. But I think async functions are kind of a nice way to clean that up, actually. And the final way is with something called a stream controller, which is sort of a glue between the inputs and then outputs of a stream. So it provides a stream that you can listen to. And then it also provides what's called an event sync. And this allows you to add data to the stream or errors or tell the stream that, hey, this actually is done now. And so yeah, so you've got these three methods basically on the event sync, add, add error, and close. And those will, of course, get sent to the places you would expect the listener to receive them. So yeah, so that's really nice. And if you're used to Rx observers, so you're working with maybe a subject, it's almost the identical, right? You can kind of see that add sort of corresponds to on next, add error corresponds to on error, and close corresponds to the on complete. So this brings us back to the block pattern that we were discussing. So as I said, these stand for business logic components. And they've got a few special rules. And so this is really the reason I was kind of describing stream controllers especially is that syncs are used for inputs. So the view layer can add data to a sync. And then the block can listen to the stream of that input and react to it. And then the block will also send streams of data back to the view layer. And then the view layer can actually subscribe to those streams and update the view when that stream changes. And because this is meant to be used cross platform between Angular Dart and also Flutter apps, it's really important that you don't inject any dependencies like concrete implementations, but you inject abstract interfaces or abstract classes. And the reason you want to do that is because if you're in the browser context, you can use a browser HTTP client. If you're in Flutter, you can use what's called the IO client. Or if you're in a test scenario, you could inject a mock client, for example. So if we look at a basic block, we'll just call it a counter block. And it'll actually emit. So this is basically the same thing as we had initially. So it'll emit a stream of integers that we'll call a counter. And then to get data from the view layer, it'll provide a sync. And this is just called the increment sync. And just to be good citizens, you generally want to dispose of your syncs when you're done with them. So say, for example, you're showing a view or you're showing a screen, and then that screen should go away. You'll definitely want to clean up after yourself. So yeah, we'll just close off the sync when we're done here. And then what you can do is under the hood, actually, the block can use either a stream controller, which I just talked about. Or in this case, I'm going to use a publish subject, which comes from RxDart and actually implements the stream controller interface. And the only reason I'm doing that is because it gives you all of the Rx operators by default, rather than having to convert a stream into an observable and then use those things. So yeah, so we'll create a subject here. And then since we don't want to expose the full subject to the view layer, we'll just end the subject, I should say, also implements the sync interface. So we'll say, OK, we'll provide a sync to the view layer by just basically aliasing subject and reducing some of its capabilities. And then the counter stream, we'll just basically listen to the subject whenever someone taps on that button. And it'll just run an accumulator function. So in this case, we're using the scan keyword. And we're just adding to the previous count every time a new tap comes in, or every time the subject emits some sort of item. So the cool thing is that these are actually really testable. So Dart, as I said, because streams are baked in, you actually get support with the testing library as well. So you can, this is just a trivial example, but I think it shows actually kind of how nice some of the testing is. So you can just say, OK, I've got this stream, and I expect it to emit these values in order. And so yeah, if you're worried about maybe your UI emitting states in the wrong way, you can really easily test, like, hey, is my stream doing the right thing in the right order? You can actually also test errors as well. So you can say, hey, does this observable or this stream, does it emit an error of a certain type? Or you could provide more complex logic in here. This is exception is just a matcher, and you can write your own matchers to verify that you're getting the right thing. And so we've talked a little bit about the sort of block or the presenter layer. So what about Flutter then? Where does it come into the picture? Well, we talked about it really being the view layer. And so what it can do is it can actually render the data from a stream using a class widget called a stream builder. And so this actually takes in two parameters, a stream and a builder. It's a stream builder. Yeah, that's what it says on the box. So in this case, we'll pass through the counter stream to this widget. And then the builder function will actually return a widget that's built on. So you basically take in the data that the stream emits, and you render a widget using that data. So this is really cool because then whenever it changes, you can just create a new widget hierarchy and Flutter will take care of rendering that to the screen. You don't really need to think about setting up subscriptions or doing anything like that. The other thing is we can do is, of course, we want to be able to send data back to the block so that it can run that accumulator function and increase the count. And so what we'll do is we'll say, OK, since we've got a sync that we can use, we'll just add data to the sync when it's ready to go. So I think what we can do is we can kind of look at our checklist again. And we can start to see that we're layering our architectures in a better way. It's more modular. It's definitely much more testable than we saw before. We didn't get into all of that, but you can definitely see how testable it is. We can definitely follow the solid principles here. We're segregating responsibilities. We're injecting abstract interfaces rather than concrete classes, et cetera. And of course, you can do all the normal stuff. You can still talk to web APIs. This is a really small example, but you can definitely do all the powerful things. So yeah, my conclusion from this is that, first of all, there's no reason for us. I really like Flutter, and I think it's really fun. But there's no reason for us to reinvent the architectural wheel in some ways. We have really good patterns that we're used to from Android that work well for statically typed languages. And we can actually use all of these normal patterns that we've used in the past and combine them with some of the things that Dart offers out of the box, like streams. And we can combine these to make really powerful architectures that are easy to test, easy to work with, and also allow us to share a great deal of code between even Angular Dart apps if you want to go that route. So yeah, I'll post these slides afterwards. But to get a look at more advanced examples, I've got some different examples up here. Some of these come from the Flutter team. Other are ones that I've done. There's also more talks on the block pattern itself. So yeah, once again, you can check these links out. And thank you very much. Thank you, Brian. Thank you.
 All right.  Thank you so much for having me today.  Let me just start my timer so I know how long I'm taking.  All right.  So yeah, my name is Brian Egan.  And I'm here today to talk about Flutter apps  and reactive architectures for Flutter apps.  And so just a little about myself,  I'm currently an indie dev contract with the Google team  to write the Flutter cookbook.  And previously, I used to work at SoundCloud here in Berlin.  And before that, I also actually worked at Zappos  for a couple of years in Las Vegas as well.  So my experience kind of comes with working  from medium-sized teams and the types of architectures  that fit well for those teams.  So I think basically what, if you're  kind of interested in Flutter, you might be like,  oh, this is like a whole new thing.  But I think we can actually take a lot of the lessons we've  learned from Android and the best practices we've  learned from Android and actually apply them to Flutter.  So if you sort of think about the architectural goals  that we've seen that work on Android,  we've seen that layered architectures are really,  really powerful.  We've started to see the power of modularity  as well of breaking our apps down into smaller parts  so that independent teams can work on those parts  and then sort of assemble them back together as a larger app.  For us at SoundCloud, at least, testability was a huge deal.  And I think that the architectures we create and that we  should be thinking about for Flutter,  I think if you're doing a prototype or something,  maybe this isn't as important.  But if you're getting an app that's  got a lot of users, millions of users, maybe tens of millions  of users a month, then testing is, I think, pretty crucial.  And of course, we want to sort of follow  the solid principles that have stood the test of time.  And it needs to handle all the normal bits.  We need to be able to talk to databases and web servers.  And we need to respond to user interactions.  So I hope this stuff doesn't sound too crazy or too foreign.  And I think we can actually apply this in a reactive way  to Flutter apps.  So if you've sort of taken a spin of Flutter,  the building blocks that Flutter gives you,  pretty quickly you're going to play with the stateful widget.  Now, this is actually just really the example  taken when you do a Flutter create.  It'll actually generate like a Dart file for you.  And it'll have this exact class in there.  And so all it does is you have a stateful widget  and it takes in some properties.  So in this case, it's going to take in a title, which  is just a string, and then almost immediately it just  gets out of the way and creates this companion object called  a state class.  And this is really where you implement  all the interesting stuff.  So we've got a home page state class.  And inside of it, it's got a few different responsibilities.  So it needs to define the data model  that this component or widget will actually need to render.  Then you'll have some methods to update the state as well.  And so in this case, what we'll do  is when someone calls this method,  what we'll do is we'll just increment the counter.  And then right after that, we'll also wrap this in a set state  call.  And that'll tell Flutter, hey, some of the data for this widget  has changed, so please redraw it on the next frame.  The third responsibility of this state class  is that you actually render the UI itself.  So in this case, you've got a build method, which  returns a widget.  And it essentially consumes the data that's been passed in,  so it can consume the title that we've passed in,  as well as the local state that's contained within the class.  So in this case, we'll have a text widget,  and it'll render the current counter.  And we'll have a button.  And when you press on that button,  it will call the increment counter method  that we just showed you.  And then Flutter will redraw, do its normal thing.  And so yeah, we're kind of rolling, right?  You could also even go ahead and get crazy and start  to put network calls in there if you wanted to.  If you're breathing into a paper bag, just don't worry.  We'll get there.  So yeah, let's quickly challenge this  and see, hey, are state classes a good idea  for this type of logic?  So at least to me, it felt like there's a lot going on  in this state class when I first saw it.  And so I thought, OK, let's just see how testing works.  So what I was hoping to do is write a pure Dart test,  just saying that the counter can be incremented.  I was hoping I could just instantiate the state class  directly, call the increment counter method,  and then finally say, OK, I expect the counter to be one  now instead of zero.  The reality with Flutter is actually  that if you want to put business logic type of things  inside of your state class, it can get a bit trickier.  Because state classes are actually bound to the Flutter  framework, this means you actually  need to use the function that Flutter provides  called testWidgets.  And this will allow you to essentially build the widget.  In this case, I'm building the entire app,  and that's just for convenience.  Then you need to look through the widget tree,  and you can say, OK, let's tap on that button.  Then you can say, OK, Flutter, I know some state has changed,  so please rebuild the UI.  And then you can finally say, OK,  can we look through the widget tree  and find the text that we expect?  So if we just kind of return to our architectural goals  checklist, I think it's pretty clear  that we're not layering stuff very well here.  We're sort of putting UI logic.  We're putting this actual state object itself.  Sorry, not the state object, but the sort of state  that this class needs all in one place.  It could be considered modular.  You could have your login team do their thing,  and then your search team doing their thing.  But it might not be the most modular.  It's testable to a point, but if you  need to do more intricate stuff, it's going to be difficult.  It definitely doesn't follow solid principles, right?  We're shoving a lot of responsibilities into one class.  And you could do all the normal bits,  but maybe because it's not meeting that other criteria,  it's not as great.  So my personal conclusion from this  is that actually, this type of stuff  is really good for UI or view logic,  and it's perfectly fine for that kind of stuff.  But it's also really cool that widget testing is built into  Flutter, because it's actually a really powerful way  to write unit tests for your view layer, essentially.  But as I said, I think we're starting  to mix a lot of logic into one class.  We're mixing our UI logic, our domain logic,  and if I had gotten crazy, I was even  going to mix the data logic in there.  And so I think you can start to see where  testing more complex scenarios.  So if you have interesting list transformations,  for example, this type of stuff is possible,  but it's going to be more difficult.  And it's going to get even messier when async code is  involved because of the way test widgets works.  It actually does some interesting stuff  to override the default async mechanisms  and Dart, which make it really a really good fit  for testing widgets, but maybe not testing collaborators that  are async by nature.  It also, I don't know if this reminds anyone else of this,  but it feels like a little bit like a God activity  where you're just throwing everything into a single class.  And I think we've seen that over time that's really actually  been a bit of a pain point for some of us  as we try to refactor our apps if you're just putting away  too much stuff into one class.  So let's introduce some layering then, right?  So this is just standard stuff.  As I said, let's apply the best stuff from Android  to our Flutter apps.  So let's break out our UI, our domain, and our data layer.  And if we want to break this out a bit further,  we can do the traditional diagram where  we have a view talking to a presenter.  The presenter talks to some use case or interactor  at the domain layer.  It will talk to some data layer, in this case, a repository.  And that will be responsible for fetching the data  from the correct data source.  And then it'll pipe all that right back up  to the presenter and to the view.  So if we talk about Flutter, then, what we can talk about  is we can say, OK, the view layer is really the widget layer.  And I'm going to be discussing a concept.  So rather than a presenter, we'll just call it a block.  And this stands for a business logic component.  And this is, yeah, it's funny because I'm putting it  in the UI layer.  The Google team has called it a business logic component.  I think we could debate that.  But I don't know.  I really see it actually as very similar to a presenter.  So hopefully the stuff we look at when we get to code  won't look too crazy because you'll kind of understand  its purpose in the hierarchy.  So if we dive in a little more, since we  want this to be reactive, we want to be working with streams.  And we'll talk a little bit about streams  in a couple of minutes.  But they essentially just represent values over time.  And if you are familiar with Rx, it's just almost  the exact same thing.  So you can stream data to the block.  And then it can go ahead and fetch  any data that it needs from the use case or interactor layer,  the domain layer.  And then it can pipe that straight back up to the view,  maybe do some transformations if necessary.  We also heard a few talks this morning.  And so for folks maybe watching this at home,  I'd really recommend checking out some of the NBI  and the Mobius talks.  They're really interesting.  But I think you can start to see this will actually work  a lot like those architectures.  So you can actually use a block in a similar way  that you would use an NBI presenter.  So let's take a look at how this works.  So as I said, it's based on streams.  And streams are, I think, really a great abstraction  because they're actually baked right into Dart.  So you actually get language level support for them.  So there are these functions called async star functions.  And they allow you to sort of create and return streams  in a really nice way.  The other really big benefit to this,  and this was actually one reason that the Google team invented  this, was because the adswords team at Google, I should say,  invented this, is because streams work on every platform.  So they actually share about 50% to 60% of their code  between their Flutter apps, which are iOS and Android,  and also they share it with their Angular Dart apps.  So they're actually able to share a huge amount of code  in between those platforms, similar to actually what Jake  was talking about this morning with sharing code.  The other nice thing is it's got deep integration  into a lot of core libraries.  So you're just going to run into streams in the wild.  And as I said, if you're working with observables,  they're really similar to observables.  They are a little bit more limited than what  RxJava or RxJS provides.  So I've been working with another collaborator  on the RxDart library.  And this essentially brings more power to the stream class.  So we just extend the stream class  and provide additional operators.  We also provide an implementation of subjects.  But just to give you really what we're trying to do  is give an API that really feels a lot like RxJava or RxJS  with everything you would expect from a stream implementation.  So of course, just like you can subscribe to observables,  you can listen to streams.  You can pass through callback functions.  That should happen.  So if the stream emits a data event,  you would just do something with that, or an error event,  or a done event.  And you can do whatever you need to do in there.  So at this point, I'm just printing stuff.  It's pretty unexciting.  But yeah, this is just to give you  a feeling for how similar it is to observables, really.  You just subscribe to these certain events, and you get going.  You can also create streams in a few different ways.  You have your stream constructors.  So this is like if you want to create a stream from an iterable  or a future, which is a nice async primitive that's  also baked into Dart.  If you want to get more advanced, you can do concatenation  or deferring streams with RxDart.  And there's quite a few other more operators or constructors  that are useful.  But this is just to give you a sample.  The other cool thing, as I said, is you get language level  support for streams in Dart.  And this is a really interesting pattern, actually, I think.  So in this case, we've got a function called search,  and it takes in the search term and the form of a string.  And what we're going to do is we're  going to mark this function as being an async star function,  which means that we're required to return some type of stream.  And so in this case, I'm just going  to make the example really silly and say, OK,  we're returning a string.  But I think you could imagine you could return more complex  or more interesting objects from this type of function.  And the cool thing is that once you're inside this function,  all right, so once you're inside this function,  you can actually use the yield keyword.  Nice, it's working again.  So every time you yield a value, it  will be emitted to the listeners of this stream.  And this is a really nice way, so you can write maybe  a little bit less like crazy Rx code  and write maybe a little more imperative code.  So this search, what we'll want to do  is we'll want to emit a loading event so that our UI can  show loading.  And then within a try catch block, what we can do  is we can actually say, hey, let's go ahead and try  to call our search service and assume it returns a future.  And we'll just wait for that future to complete.  And if it completes successfully,  we'll yield that value out to the listeners of the stream.  But if it fails for some reason, we can emit an error event.  So I think you could think if you've worked with Rx,  I think you could model this in the same way with Rx very  easily.  You could do a start with loading event and then  on error return the error instead.  But I think async functions are kind of a nice way  to clean that up, actually.  And the final way is with something called a stream  controller, which is sort of a glue between the inputs  and then outputs of a stream.  So it provides a stream that you can listen to.  And then it also provides what's called an event sync.  And this allows you to add data to the stream or errors  or tell the stream that, hey, this actually is done now.  And so yeah, so you've got these three methods basically  on the event sync, add, add error, and close.  And those will, of course, get sent to the places  you would expect the listener to receive them.  So yeah, so that's really nice.  And if you're used to Rx observers,  so you're working with maybe a subject,  it's almost the identical, right?  You can kind of see that add sort of corresponds to on next,  add error corresponds to on error,  and close corresponds to the on complete.  So this brings us back to the block pattern  that we were discussing.  So as I said, these stand for business logic components.  And they've got a few special rules.  And so this is really the reason I was kind of describing  stream controllers especially is that syncs  are used for inputs.  So the view layer can add data to a sync.  And then the block can listen to the stream of that input  and react to it.  And then the block will also send streams of data back  to the view layer.  And then the view layer can actually  subscribe to those streams and update the view  when that stream changes.  And because this is meant to be used cross platform  between Angular Dart and also Flutter apps,  it's really important that you don't inject any dependencies  like concrete implementations, but you inject abstract interfaces  or abstract classes.  And the reason you want to do that  is because if you're in the browser context,  you can use a browser HTTP client.  If you're in Flutter, you can use what's called the IO client.  Or if you're in a test scenario, you  could inject a mock client, for example.  So if we look at a basic block, we'll just call it a counter  block.  And it'll actually emit.  So this is basically the same thing as we had initially.  So it'll emit a stream of integers that we'll call a counter.  And then to get data from the view layer,  it'll provide a sync.  And this is just called the increment sync.  And just to be good citizens, you generally  want to dispose of your syncs when you're done with them.  So say, for example, you're showing a view  or you're showing a screen, and then that screen should go away.  You'll definitely want to clean up after yourself.  So yeah, we'll just close off the sync when we're done here.  And then what you can do is under the hood, actually,  the block can use either a stream controller, which  I just talked about.  Or in this case, I'm going to use a publish subject, which  comes from RxDart and actually implements the stream controller  interface.  And the only reason I'm doing that  is because it gives you all of the Rx operators by default,  rather than having to convert a stream into an observable  and then use those things.  So yeah, so we'll create a subject here.  And then since we don't want to expose the full subject  to the view layer, we'll just end the subject, I should say,  also implements the sync interface.  So we'll say, OK, we'll provide a sync to the view layer  by just basically aliasing subject  and reducing some of its capabilities.  And then the counter stream, we'll just basically  listen to the subject whenever someone taps on that button.  And it'll just run an accumulator function.  So in this case, we're using the scan keyword.  And we're just adding to the previous count  every time a new tap comes in, or every time  the subject emits some sort of item.  So the cool thing is that these are actually really testable.  So Dart, as I said, because streams are baked in,  you actually get support with the testing library as well.  So you can, this is just a trivial example,  but I think it shows actually kind of how nice some  of the testing is.  So you can just say, OK, I've got this stream,  and I expect it to emit these values in order.  And so yeah, if you're worried about maybe your UI emitting  states in the wrong way, you can really easily test, like, hey,  is my stream doing the right thing in the right order?  You can actually also test errors as well.  So you can say, hey, does this observable or this stream,  does it emit an error of a certain type?  Or you could provide more complex logic in here.  This is exception is just a matcher,  and you can write your own matchers  to verify that you're getting the right thing.  And so we've talked a little bit about the sort of block  or the presenter layer.  So what about Flutter then?  Where does it come into the picture?  Well, we talked about it really being the view layer.  And so what it can do is it can actually  render the data from a stream using a class widget  called a stream builder.  And so this actually takes in two parameters, a stream  and a builder.  It's a stream builder.  Yeah, that's what it says on the box.  So in this case, we'll pass through the counter stream  to this widget.  And then the builder function will actually  return a widget that's built on.  So you basically take in the data that the stream emits,  and you render a widget using that data.  So this is really cool because then whenever it changes,  you can just create a new widget hierarchy  and Flutter will take care of rendering that to the screen.  You don't really need to think about setting up subscriptions  or doing anything like that.  The other thing is we can do is, of course,  we want to be able to send data back to the block  so that it can run that accumulator function  and increase the count.  And so what we'll do is we'll say, OK,  since we've got a sync that we can use,  we'll just add data to the sync when it's ready to go.  So I think what we can do is we can kind of look  at our checklist again.  And we can start to see that we're layering our architectures  in a better way.  It's more modular.  It's definitely much more testable than we saw before.  We didn't get into all of that, but you can definitely  see how testable it is.  We can definitely follow the solid principles here.  We're segregating responsibilities.  We're injecting abstract interfaces  rather than concrete classes, et cetera.  And of course, you can do all the normal stuff.  You can still talk to web APIs.  This is a really small example, but you  can definitely do all the powerful things.  So yeah, my conclusion from this is that, first of all,  there's no reason for us.  I really like Flutter, and I think it's really fun.  But there's no reason for us to reinvent  the architectural wheel in some ways.  We have really good patterns that we're used to from Android  that work well for statically typed languages.  And we can actually use all of these normal patterns  that we've used in the past and combine them  with some of the things that Dart offers out of the box,  like streams.  And we can combine these to make really powerful architectures  that are easy to test, easy to work with,  and also allow us to share a great deal of code  between even Angular Dart apps if you want to go that route.  So yeah, I'll post these slides afterwards.  But to get a look at more advanced examples,  I've got some different examples up here.  Some of these come from the Flutter team.  Other are ones that I've done.  There's also more talks on the block pattern itself.  So yeah, once again, you can check these links out.  And thank you very much.  Thank you, Brian.  Thank you.
_DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_0_959.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_960_2903.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_2904_3071.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_3072_3599.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_3600_3743.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_3744_4619.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_4620_5471.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_5472_5675.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_5676_6023.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_6024_6467.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_6468_7427.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_7428_8435.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_8436_10823.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_10824_12275.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_12276_13535.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_13536_15131.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_15132_16067.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_16068_16607.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_16608_17315.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_17316_18383.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_18384_19859.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_19860_20471.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_20472_21215.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_21216_21395.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_21396_22943.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_22944_23855.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_23856_24935.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_24936_25463.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_25464_28535.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_28536_29423.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_29424_30407.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_30408_30827.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_30828_30995.png _DCBerlin18_106_Egan_Build_a_reactive_Flutter_App__As6sFqpIwII_30996_31010.png