Gabriel Peal - MvRx: Android Development on Autopilot

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right we'll just give everyone a few seconds to settle in can you hear me okay raise your hand if you can hear me awesome all right my name is Gabriel Peale I'm a software engineer at tonal and I used to be at Airbnb today I'm gonna talk really really fast because we have a lot to cover in about 40 minutes so bear with me so we're gonna talk about Mavericks Mavericks is an Android architecture library built on top of Android jetpack view models and Kotlin and if you like buzzwords it also conforms to mvvm MBI and unidirectional data flow but I'm not super into buzzwords my colleagues and I started working on Mavericks about a year ago at that time we had about 75 Android engineers at Airbnb building product and we saw them reinventing solutions to the same problems over and over again this was really time consuming and it led to fragmented solutions and different patterns used across the codebase at around the same time Google introduced Android jetpack in specifically view models that were aimed at solving these problems specifically some of the conventional challenges around life cycles were dramatically easier after view models so the life cycle that we see on the left is what you might be used to for fragments and activities and on the right we have the view model it's much more simple because it survives for the logical lifecycle of your fragment rather than the view lifecycle so when you rotate the screen you'll actually get the same instance back and you don't have to worry about save instance State for configuration changes or recent scribing to network requests or anything like that in addition we were starting to adopt Kotlin in a really big way at that point pretty much all new code was written in Kotlin but jetpack required the its compatibility with both with both Kotlin and Java and we saw this as an opportunity to improve some of the api's on jetpack with Kotlin in addition it didn't try to solve every problem so we figured we try to solve it some additional problems that people were reinventing solutions for over and over again so we started building Mavericks and we launched it with a small pilot of about 14 Android engineers at Airbnb after they shipped the first features we asked them how they liked it on a scale from 1 to 10 and 13 out of 14 of those engineers gave it an 8 out of 10 or higher so I like Mavericks but just take my word for it this is what other people were saying as well so at that point we open it up to the rest of Airbnb and pretty much overnight it organically became the de facto way to write anything on Android at Airbnb from simple static screens to some of the most complex screens in the app like the reservation screen and none of this was tied to Airbnb so we decided we were gonna open source it and in August of last year we open sourced Mavericks and it was a beta and we wanted to get feedback from the community since then Mavericks has grown a lot inside of Airbnb it's continued to grow and today there are over 350 unique screens written with Mavericks at the Airbnb app that they're shipping every week that means that since we launched Mavericks a new Mavericks screen is added to the code base every single day in addition it's growing in popularity and many other companies have adopted it including tonal and actually as of this week we've released a castor i/o chorus to help onboard new teams and released Mavericks Wando the first proper one auto release of Mavericks now I could stand up here and show you 100 screens a hundred more slides as to why Mavericks is really useful for building products on Android but I figure I would just show it to you instead so this is my last slide and now I'm gonna hop out of the safety net of keynote and do some live coding everyone tells you you shouldn't do this but I'm gonna do it anyway and this is why we're gonna move quickly so we're not just gonna build one app today we're actually gonna build two the first one we're gonna build is a counter I have it running on my emulator here it has a textview in the middle of the screen when I click it a counter goes up by one and when I click next counter it adds a new fragment to the back stack with a new account so now let me know can you guys in the back see the code here raise your hand if you can all right I think that's pretty good and there's some empty seats if you can't see so you can move up so what we have here is the code for a simple counter we have a me to bowl integer property called count right here and then we do a couple set of things we set the enter transition just so you can see the difference between counters we inflate a layout all basic stuff and then an on view created we you Android navigation components to navigate to a new account or fragment now we're using navigation components you could use anything it has nothing to do with Mavericks but I chose it here because we can do it with just one line of code and then we set a click listener on that textview itself which increments the count and updates the textview and for the simple example this pretty much works well but things would start to break down pretty quickly in a real app so if we increment the counter and then rotate the screen it resets now we could fix this but it would take a little bit more boilerplate we'd have to use save instance State or store that counter is somewhere else or if we wanted to fetch some asynchronous data things would get even more complex handling things like the loading state success state and error state all independently which starts to become really cumbersome really fast in fact a lot of the time people just choose to ignore loading States and arrow states because it's just kind of annoying and it requires a lot of boilerplate and then God forbid if you want to handle rotation in that case or share that data between screens in a flow so mavericks is gonna help us with a lot of this stuff and view models actually from jetpack solve some of these problems as well as well so let's take one step in the right direction let's convert this to the jetpack view models so I've already added the dependency it's my build out Gradle file and the first thing I want to do here is create a view model so make class counter view model and we'll make it extend view model great so now we have a view model and we need to recreate that count property that it alone so we'll make Val count and then we'll make a mutable live data and it's an int now in the current version of jetpack you can't set a default value on live data so we'll have to set it up in in it so say count add value equals zero awesome now we need a way to increment our count so say fun increment count and then we need to increment the value so we say count value equals count dot value plus one now we have a little problem IntelliJ or Android studio is actually going to tell us that we can't add one to knowable end because we couldn't set an initial value on a mutable live data so we need to give it an initial value or a default value of 0 just in case it's null now it probably won't be in this case but the type system we have to do that now let's go to our fragment and we no longer need this count property because we've moved it's our view model and we'll just cut this update counter text for now okay so we'll delete that and delete these all right so now we need to get our view model that we just created now as you saw and that one of the four slides that I had view models have a really convenient lifecycle and that they persist for the logical lifecycle of your fragment which means that if you rotate your screen you'll get the same instance back that means we can't just instantiate a new view model in our fragment so we're gonna use something that comes with jetpack called view model providers so we'll make a private valve view model and we'll give it the type counter view model and then here we're gonna do by lazy we're doing this by lazy because view model providers can't be used until after the fragment is attached to its activity which will happen when any of our code is run but not when this property is instantiated so here we'll do view model providers dot of this now this is the scope for the view model it could be this in this case the fragment or we could put the activity which I'll show you in a second and we have to get an instance of the view model counter counter view model which we give it a class reference to so that it can look it up in a map to see if it already has an instance or it'll give us the existing one if it was already created so now we have our view model and the the first thing we need to do now is why aren't that click listener to increment that count so now when the click listener for that counter will say view model dot increment count and finally we have to update the textview anytime it changes so we'll say view model count dot observe we need to give it a lifecycle owner because live data is life cycle aware and then a new observer where it'll give us the count anytime it changes and the fragment is started and then we'll update the counter text text so if we rerun the app now you'll see it should behave exactly the same for the most part except now our counter will survive configuration changes so if we increment it and rotate it stays the same and when we add a new fragment to the back stack we have our fragment scope view model so gets its own so view models can also help us persist data and share data across screens so if we wanted to share that counter across fragments all we need to change is a scope of our view model from this which is the fragment to not require context but require activity which is the parent activity of this fragment so that all fragments in the same activity are going to share the same view model so here when we increment the counter and then hit next counter it stays at 5 this is pretty handy but now let's take it one step further let's convert it to Mavericks so in Mavericks one of the key differences is that it's although it's built on top of jetpack view models instead of exposing individual live data properties that you subscribe to individually your view model is now generic on a single state class that describes the entire state that the view model owns so Mavericks enforces a few things but let's let's get a project set up I've already added it to the build.gradle file and you can look in the github repo to see how to do that now let's get it ready to be integrated into our app I've done nothing except for that now the first thing we need to do is update our activity and in Mavericks and generally with navigation components there's really nothing in the activity it's more or less just a nav host or something that creates a fragment so we're just gonna change our base activity to base Mavericks activity now you probably have your own base activity and we recommend that you just make that extend based Mavericks activity or if you'd really like you can just look at the implementation and pull it out into your own base class that's all we have to do there now the only actual class we have to make to make our app Mavericks compatible is our own Mavericks view model so let's make a new class Mavericks view model and you'll see why we're doing this in a second it's gonna be abstract class a very few model and then it's gonna be generic on that state now as I mentioned with Mavericks view models your view model is generic on a single state class and so and we give it an upper bounds of Maverick State it's just an empty interface that tags a data class that's compatible with Mavericks and also can be used for things like ProGuard configs now Mavericks is also going to create that initial State for you and give it to you automatically so you want to give it a constructor parameter of this state and give it a type s and then a have extend base maverick view model like that now at this point you're like why are we even doing this in the first place it doesn't seem to add any value well it turns out that base Mavericks view model has a second constructor parameter called debug mode and we're going to set it to build config diva so Mavericks itself has a series of debug checks that it only runs in debug mode to ensure that you're using Mavericks in a safe and performant way but Mavericks doesn't actually know whether your app is in debug mode so this is really the reason why we're setting this up now let's go to our fragment back to a fragment and set it up for Mavericks so as I mentioned we're gonna update our view model but first we need to create that state class so we're gonna make data class counter State it's gonna have one property and it's gonna be an int well initializes initializes a zero and make it extend maverick State pretty simple now let's update our view model so now we're gonna change counter view model to extend our new Mavericks to model class and it's gonna be generic on counter state but you'll notice that we had that initial state constructor parameter Mavericks is going to do it for us so let's just do this Conner's state and then pass it to super that's all we have to do there nothing else now now that we have our state we no longer need this count property like that and then we need to now update increment count so we have our state it's going to default to zero and now we need to create a new state incremented by one if you're familiar with react this is gonna look really similar so there's a function called set state and it's only available from within a Mavericks view model now this you get a reducer inside of the reducer the this object that actually the context for this is the current state when it's called and you're supposed to return the new state so for verbosity and to make it a little bit clearer for the first example let's do valves like old state equals this so again this is the current state and we're gonna clean this up in one second and then we want to return old state dot copy count equals count plus 1 now copy is a column data class copy function so let's clean this up a little bit so to lamda so we don't need the return and since this is kind of implied anyway we can just delete this variable and just have copy now we can put it on one line and even set the entire function as an expression body syntax like this so now with one line of code we can easily see that we're updating the state to increment the count by 1 now let's go back to our fragment and we now need to make this fragment compatible with Mavericks so we'll change the base class from fragment to base maverick fragment okay pretty simple although now you see that the class is read because we have to implement a new function called invalidate now invalidate is a function that's very similar to the render function from react render automatically gets called anytime your state or props change for your component and similarly here invalidate will automatically get called anytime the state for any of your view models is updated so but now we need to actually wire up our view model with Mavericks and this is one of the ways we're actually able to simplify the original jetpack API because of Kotlin so here we're gonna erase this entire lazy block with something from a verse called fragment view model like that and now it's basically and you're gonna wrap the same thing we just did and automatically subscribe to state changes in a lifecycle away lifecycle of we're way and automatically call invalidate when it changes so we're gonna cut this text again because we don't need it and delete that observable value now everything else so far is the same the navigation controller the both these click listeners don't need to be changed at all now in validate we now know that this is automatically gonna get called so we need to update our text view accordingly so the inverse of set state is a function called with state and I'm gonna use the same expression body syntax so to simplify things since this is a common pattern in Mavericks we're going all Wis state view model and then it's can give us a lambda with our state and now we just need to update our text view instead of counting its state count and now it should just work so if we rerun the app it'll relaunch in a second you'll see when we click the text view it increments when you hit next counter it goes to a new counter and similarly with scopes if we change fragment view model to activity view model like that if we run it again it's going to do that same thing we just did with jet pack where it's going to now activity Skov this few model so when we increment the counter go to new screen it stays the same now all of this the a lot of this stuff that works under the hood people only think about within the context of rotation but one of the more edge cases for Android is that when your app is in the background it Android might actually kill your process but first it will give you savings estate bundle to save some stuff that you can actually restore in a new process and pretty much no one actually handles this correctly but Mavericks actually makes that a little easier as well so to simulate this let's open up settings here and we'll change the background process limit to zero there we go no background processes so when you switch back to the counter app and we switch apps and come back you'll see that the counter gets reset and this is gonna be the case pretty pretty much the behavior for most apps but Mavericks makes it really easy to solve this if you annotate your state properties with persist state Mavericks is gonna pull those out and save those so they can be restored in a new process so if we increment the counter now switch apps and come back it actually restores that we're in a new process now but we've actually carried state over from the original app all right so we've done the first app on it in a second so the second one's a little bit more complicated let's take a look here we're gonna build an app for a local dog adoption facility so when you launch it it's going to load a list of dogs from a dog repository we can scroll through them go to a details page where we can see more information about them at the bottom they'll be a little button that says I love this dog you can go here now click on wonder let's say I love it and you'll notice that the footer here now updates when you click adopt it will do some paperwork at the dog ready to be loved by its new family for about five seconds then take you to the adoption fragment where you can actually see your dog and you can figure out how to meet it so this is why we're moving quickly we have a lot to do in a short amount of time so I'm just gonna go ahead and close this module here and we'll open up something called dog fragment if we run the app right now it's pretty basic it's not actually really doing anything at all so it's just inflating a layout pretty simple let's get this set up so the first thing we need to do just like anytime you're setting up something with Mavericks is to set up your state so let's make an ood Kotlin date a class called dogs dog state it's gonna be a data class call a dog state and we're gonna make it extend Mavericks state okay now let's talk about what properties you want we know that we have a list of dogs and this is going to be shared across all of our screens so will say valid dogs a sing list of dog I'll explain this in a second and we're gonna initialize it to uninitialized so what you might normally do in the situation with live data is you would maybe have like a nullable list of dogs and a loading stayed and maybe the error in case it failed while Mavericks ships with a Kotlin seal class called async async' is a sealed class and it has four subtypes uninitialized loading success and fail it's generic on the success type so you can invoke it just like a function just by calling property with parentheses and then it'll return nullable version of whatever type that success type is or if it knows that is a successful value it'll actually return the wrap value we're gonna use this in a second so if it's a lot to take in right now just bear with me now let's let's do a couple things here we know that we're we want to fall in love with some sort of dog right that's the point of this app so we'll do Val loved dog ID and it'll be knowable along just and then we'll initialize it to null now this is great so the reason we're here we're using love dog ID is because our list of dogs the single source of truth for our data we don't actually want to store the dog there as well because if the if some information about that dog updates we'd have to remember to update it in two places and this is where derived properties come in handy so here what we're gonna do is we're gonna make a little function this is just a class so we can create functions and properties on it so we'll do a fun dog dog ID and it takes an illiberal ID and then it's gonna return dog nillable all right so you can give it a dog ID it will return either the dog or no so dog or no and then we're gonna say equal to dogs and so this is we're invoking these async property comes in handy because here it'll either return list of dogs if we've already fetched it or no if we haven't so here we have noble dog dot first or know it ID equals dog ID okay this is all we have to do and now if we want to create another derived property for our love dogs to get the actual dog that we love we can say Val loved dog equals dog love dog ready just like that one line of code now we have a property that's always going to be up to date and it's always going to have the latest information so the next thing we have to do is to create our dog view model so make dog you model as a class class dog view model and then it's gonna have the state which is dog state it's gonna extend Mavericks view model very few model dog state and it's gonna pass in the state so this is really standard pretty much all your Mavericks screens and view models are gonna look like this but we have this thing called the dog repository now it has two functions one get dogs and one is adopt dogs now I've made this simple for the case of this talk but it could be a network request or a database request anything like that but here I've just simulated it so it returns an observable list of dogs that sleeps for two seconds and then returns a list to simulate that loading time similarly adopt dog does something similar it's a single it sleeps for five seconds and then returns that dog so again this simulates an API request database whatever you'd like but now we need to get that in our view model so how are we gonna get this how are we gonna inject this so let's go back to our view model and we'll add it to as a constructor parameter because that's really the ideal way it makes it really easy to do things like testing okay so now we have it in our constructor but Mavericks doesn't know how to get your dog repository it could be in dagger it could be in coin it could be a global singleton it really could be anywhere so Mavericks can't do this for you so if Mavericks notices that you have extra parameters in your constructor it's going to look in the companion object of that view model to see if it implements Mavericks view model factory its generic on dog view model and dog state and then it's going to delegate the creation of that view model a function called create okay create gives you two parameters it gives you that state that you should pass directly to the view model that you're creating and also something called view model context now view model context basically gives you access to things that should enable you to get to your object graph so we hit die here you can see you have access to your activity your fragment arguments your application class anything like that so no matter how you have your object graph in your application this should be enough to at least retrieve the objects you need so here we'll say Val dog repository equals view model context dot app and then it's gonna give us a generic form so we can easily just have it casted automatically and then what I haven't shown you yet is on our application for simplicity we have a custom application and I've just created this dog repository as a singleton property on the application again this is an example but through your application or activity you should at least be able to get access to your object graph somehow so it's a dog application dot dog repository awesome we're getting closer so now all I have to do is return dog view model dog view model passed it the state that was given directly to us and a dog repository again you don't want to mess with that initial state because it handles things like persist state like we saw before and so you don't want to get in the way of that okay great so now we have our dog repository injected into our view model so in our knit block let's just subscribe to those dogs so say dog repository dot get dogs and now we want to subscribe on schedulers dot IO so again this is standard Oryx Java Mavericks uses our Java under the hood and exposes some conveniences for our extravagantly tied to it so if you want to use car routines or anything else you are more than happy to mavericks does not actually care so now what you would normally do is something like this you would subscribe and then maybe set state and there you have your dogs and then you say copy dogs equals success with wrapping the value okay so this would be up here and then this is kind of a lot of work and we haven't handled the loading state we haven't out of the error state and then here you'd have to create that you have a dist possible you not have to deal with that you'd have to figure out when to dispose of so mavericks makes all of this way easier so let's go ahead and just just delete this subscribe block and instead call a function called execute execute is an extension function from Mavericks as a convenience unobservable of T that will take your stream it will emit loading at the beginning and then map on next to success wrapping the successful value and on error to fail so now all we have to say is copy dogs equals it because it's now mapped this stream of T which is list of dogs to stream of async list of dogs great three lines of code we now have the life cycle is properly handled we have the loading state we have the success state and we have the arrow state so again this is one of the court cases that was really common that we saw at Airbnb that we think a lot of people do all the time that we really really want it to simplify all right so we're flying along here let's go to our dog activity dog activity here and you can see we've already updated it to base metrics activity and so now let's go to the dog fragment and update it so we'll say now dog fragment extends Bayes base Mavericks fragment like like that and then it'll ask us to override invalidate and then we need to get our view model just like we did in the counter so we'll say private Val view model dog view model by activity view model and we're just can go straight to activity view model in this case because we know already that it's going to be shared across different screens and so here now just like our counter example again these screen starts to look really similar which is really convenient when you're working in a large app because as you move around the code base the code looks very familiar everywhere you go so we say with state you model and now we have our state now I have to do is render our dog state and again this is automatically gonna get called in a life cycle aware way anytime to stay for any of your view models changes so we're gonna use Kotlin synthetic accessors to access the views here again for simplicity we have in our layout a recycler view that button at the bottom and a loading animation so let's take this loading animation and we wanted to be as visible this is from Kotlin or Android KT x equals state dogs is is loading great so now the loading animation is gonna be visible right when we're loading and so now we have the dogs for the cycler view and we're gonna use something now we we want to take this custom view that I made called dog row and we want it to be available in our recycler view so normally if you wanted to do this it would take quite a lot of work you'd have to wrap this in a recycler view view holder you'd have to make if you hold or layout you'd have to make an adapter it would take a lot of work but instead we're gonna pair this with another library we open sourced at Airbnb called epoxy epoxy makes it really easy to put custom views and put them in a homogeneous or heterogeneous recycler view and the heterogeneous part is actually really convenient because that gets really complicated with recycler view when you have to try to get the item view types correct so in order to make this compatible with epoxy follow with me here and then you'll see it'll all come together in a second we're gonna annotate this custom view with Model View and then what this is going to do it's going to tell epoxy that this custom view can be used with epoxy then it's going to generate something called an epoxy model which is the data component for your for your actual views epoxy then instead of using your like a recycle view adapter directly well automatically dip your epoxy models against what's currently on the screen and only dispatch the minimum set of changes necessary to bring the screen up-to-date so here this auto layout this is going to be the layout params for your view holder layout that will automatically get generated so there's some constants constants that come with epoxy so we're gonna say we want this to match parent width and wrap content height pretty standard for a view holder layout for something like a row now we have two functions here one called set dog and one called set click listener now the dog is really the primary property for our epoxy model because it contains the data and whenever the dog changes we want to rerender that on the screen so let's annotate this with model prop this will tell epoxy to create a dog that we can set on our epoxy model and then it'll use its hash code to determine whether or not the view change now we're gonna use a slightly different one called callback prop on the now callback proper is basically the same as model prop except epoxy won't use the hashcode since it's just a lambda and it doesn't have an inherent hash code and it'll automatically unset it when the view is recycled now let's go ahead and rebuild the project here and it's gonna do a lot of that work you would normally do manually under the hood like creating an adapter creating a view holder view holder layout it's gonna do all that stuff with just these three annotations so if we go back to our dog fragment now we have the dog's recycler view and we're gonna use a function called with models now and here we just put the epoxy models so epoxy is automatically going to create an epoxy model with the same name as a custom view so now there's something called dog row and it matches exactly the name of the custom view that we have here now all epoxy models have an ID property these maps are recycler view stable IDs so that epoxy can get things like notify item moved so and and this can really be anything but what we really need to do here is not just create one dog row but we want to iterate through the dogs from our state so do state dogs dot for each for each dog and now let's just render one dog row for each dog so we'll say the ID is dog that ID again this is the recyclerview stable ID we'll set that dog property from that model prop and then we'll set a click listener in a minute now we've written a lot of code let's see if it works I'm gonna run the app here pop it up on the emulator we have our loading indicator and after two seconds we get the data here we have again we've took that custom view and you put in a recycler view without making an adapter or viewholder layout view holder anything like that if you have no item view types it's kind of magic and therefore our data ever changed we automatically get the right know recycler view changes dispatch to the layout so you would see things move insert and all the animations would be correct already out of the box great now let's sit up a details page so here what we want to do is set the click listener to go to the details fragment so we have our click listener here like that and then we'll use navigation components again this is not tied to navigation components but I've done it for simplicity so we're gonna do art ID dad action dogs too dog detail like that and it's gonna go to our dog detail fragment and this dog util fragments can look very similar to the the main fragment we have right now so I've commented out some of the code since I it's a little bit easier to explain that way the first thing we need to do is to change our base fragment from fragment to base Mavericks fragment like that great and then it's gonna ask us to create invalidate now again these things are gonna start to look really really familiar we create our view model property like this just need to get everything imported dog detail like that it's gonna dog view model like that great and we okay more imports like there we go and one more okay so now we pretty much have most the fragment that we need except you see we don't know which dog detail view we're going into so we have a problem we need to figure this out and we could we could maybe say update state and put that current detail view in our state we probably don't want to do that in the at a future point in the app you might have multiple dog detailed fragments on the back stack and how would you know from the perspective of one fragment which dog you're trying to render so instead we're just going to use plain old fragment arguments they're still perfectly fine navigation components support them and we so we wanted to make that easier as well so let's let's just comment this out for a minute actually we'll leave that that's alright let's set up our fragment arguments so this is going to be pretty similar to fragment arguments if you've used them before so we'll say fun argh dog ID long and it's gonna return a bundle great so I'll save out our zequals bundle and then we need to put this dog idea into the arc so argh start put long and then so now this is when you oh it often have to add additional boilerplate to your app you would probably want to create some sort of key so you can put it into the bundle and then retrieve it out but instead we're gonna use a magic key called Mavericks daaad key argh and all you'll see why this is useful in a second and then we'll put in the dog ID and return our eggs just I got so this is pretty great and this feels a little bit magical because we wanted people to be able to use fragment args without creating constants all over the place so the way we get this out we never actually needs to use that key again now Mavericks is it gonna help us with that as well so we know we won private valid dog ID it's a long and here we have another property delegate that ships with Mavericks that makes things easier for you so we're gonna say bye args so what that's gonna do is if you're in a maverick fragment it's automatically going to look in the arguments for that fragment for maverick sake argh and then try to cast it to whatever type you have there so this makes it really really easy we now have our dog ID in our fragment without having to create any new keys and this is pretty simple for something like along but what would be found is that by combining this with parcel eyes to automatically create parcel of all data classes you if you have a bunch of arguments for a fragment you just create one data class that's parcel able and then you can get it you can get access to it really easily like this you're not tied to long it can be anything that's serializable or parcel great so now that we have our dog ID if we go back to the original dogs fragment we need to pass in that bundle so we're gonna say dog detail fragment dodge argh dog died ID great so this should be all that we need and if we go back to our dog detail fragment we now have all this code on commented and let's go ahead and run it and see if it works and rebuild the app it's gonna relaunch it's going to load our dogs for two seconds it's gonna load our dog when you click turbo it crashed let's see why it crashed so we're going to open up the stack trace here like that nope let's see see if we can get that stack one more time like that boom all right so it's a missing text view because I changed the name of this from a top button to love button there we go so nothing's here with Mavericks simple resource aidid now when you click the layout we go to the details page awesome so now that we're here we want to wire up this love this dog button so now you see here I have this function that I recreated on the view model but we haven't actually implemented it yet it's called love dog and again these things are gonna start to feel really familiar because Mavericks simplifies a lot of this stuff so we'll go to our view model and create a new function called love dog and we'll pass the dog I'd eat dog ID love dog dog ID long and then we're gonna say it equals to set state and then we need some we need a place in our state to put this so we already put it there so we'll just say set state copy love dog ID equals dog ID great pretty simple now if we go back to that detail fragment this isn't this will now work we can love this dog and then as soon as we click love dog we just want to pop that back stack to go back to the original screen it's a demonstrate that this actually works let's go ahead and we have an adopt button on this screen so we're gonna say adopt let's get this out of the way a dog's button died said text died text equals get string art out string died adopt dog and then we're gonna give it state dot love dog dot name just like that but we really only want to be able to show that button when state that love dog is not know otherwise there's no point in showing that button since this is the adoption button so we'll just say a dot button again dot is visible it is visible nope equals state dot love dog is not equal to null and if you remember love dog is a derived property that we created that is of type nullable dog that will either be dog if we've loved one or or no if we haven't so let's go ahead and run that just to confirm that this works all right two seconds of loading and now let's pick fuzzy great I love this dog now it says adopt fuzzy again we're sharing state between screens really really easily now the last thing we need to do is actually adopt this so now remember if you are an our dog repository we have this function called adopt dog it's a single that returns the dog after five seconds so let's go back to our state and let's make Val adoption adoption request it's a type a sink of dog and it's also going to be uninitialized great so now we have a place to store the loading stayed success state and error state for our adopted dog so now if we go back to our view model view model we want to create this function called fun adopt loved dog and here we want to get the state with state like that now we want to adopt this dog so what we're gonna do is we're going to say Val loved dog equals state love dog but if you call this before you've loved a dog that's really not okay so we're gonna say we're just gonna throw a legal state exception you must love dog first before we can adopt a dog and now Coughlin's gonna smart cast this love dog from nullable dog to not know dog so now we want access to this dog repository we've injected it but we haven't made it a property so we'll just do private Val like that so we can access our dog repository so we'll say dog repository again it's gonna look really similar to the code we have up here so gonna adopt dog and we're gonna pass it that love dog and then we're gonna say dot subscribe on schedulers dot IO again really familiar and then we're gonna say execute copy adoption request equals it this is all we have to do now we need to trigger this from our dog fragment so we're gonna say and on view created we're gonna say adopt button dad said onclicklistener view model dot adopt loved dog and again this should be fine and safe to do because we're hiding that button if that love dog is not know or if it's no rather so this should be pretty good and now the last thing you want to do is navigate to our adoption screen as soon as the that request is successful so let's go to on create here and I'll show you something new so you might see here that we've only reacted to our state in invalidate or one with a one-shot kind of with with state but Mavericks provides a lot of tools to make things easier so if you say view model dot subscribe subscribe you'll get a lambda here and this is basically exactly the same as invalidate it'll only get called when the fragment has started it's fully lifecycle aware and it'll get called anytime your state changes but some sign sometimes you going to be a little bit more specific so you can say view model Dodd select subscribe dog state love dog and now you'll get a lambda that's only called when that specific property changed and again this is really useful in this particular case we're select subscribing on a specific derived property so if you want to get certain triggers you can always do that with derived state went instead of subscribing to your primary mutable state properties up here but there's one that's even more convenient for our use case right now remember we want to navigate to the adoption fragment as soon as that request is successful so instead of checking for the type of here Mavericks makes that easy so we're gonna save view model dot select or async subscribe and this is a special version of subscribe that's specifically geared towards making these async operations easier so we do dog state adoption request now there is two optional parameters one for on success and one for on error you can use this for analytics you can use it for navigation we're gonna use it for navigation in this safe case so when it's successful we're going to find navigation controller dot navigate our that ID died action dog to adoption and if we go to our adoption fragment which is right here we'll just uncomment some of this code again looks super similar to the the code that we've already written we have a private value model where you have a view model I just get all this to import like that and then everything everything works out you'll see here there's even one additional epoxy model we've made called title row now a title row it's another custom view just like everything else there's only one difference here that we've annotated this function with text prop now text prop if you give it a char sequence it's also geared just to help you out because in the property that you get from epoxy in the epoxy model it gives you an automatic overload for the Android string resource so here we can put the string resource in here and it'll automatically do the right thing under the hood and again this also highlights the power of epoxy because this will get called automatically when the state changes and you have a heterogeneous recycling view now without having to worry about view types and making sure that you get that state correct so now we have an adoption fragment with a title row and it's going to show the adopted dog from the adoption request as soon as it's done so let's go ahead and run the app all right we'll go ahead and say we like turbo first say I love it now it says adopt turbo if we scroll down how about uh maybe maybe actually happy you know how I became a little later say I love this dog a dog copy and did we I don't think we wired up that click listener did we so dog fragment we want to say here let's make sure we got this right oh there we go it actually went after five seconds we just didn't update invalidate so what we want to do right here we was going to say that loading animation we wanted to come back after a state adoption request is loading as well and for the recyclerview reacted we want dad to go away only when equals state adoption request is uninitialized okay so this is gonna make that recyclerview go away we'll get that loading indicator back and then we'll go to our adoption fragment so we'll say jewelz I love this dog adoptions now again this is automatically we don't have to wire anything up this just happens automatically after five seconds we go to the adoption fragment and we've just written two apps in about 30 minutes so I hope this shows some of the power of Mavericks I know we covered a ton I'm pretty much out of breath I know it's probably hard to retain all of this in one talk but I hope this gives you some of the ideas so that you can go and check out our github wiki's for either Mavericks or epoxy we now have a caster IO course for Mavericks which covers all of this and more and I'm always reachable on twitter my handle is GPL eight if you have any questions and finally I'm gonna just hang around back here if you have any questions just come up and talk to me because we're just about out of time so thank you very much you
Info
Channel: Android Heroes
Views: 6,155
Rating: undefined out of 5
Keywords: Gabriel Peal, MvRx, Android, droidcon, droidcon italy, turin, google, conference, airbnb
Id: Web4xPi2Ga4
Channel Id: undefined
Length: 45min 30sec (2730 seconds)
Published: Fri Apr 19 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.