KotlinConf 2018 - Shaping Your App's Architecture with Kotlin and Architecture Components by Florina

KotlinConf 2018 – Shaping Your App's Architecture with Kotlin and Architecture Components by Florina



[Applause] hello everyone my name is Renee Montagne high school and I'm a developer advocate at Google and in the last few months together with three of my colleagues we'll be trying to bring plants back in fashion blood is actually Nick butchers application and he started developing some around 2014 as a way of showcasing material design so plant aggregates data from three different sources these are the news dribble and product hunt and what Nick did here is use all sorts of Android API s like shared element transitions and all sorts of fancy animations that really show how the user experience can be improved by applying material design principles he also added all sorts of like very fancy animated vector drawables so I remember when I use Platt for the first time I think I've traced that search button like a hundred times just to see that magnifier transform into an arrow it's pretty cool so the video that you showed there you saw earlier was actually from 2016 when it was like plaids glory time because from 2016 and 2018 things change a little bit and that's because some of the API is were deprecated so for example with the dribble and now you'll see if you're scrolling you see just a progress bar that's loading without being able to actually see the comments for that shot also pressing on likes just gives us the number of fans and that's it so that's because part of the API was deprecated we can no longer actually like a shot we can no longer follow a user so this means that step by step almost half if not more of this functionality what's gone well with designer news things were actually even worse so for example in designer news for a story you can see the comments and the comments are threaded and you cannot add a reply either to the story or comment whether the story or as a reply to another comment so in order to easily differentiate two which comment you're actually adding a reply and Nick added this nice effect of elevating the common that you've just touched and I think my favorite feature in the entire plot up and the feature that still makes me gasp when I see it is this reflow text so Nick worked a lot on like arranging exactly like the speed of the text away the words move if you actually work at it you see that it's they moving on arc it's not a straight line he did an amazing job there and he also did I talk about it in droidcon New York 2016 so if you wanna see how everything was implemented check that talk out but the problem was designer news is that the entire API was deprecated so the entire v1 was no longer there so this means that in Platt we could no longer see the comments or this awesome reflow text what was working though was product hunts just like with fraud our hands well you are tapping on an item and we were just opening a chrome custom tabs and that was it so nothing very fancy here so this meant that from 2016 until now we ended up with like decreasing step-by-step the feature of Platt so from that initial beautiful shirt we ended up with well something quite plain so we decided that we we shouldn't leave it like this plaid is such an awesome application so together with three of my colleagues with Nick butcher band vice and Tim song but he said that we wanted to bring plants back to life so more precisely we wanted to make sure that we're fixing the broken functionality but also if you look at the code you see that it's using quite outdated technology in the end it was started in 2014 so it wasn't using any of these fancy MV star patterns so we wanted to build something that smaller that's extensible and of course based on the guy to have architecture that we're recommending and Copland so you know how is measure twice cut once so before we we wanted to cut we decided to decide to define how we want to reach plaid back so we knew that okay we have three different sources so each of these sources should be in their own module so like this we can easily plug and play another data source when we want to extend the app even more well we found it would be interested interesting is we have a filter list here so whenever we want to add another another source to that filter list and then actually see the items there only at that time when the user clicks on the item to download the code for that so this is possible with the new app model that we released this year at i/o the app bundles and more precisely with dynamic feature modules which are part of the app bundles so with the dynamic feature modules you can in a module you would put all your code or and all your resources and they if you enable the dynamic downloading then it means that they will only be downloaded at one time so we wanted to have this clear separation between them okay but if you have the module separation we also want it like a layer separation so then if you want to replace a part of the app so for example instead of using share preferences to save some data to use room we decided to also create layers so we had data domain and UI layers but this is pretty much how our app looked like I wanted to create a better diagram of this but I think this shows best how ties the dependencies were in flat so we knew we wanted to fix it and we also knew that we wanted to use Kotlin and we knew that the language features that cousin brings will also shape the way we are hitting our application so one of the first decisions that we had to make is decide how are we handling asynchronous work so initially plaid was just using retrofit two calls to handle well background work but actually that meant that only the network requests were done on a background thread so things like the business logic was done on the UI thread it's okay definitely this shouldn't be used only so we looked at three other options executors our Java and coroutines so executors in the end you're just creating a runnable and that's about it the API lissa is not the most usable one and it's not supporting extremes of data it's just something that you're executing on a background thread and then you're handling that result another option was rx Java where you can work with streams of data you really have a large number of operators but the downside of this is that you have quite a steep learning curve and out of four people in our team three of them have never worked with our Java and it felt like actually climbing that learning curve well we consider it as a disadvantage at least for now and then coroutines have that ease of learning it's easy to do the first steps with coroutines and because a lot of times with our java single maybe and completable are just used to move something move up work on a background thread then with coroutines these things and easily be translated into a suspension functions and on top of this they also proteins also support streams of data so so far we haven't used streams of data we haven't used channels but this is something that we're considering in the future okay so looking at executors our extra line coroutines and all of their pluses and minuses we did the math and we decided to go with quarantines and more precisely we decided to launch the quarantine as close to the UI as possible and this is because whenever we're launching a quarantine coroutine for example to do a request to the backend chances are when more exiting that activity we want also to cancel that request so we wanted to have the launch and the Council of the quarantine in the same class and the place where we decided to do this is in the view model and also in the view model the result from the Coyotes will be passed to alive data but I'm going to suspend for now the idea of view models and I've promised to come back to it a bit later so these are the layers that we ended up creating so we will have something like share preferences or a database a local data source a remote data source a repository that would work with these two and then as part of the domain layer use cases that will work with repositories and in the UI view models activities and XML and let's go over actually every every layer and see what exactly we've done there because before we started implementing we decided to create a set of rules for each of these classes so let's start with the data layer so the API service is using the retrofit to cortines cotton cortines adapter where in your service you're actually returning a deferred of a response then for the remote data source we decided that its role is just to construct the request data and actually fetch the data from the service it will depend of course on the API service and as a input it will only get the request information and as an output a result of type response this being the response that we get from the backend so what's this resolved class so we knew that we wanted to moon express in a way the fact that we got the data either with success or with error and the way to do this is with sealed classes because with seal classes you can only have a limited type of you can decide what's the type here key so often you end up using the seal classes or we end up using the result with when so we were checking okay what should happen in case a result is successful and what should happen in case of error but because we wanted to make sure that we're always handling this so in case we're not handling the error case we wanted the compiler to tell us something the compiler to tell us that we an error so something like the one expression must be exhausted well in Cortland when is only exhaustive when it's rated as an expression so this is what we did we treated when as an exhaustive so we actually just created an extension property 2t and then we just return it like this the compiler will really force us to handle all possible branches on the one okay getting back to our remote data source let's see how an implementation looked like so we would have a comment remote data source that would depend on a design on your service so inside in order to post a comment we would have a suspension function that would get us a parameter a body for the comment and with return a result of the comment response in the implementation we would build the request and then we will trigger a request to the back end and then a wait for the response and then we will handle the success or failure case of the response but actually this code is not complete because for example if your device is offline this code will crash so what we need to do extra is to wrap this is inside a try-catch so this means that for absolutely every Network request that we make we had to add this try-catch we didn't want to do that so we created a top-level function which is a we call it a safe API call and this is also suspending function and most of all it also gets a suspending lambda as a parameter so inside this function we'll just call the lambda and then in case an error is thrown we just return a result of error based on the message that we passed as a parameter so this means that in our remote data source our code now looks something like this we'll just call safe API call we would put our post comments method as a comp and then we will define an error message so this means that when we're implementing the post comment we can focus on what matters on creating that request and handling the response from the okay next we work with a local data source so the role of the lake local data source is just to store data on this it depends on either share preferences or database for now we're still working with the share preferences but we think that because the local data source is the only place that knows how to save the date and it shouldn't be hard to replace in share preferences with room for example so as an input the local data source gets the data to be saved and then at the output the data that was saved the class that works with the local data source and the remote data source is the repository its role is to fetch and store data and optionally we can decide to do also an in-memory cache its input would be the IDS of the data that needs to be retrieved or the data that needs to be saved and as an output would also be a result of type response okay so I was saying that we want to work with suspension functions here in the data but also in the domain here so this means that the repository also this suspension functions from the repository would be called from the domain layer but the truth is our code wasn't like this we were still refactoring flat from Java to coughlin so our code was something like this part of the repository was called from cotton UI apart from the domain layer also returning cotton but we also had of course calls from some layer in Java so if we have something like that it's like in our sauce repository we expose a search suspend function well if you want to handle this in to about to call this in Java this is what you'll need to implement you'll need to define a continuation and there define what's the context define what should happen with resume what should happen with resume with exception and then you also need to make sure that you're canceling this so it felt like okay this doesn't look so nice from Java so we looked again at what we have in the repository and initially we saw that there we were also handling the canceling of the calls so at that time you were working with the calls from retrofit I mean make sure that in certain cases we were cancelling all the searches so also right now the repositories are Singleton's living in the scope of the entire application so it was okay for us to handle the cancelling like this also initially we were calling the search method also with a callback so with a resolve callback so it felt like okay for now while we still have the java parts let's just use jobs in supporting jobs instead of calls so like this we make sure that indeed were cancelling all of our quarantines when needed so we did this mix of callbacks and coroutines which isn't that nice but it felt like it was a good enough compromise a good enough compromise until step by step we refactor the application and then we saw that indeed with time as we refactor the specific flows going from this to how we actually wanted our code to loop so to suspend functions wasn't really that hard it wasn't hard to get rid of that callback okay another problem that we had was this so if you look at these two methods you'll see that they're very similar they both get a string and two Long's as a parameter so the difference being that when you're posting a comment or story you actually need a story ID when you're posting or reply to a comment you need a parent comment ID so what we would have wanted to have is to enforce that type safety so we would have wanted to have just post comments but with two clear overloads with one with story ID and one with a comment ID this is possible to do this you would implement your own data class that wraps that ID and then in the story and in the comment instead of having Long's of type IDs you would just have your story idea bracket or comment ID but the downside of this approach is that extra object creation that you have to do and this is why we look forward to a caught in 1.3 to b2b out because here the inline classes were released so with inline classes all of this extra object creation is no longer needed with inline classes automatically the compiler compiler will box the things for you so the story ID will behave as along what we need so overall for the data layer we had a repository the local data source and the remote data source and for each of them we defined clear roles dependencies inputs and outputs for the domain layer we used use cases so use cases are the ones that process data based on business logic and they would work with either the repository or different other use cases as an input they usually get IDs and as an output a result of type object so what's more important is that the use cases should be very lightweight they only do a single task they only do a single task so we wanted to somehow enforce this so let me give you an example so when we want to post a story comment we would work with a comments repository and a login a repository so our method would look something like this we would have a body and a story ID so this means that whoever calls this kind of use case doesn't know that we also need the ID of the user and it's only the role of the use case to know that it needs to get this kind of information from the login repository but because the use case should only have this one task of posting a story comment it means that this should be the only public method that we expose so what we ended up doing is working with the invoke operator so by implementing the invoke operator it means that we can call a class as we would as a function so in our case we can just call post story comment use case of the comment and of the story ID and even more as a naming convention in our team we decided to drop the use case completely so like this our call just looks like post story comment and that's it the class that calls this doesn't care about how this is implemented but we know that under the hood is a use case all of the lottery that comes with it it's all separate so because you can only have one in Volk implementation we decide that this would be actually the only public method inside our application and all of the other methods to be private okay let me tell you about another problem we had so in our designer news story we had I was saying threaded comments so this means that here we have something like four layers of comments of a reply to a reply to a reply to a comment of stock but what we were getting from our API was something like this for a story who would get the IDS of the first layer of comments so then we will need to request the IDS of those comments where we would get the actual comments but also the IDS of the replies and then we would get those IDs and then get the replies to the replies and then again the replies to the replies of the replies and so on so what we were ending up with was with a list of lists that afterwards needed to be matched that's a lot of operations we had to do there with collections so we worked a lot with the extension functions from the collection API so for example here to match the comments with replies we use functions like group I that just does all the heavy lifting for us of mapping a parent ID so an idea of a comment with that list of replies similarly we have something like math in order to create that transformation but next all of our comments also had a reference to the user ID so to the user that posted it but we didn't want to display the user ID we wanted to display the name of the user so we had to create a sets of all of those users that added a comment to that story and then do another request to the backend where we are actually getting the entire user information so here again then afterwards we had to map it and then create a new list with a new comment with replies and user information so here again we ended up using heavily the collections so we also use the Methos life associate by I'm an apart from the collections we also used sequences so sequences are lazily evaluated collections so the main difference between these is that with sequences when you have multiple steps that you need to perform on the list like in our case flat map and map with sequences all of these operations are applied on every item with collections all of these operations are applied on the entire collection so this means that for every step a new collection is being generated so this means that if you wanted to reduce the number of objects that you're creating sequences are better so it took me a while to just explain our business logic but you saw that the code was so simple and easy to read so methods like this like for each map flatmap group I were extremely useful for us if they reduce the boilerplate a lot so in general whenever you have to work with collections check out first the extension functions from the standard library I'm pretty sure they'll be very useful for you too okay so we looked at the repositories and use cases the next is that Europe so for the UI we define the view model and the role of the view model is to expose data to be displayed to the UI and it would also trigger actions on use cases based on the user's actions also the view model is the place where we're launching and will canceling the coroutines it depends on use cases and as an input it can optionally have an idea if for example this is a detailed screen or it can have users actions and as an output it would have a live datum of a UI model so for example when we need to build us the view model for the story activity we would have a story ID and also several years cases and we would extend from the view model because the view model by default doesn't have any arguments we had to implement our own view model Factory and we decide to implement your model factory for every view model that we had even more we wanted to keep that immutability of the view model we wanted to always keep that story ID immutable so this means that we had a view model for every activity so if we had multiple instances of the same activity being opened each of that instance would have its own instance of the view model in the view model we would expose the UI model via live data but in order to actually set the values on a live data you will need to work with the mutable life data because this is the class that has the set and post value functions but because we didn't want the activity that uses this view model to also be able to modify this we decided to use a backing property of type of mutable live data and only expose a live data and we decided for events like opening a URL or showing a toast to expose a separate live data's and more precisely these would be a live datum of an event this is because these kind of events like opening a URL or showing a toast is something that should be done once and then that's it whenever the activity is recreated and then we get again we start observing that live data we want to make sure that we don't trigger that request again because in the end live data is just the data holder so we use this event class where we're just wrapping the actual content and we added another consume property and then once the object is read from the life theta once we're changing this value so consumed would become true I'm not trying to go over the options that we've thought about before using events and I definitely recommend checking out the hotels Erica's article on online theta he when he goes there over several other options that we thought of and their disadvantages and advantages okay so the view model was actually used from two different places it was used from the activity and from the XML so in our activity we would use a late in interview model and this is because we want to we know that the view model will not be null and it's going to be initiated later on in the oncreate so in the oncreate we work with the view model providers to actually get the view model in the XML we are using actually data binding so this means that we define a view model variable as part of our data block in the layout and then in the text view what we are doing is working directly with the data from live data because data binding and live data have this nice int of interoperability so this means that we can just do this in our XML so we looked over the view model activity and XML I think because plot is such a UI heavy application it's still the part where I think we can do better we are still considering of maybe using a presenter maybe splitting that action that responsibilities even more but these are overall our main layers the data domain and UI and creating these kind of separation these kind of rules really helped our team to to build things easier we knew what are the guidelines we knew what the restrictions were okay so overall we knew that we wanted to fix the broken functionality and this is something that we mostly done so we can say that the product part was mostly handled but then we saw that apart from defining these layers and modules working with encoding properties like silk classes and delegates and so on really helped us build this kind of modular and extensible architecture that we really wanted or help us do this now and in the future so please check out the plaid applet I also become better testers so we can see what kind of errors we've created while refactoring this and because we're still in progress keep an eye on what we have on on github so thank you on behalf of the plants team [Applause]


13 thoughts on “KotlinConf 2018 – Shaping Your App's Architecture with Kotlin and Architecture Components by Florina

  1. we have almost the same architecture, but we are using rx java 2, on my opinion it's much more flexible than coroutines.The way you handled the errors is already implemented inside rxjava and it works fine with retrofit and room. The only interesting thing is testing. I'd be glad to see some master class on how to write test-coverable code in android

  2. 10/10!! Awesome talk and presentation @Florina. Can’t get my eyes off the slides.. What tools/software was used to design the presentation??

  3. Very similar to our project, except we named the safeApiCall 'asResult", as it makes more sense to wrap some callable and have it try-carched and get a Result<T>

  4. Thanks for sharing this!
    At https://youtu.be/Sy6ZdgqrQp0?t=637 you mention the Result class. I use a similar approach to the projects I build using RxJava2.
    The main issue I have with that solution atm is the fact that it gets quite complicated when the Use Case needs to grab data from different repositories in order to perform business logic.
    I know you use coroutines, but on Rx when you need to .flatmap calls it's super annoying.
    Did you fall into a similar issue?

  5. It would've been nice to see some Room code snippets in combination with the presented code, but still, nice talk. Felicitari!

  6. @Florina What did you use to make slides for this presentation? Animations and transitions between slides are awesome!

  7. Great talk! The only thing missing is a section about testing/testing strategies they followed. It would then qualify as the best talk regarding Android + Architecture 🙂

  8. This talk is amazing, both in regards to contents and presentation. Which software did you use to create the presentation?

  9. I am very much Sutterfield with this architecture will definitely give a try for my future projects. Thanks,​ @Florina For the awesome talk

  10. In the safeApiCall method the call lambda could be the last parameter, this way you could place it outside the () for a more ideomatic use of the function on the call site

Leave a Reply

Your email address will not be published. Required fields are marked *