Single Activity: Why, When, and How (Android Dev Summit '18)

[Music] hi everyone thanks for joining me my name is Ian Lake I am a developer on the Android team and I work on quite a few projects but most notably the navigation architecture component as well as fragments as well as some of our new libraries like the Android X activity artifact and loaders and today I want to talk to you about single activity why when and how and really try and share some of the best practices from the Android team and from the architecture component team on what actually is going on in this world there's been a lot of questions way back from 2014 16 and even here in 2018 so we're here today to kind of talk over all of those wonderful things that make up what our activities so activities are really a component at the android level so they're at the same level as content providers broadcast receivers and services and they're registered in your Android manifests and really they are the UI facing pieces of your app right so when the android framework goes to start your application from launcher icon or an app shortcut or what you see when you're doing multi window those are all activities right so they really are kind of the entry point into your apps UI right when the user goes to launch your app they're launching an activity and we had a very interesting quote from Dianne Hackborn back in 2016 that once we've gotten into this entry point to your UI we really don't care how you organize the flow inside 2016 and I think it was controversial I guess in 2016 maybe we'll just say it had 77 comments on Google+ all right so a lot of people were really enthusiastic about this post but really like what what does it mean right like well what I think it means is that the framework shouldn't care the framework shouldn't care about your applications architecture right it needs to provide the hooks needed for the android framework to start your application but you should probably care about the architecture if you're out that's why you're all here today and I love having you here so the biggest problem is that you really don't know what an activity is actually going to do so what's the default animation for an activity well it depends depends on the version of Android what manufacturer you're on and even what seems they've the user has selected right so similarly we had property animations that were added in API 11 and they're much superior to view animations and the thing is is that while these new things become possible on new versions of Android they're not always applied to everything and things like activities they don't support property animations at all even today and even if we were to add them in the next version of Android maybe the letter after P we wouldn't be able to back port them because they're part of the framework right so thinking about all these things it's like well okay what what should an activity be used for right well why why do we even have activities they're useful as this entry point but then beyond that you're in your realm right you're in your realm of what your app needs to do and you don't necessarily need to rely on activities being the thing that you have to work with so let's take an example here we have some code like this we're calling start activity and we have to use activity compat because we want to a shared element transition alright so great our intent and then we say oh we want to have this one element shared well what API does actually work on well it depends on what you mean by work right it technically launches an activity that's true but it's only actually going to do a shared element transition on newer devices API 21 plus and really how many compactions do we need in our life it technically works you're saving some API checks but really this isn't the prettiest code to look at and similarly are there any hidden gotchas right if you are testing this on the latest version of Android if you test it on an older version of Android are you actually going to get the same experience well in this example I actually ran into this and I was like oh like well things are fading in and out and like sure I chose fade but it turns out you need to exclude things like the status bar a navigation bar otherwise still flicker and I was like well okay well that's fun I wouldn't have ever known that unless I tried it once and I tried it on a whole bunch of different devices and turns out on some devices it's totally fine and other devices not so much so there's a lot of little hidden gotchas here that you can't actually control or rely on in your app another example where we have multiple activities right and really each activity is kind of its own component in your app right so if you have two activities and you want to share data between them well there's not really a scope for that there is a scope for that but it's called your application scope it's the same scope that's shared by services and everything else in your app but you really want kind of a shared scope that's just within a couple of components so this is a structure that Android provides but it's maybe not the one you actually want to use so what you actually want is you want to build the layering you actually need so in this case we can have multiple things multiple destinations within an activity right and share in across each of these destinations by using the activity scope as an actual useful element now so for example you could have a shared view element or view model that both destinations talk to so one destination could put data in and the other one could observe changes to that data right you don't need to work at the application scope level for this to work so I mention this for a destination so what is a destination well really it's just a subsection of your UI right so for most destinations they're going to take over the majority of your screen right like when you move from one screen to the next screen in your app it's gonna change the vast majority of your screen right maybe you have some global navigation like a bottom nav or maybe you have an action bar at the top but the rest of this is all in a destination so the sub section of your I we have a word for this it's called a fragment but a fragments really just one implementation of this idea of having separate destinations for each screen in your app and really this fragment is serving as kind of the view controller right the thing that isn't a view itself but something that owns and changes those views which are really more about display all right so really the activity instead of owning all of this business logic and things like that is really as small as is physically possible and really only working on the kind of that shared UI that's shared across all destinations so what we're thinking about this like if we're moving from this activity world to a destination rolled we really want to make that world as easy as possible right otherwise why it would be moved and we focused on kind of two things one was the that global UI kind of thing like how can we make that part easy right like it's something that every app kind of has the same kind of patterns and we really don't want that to be some that takes a lot of effort to do also the navigating between destinations right that start activity activity compat thing can we make that even easier so we started the navigation architecture component and we introduced it to you all in IO this last year in 2018 it is still an alpha right now and we're looking to fill out all the feature gaps before taking it to 1.

0 here soon but really what this allows us to do is take a super simple activity like this the content view will set us action bar because that's a thing right and we want to make this smart we want to make this useful but we still want it to fit on this one code slide so some of this is we need a nav controller a nav controller is really the heart of navigation it's the thing that knows everything about how your app works via a navigation graph and we can get one we're using Kotlin here column extensions and call fine nav controller and here we're just giving it the ID of a nav host fragment the nav host fragment is basically that part of your UI that's going to change whenever you change destinations we'll also add an app bar configurations this is what controls the up action and what needs to happen when you move up from a destination and how do we hook those up nice one-liner that just says set up the action bar gives us an app controller and gives it an app bar configuration now because we're using a drawer layout here in our app bar configuration we also want to open and shut the drawer when you hit the UP button so we'll call navigate up in our support navigate up and we've set up our whole action bar now it changes titles as our destinations change we're good if we want to add the navigation view and make sure that we can click on things in our side nav and they go to the right place that's again one line because we can do this all because the nav controller knows about our destinations in our app so then how do we actually do navigate actions if we're not doing fancy one-liner stuff well we can get a nav controller from basically anywhere if we're in our activity news find nav controller it's even easier from a fragment we can just call find nav controller and we built the Kotlin extension for this and similarly even from a view any view that's created by any fragment in your navigation graph can just call find have controller from a view so you have this reference to it from basically anywhere and we really tried to think alike all right well you know if you have arguments to something how do we make this nice so we built a Gradle plugin called safe args which for every destination in your graph such as this main fragment we generate a directions object which has a nice simple show profile method which gives you a directions object with type safe arguments that you define in your navigation graph and then you just call navigate and that's it will take care of all of the fragment transaction and all of that sort of stuff for you so this makes a lot easier but we can really go a lot farther with navigation so has anyone ever built an intent filter before deep linking in your app has anyone enjoyed that experience great one person enjoyed that experience and really you have to do this because this is what the android framework knows right it knows I can par as an intent filter and start an activity right but in so oftentimes like that's not quite enough I'm you need to go a little bit farther right so what we've done in navigation is for any one of your fragments any destination in your graph you can add a deep link it's a simple one-liner and you can even add arguments right here and we'll parse those out even for things like query parameters will parse those out and give you them as arguments to your destination and then because no one likes writing intentional elders will also generate the intent filters for you by adding a navigation graph so this was something we actually added to manifest merger to kind of generate that for you so all this layering helps us build nicer api's but it also makes it easier to test your application right if you're testing at the activity level all of a sudden that means okay well how do I test that start activity actually did the right intent and we have to build extra testing frameworks on top of testing frameworks to try and mock out these things but if we're moving towards more of a single activity model into the navigation controller world like we still want to test all of those things we want that to be easy to test so rule number one of testing things at the destination level is don't test at the destination level it's really the number one thing with testing right is making things nice and separate and extracting some of that business logic out of a destination and into something you can test in isolation right so example a view model is a really nice place to put some of your business logic because you can test it in isolation alright we have a view model provider Factory for providing view models where you can just inject things into your view model test that totally separate from your UI but that doesn't mean you don't want to test any of your UI stuff at all right we have espresso tests for a reason right we want to make sure that all parts of our app work well and are testable so how can we do this so last this Monday we released fragment 1.

1 the first alpha and with this became a new artifact called fragment testing which is about like six years overdue and it's really around being able to test your Android X fragments in isolation right separate from an activity separate from everything else but being able to test and verify that that fragment doing the right thing so super useful for things like espresso tests where you do want to test that logic right your business logic separate object but your UI logic right what happens when you click that button is still something that we want to verify is correct now the nice part about this it's called fragment scenario because it's actually built on a different class called activity scenario which is part of the Android X testing team and actually the testing team was instrumental in getting fragment scenario out there but the best part about this whole scenario is that it works both on instrumentation tests tests on your actual device and on robolectric so you get one test framework that works on both of these so really exciting opportunity and something that now you can test with fragments so what does this look like so let's say we want to test our profile fragment right we did our directions thing right we're passing in a fake user ID here and we call launch fragment in container not that's it the this one line has both created an empty hosting activity added the fragment to it and waited it for it to be resumed and now it's ready like you can now use this fragment so if you want to call on fragment and run some code on your fragment and say like well is the fragment in the right state great you can do that here we're just gonna check to see if our args are what we think they are we've passed in a user ID we can use the other half of safe args using the args class another generated class and just say like well is the args user ID actually equal to the user ID we passed in right did we not mess up on all that sort of stuff but you can see if you could run any logic any method on your fragment right from here or we just run espresso tests right you say is the username actually equal to the user ID we passed in right when we click the subscribe button does it actually change the text to subscribed right does it do its thing right and all we can do this with just that one line of launch fragment in container four the users it'll be fragment scenario launch in container we obviously make that a little bit nicer for you guys but you don't test a fragment in isolation because you know fragments do talk to other fragments right and like I work on navigation so there's that other bit of testing of how can we test the links between different destinations right between different fragments and really the nice part here that we have because we're using these higher-level components and not something like activity is that we have unlockable layer right one of the things that we found when building navigation is that most companies once they got to a certain point and they're like wow we should add some testing and they're like wow we can't really test start activities so they built their own navigator which just provides a layer to mock out the start activity calls well that that layer is handled for you it's called nav controller we test nav controller so now what we can do in our activities is just mock out that nav controller and confirm that yes you're calling the right navigate calls so here we have our profile fragment again and now it's getting our user ID and really what we want to test is this on view subscribers button alright so you could tell like all right well we click this and like oh my god it's like doing something complicated in the fragment how are we going to test this here it's calling navigate right like how can we make sure that this is actually doing what we want it to do well it's pretty easy we can do our scenario thing just the same launch fragment and now we can just mock out or a nav controller and now you call on fragment and what we're doing here is actually just creating our own nav controller right there's no nav host here but we can just eject one right this is actually what nav host fragment is doing under the covers it's calling sat nav controller on a view and saying here's my nav controller but now we've done is from this fragments point of view it has a navigation controller all those fine nav controls or calls that normally you'd have to inject something in to get your nav controller now it just it just works like they're in there right and now we can just run espresso tests and say like click on the view subscribers button and the nice part is is that because we're using these directions class we can use them also in our tests and because they implement equals we can just do a simple verify and say verify did you actually navigate to where we think you're navigating all right and even if there's a lot of parameters in there if there's extras and other options in there we can now just verify right and this makes it so much easier to test those interconnections between each destination so nav controller is kind of a special case because we find an app controller right so many other things aren't a service locator kind of pattern it's we need to inject in those dependencies and this is another one of those like 6 years too late kind of a thing but we're finally working on it so there's a class in Android P called app component Factory which allows you to construct activities services broadcast receivers and services all via dependency injection right you get a chance of calling the constructor instead of the system calling your constructor same thing here with fragments where now you can't actually do constructor injection into fragments right you no longer need to only have a no args constructor to use fragments you can use a fragment factory to instantiate your fragments for you so this is really useful also for cases where your fragment was like casting your activity to something I know we probably still have a template that does this we'll fix that and there's a lot of ways where really we want to inject in all of those external dependencies so we can test again in isolation and fragment factory works great with our fragment scenario so what does this look like we know how to test a view model right it's just an object you instantiate it you do the thing and it has a real method called subscribe right but really we want to test our fragment and our fragment has a non subscribed method that calls you model dot Subscribe right and it does its thing how do we get this view model well we can inject the factory itself inject the view model Factory and here we're using some of the other new stuff in fragment 1.

1 this by view models another collin property delegate that does all that view model providers of kind of stuff for you but we now have a fragment that well we've injected something but then we still need to test like okay well did it actually call subscribe right we're back to the same situation of building testable code so we can build a navigation activity this is what it's going to look like in real life right we're going to inject our view model factory and then because code is hard and I wanted to write things on slides I built a helper class called an initializer fragment Factory that basically you just call add initializer for each fragment and we call that method to construct your fragment rather than use the default no Arg constructor so a little bit of magic there's a link here if you want to check it out we're looking at trying to integrate this more deeply into the actual library itself but once you've called this fragment factory now whenever your activity creates a profile fragment instead of using that no art constructor it's gonna use this constructor it's gonna pass in our view model Factory great so our activity looks fine but our tests how's that look well we create a mock of our profile view model and then we can set up a factory for it and then again kind of use a fragment factory here that again does the same type of thing where we're passing in our mock view model Factory and then our scenario looks almost the same we just add it in and add instead of just the arguments also the fragment Factory great now we can do our same thing on view perform the click and then verify that yes are mocked out view model did the SUBSCRIBE call alright so now we have a testable fragment a testable view model and we've injected all of the dependencies into our fragment all right we actually have a testable thing now we are looking at some improvements to this API because you know we don't we want make this even easier so in this case because we know you're constructing a profile fragment but we want to change this into is actually something that looks like this where you can say launch and then give it a method saying oh like you launched this fragment and specifically give it the constructed out instance of your fragment so you don't have to actually know that oh it's using a fragment factory under the hood you can test just one fragment just fine now there are a few cases where you might think oh man maybe I do need multiple activities and there's got to be reasons to use multiple activities besides just momentum right I understand a lot of apps if you have multiple activities right now this isn't actually an easy sell right so there are a few cases where even today we do recommend using multiple activities not a lot though so what I'd like to say is you don't actually need multiply DVDs what you need are multiple tasks so what are tasks tasks are actually the thing that users are actually interacting with so a task is a stack of activities and each task has a back stack so in your overview menu here each one of these entries isn't just an activity it's actually a whole task stack right so you're only just seeing the topmost activity of that stack right so each element here is a stack right when you're doing split screen multi tasks side-by-side right on Chrome OS devices things that support floating multi-window each one of these tasks is a window it's a one-to-one between windows and tasks not activities and windows tasks and windows right so launching a new task on one of these Chrome OS devices gives you a new window right so your app maybe it doesn't need multiple activities but maybe it wants multiple windows right so this is the case where yes you need to use activities under the hood each one of these tasks is going to be a separate activity but you may not use some of the other things such as a stack of activities in one task so what does this actually look like a lot of this is that there are a lot of different ways of saying new tasks right has anyone looked at all those wonderful launch mode flags and all that fun yeah how many people have you are still saying okay well I'll say that there were a lot of good flags out there in Android one they were great back in Android one in today in 2018 they're maybe not the best thing to use what you actually want to use is document launch mode now document launch mode was added actually an API 21 so please like if you're thinking about pre API 21 first what are you doing and second probably try and steer away from hacky solutions maybe it's just not where for those users but try and avoid things like launch mode flags and task affinity and those type of things because while the framework does honor those maybe doesn't honor them in the way you want them to honor there's certainly a very different kind of thing so what can you actually do with document launch mode well the biggest thing is multitasking right if you can have multiple tasks then you can have multiple windows right you got multiple entries in your overview screen so the first way of really doing multitasking is into existing now into existing basically means whenever I launch this activity that activity has its own task right every time you launch this activity it has its own task but if we already have launched that task don't create a second and a third copy right so this is really useful for things like documents conversations things where someone might want to side by side compare two different documents right if they're copy pasting from one document to the other they're not gonna exit out of one dock open one copy it and then open the other one and then copy it into there right this is kind of taking that multitasking model that is Android that is that recent screen and making it so that your app actually gets to use this now of course the into existing assumes you have some notion of uniqueness right so it does assume that from an intent filter equals kind of point of view like if you're using the data URI on your activity that there is some sort of unique ID write a conversation ID a document ID something to uniquely define that task in and of itself now one great example of this that you can try out on your phones is Google Docs so Google Docs when you open a doc it actually launches it in another task and if you have multiple Doc's you can actually load them up side-by-side on a phone two different windows on a Chrome OS device and just works right even though it's one app it can have multiple windows and really allow a different level of multitasking between different things so another big one is creating new content so new contents a little different because there's not really any unique ID right but you still want that kind of multitasking behavior where you can reference existing material while you're creating something new all right so the always flag is very similar to into existing but it just always creates something new right Wow it's like self descriptive names we can do this guy's and it allows you to do multiple things at once right that's great so one example this is Gmail so Gmail actually uses this kind of mode when you create a new email so this allows you to create a new email and reference your existing email at the same time magic write it this is the equivalent on mobile of when you do it on the web and it pops up a little Mole that's separate from the other one right you still need that other material as reference even when you're creating something new of course on a phone or on a tablet it looks slightly different the other case is picture and picture now a picture and picture actually has two entirely separate modes for how you want to approach picture and picture one is using a separate task right this would be a separate activity just for your playback so this is really common on Android TV devices for example Google Play movies & TV uses this approach so that you can actually put things into picture in picture mode and then browse through other movies so in this mode it's very much that you have a specific picture and picture mode button in your UI the other mode is using just a single task one activity right you actually don't need anything at all here and this is the approach the things like duo and Google Maps use where your whole task is becoming the picture-in-picture right so when would you want to choose one or the other and it's really this case where once I'm in picture-in-picture mode if they were to click my launcher icon what would happen because the launcher icon always launches the default task right of your app so in duos case where they only have one task when you launch that duo from your launcher icon it's just going to pop open the picture-in-picture you go from a little tiny picture picture to full screen because it doesn't make sense to have multiple conversations going at the same time you're never going to replace one with the other you're never going to continue to reference something even though something is already going alright so that's kind of the differentiator here where do you want to be able to browse and picture and picture at the same time if you do want to browse then yes you're using the android framework and therefore they need to position those separately two separate tests separate activities but that's that's kind it the one thing I didn't mention are things like instant apps now in synapse kind of works at the activity level right you call startactivity it downloads your instant at module but there's actually some really exciting things that are being worked on by the Play team I think there was a talk here at Android dev Sun but a lot of it is around the instant experience for your app bundle making your whole app instant and also dynamic feature modules now these are really interesting ways of adding things on to the thing that don't require a specific app architecture to implement right we can't actually do more things with this so for the instant experience it's adding this distribution instant equals true and this means that someone can try your entire app write your entire base module all one's totally instant right and this works really well with things like app links so those deep links that I said you could add to any destination you can also make them into app links by adding the Auto verifiable true and that means you skip the disambiguation and when someone launches your a deep link on the web they're going to open up your instant app experience right they're going to download that whole base module for you and your whole navigation graph is already there ready to go but this doesn't work if your app is too large right so you want to dynamically deliver things that are not used very often or rather big things right that's what dynamic feature modules are all about about being able to download them on demand now the really interesting part about dynamic feature modules is that you're adding classes right you don't need to add activities you could add just destinations just a number of fragments all right so in this case you can add new destinations on demand just because you've built out a XML file for your navigation graph statically each one of these dynamic feature modules can also add their own navigation graph so this means that we're not tied to separate activities we can now actually still use one activity now there's still more to be done here both on the play side and on the navigation side but we want to make this really easy for developers to use so what we want to get to is where you can add something to your navigation graph with just a feature tag just like you'd add a fragment tag or something like that and when you navigate to this destination that's actually going to do all of the downloading and making your feature module available from whatever your split name is so that's the world we want to get into where you can use a single activity where you can use a lot of the useful things like deep linking and navigation without necessarily contorting your app architecture around everything else so really like to end this with OneNote a lot of you have existing apps that have very different kind of experiences and I'd like to say do what's right for your app I think single activities great I was writing a new activity it would also be single activity but I realize that doing going to your p.

m.

and be like hey let's rip the whole app apart is sometimes a hard sell some of them don't like your current app so maybe you'll actually get some yeah okay go for it really depends on your own experience if you find yourself contorting your own experience and it's not making sense to you don't do it right if something is working that's good keep it working right but if you're finding you're running into issues you're having inconsistent behavior right or you want to do things like share a few models maybe that's the time to think about moving towards a single activity structure so thank you QA your outside really appreciate you all coming [Music].

Hãy bình luận đầu tiên

Để lại một phản hồi

Thư điện tử của bạn sẽ không được hiện thị công khai.


*