Single activity: Why, when, and how (Android Dev Summit '18)

[MUSIC PLAYING] IAN LAKE: 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 AndroidX Activity Artifact and Loaders And today, I wanted 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 are 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 So when the Android framework goes to start your application from a launcher icon or an app shortcut or what you see when you’re doing multi window, those are all activities So they really are kind of the entry point into your app’s UI 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.” Now, this was 2016 And I think it was controversial, I guess, in 2016 Maybe we’ll just say it had 77 comments on Google+ So a lot of people were really enthusiastic about this post But really, what does it mean? Well, what I think it means is that the framework shouldn’t care The framework shouldn’t care about your application’s architecture It needs to provide the hooks needed for the Android framework to start your application But you should probably care about the architecture of your app 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 It depends on the version of Android, what manufacturer you’re on, and even what themes the user has selected So similarly, we had property animations that were added in API 11 And they’re much superior to view animations And the thing is that while these new things become possible on a new version 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 backport them, because they’re part of the framework So thinking about all these things, it’s like, well, OK, what should an activity be used for? Why do we even have activities? They’re useful as this entry point, but then beyond that, you’re in your realm 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 startActivity And we have to use ActivityCompat, because we want to do a shared element transition So create our intent And then we say, oh, we want to have this one element shared Well, what API does this 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 compact shims 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?

If you are testing this on the latest version of Android, if you tested 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, well, things are fading in and out And it’s like, sure, I chose fade But it turns out you need to exclude things like the status bar and navigation bar Otherwise, they’ll flicker And I was like, OK, 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 it turns out on some devices, it’s totally fine On 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 And really, each activity is kind of its own component in your app 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, and share information across each of these destinations by using the activity scope as an actually 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 You don’t need to work at the application scope level for this to work So I mentioned this word “destination.” So what is the destination? Well, really, it’s just a subsection of your UI So for most destinations, they’re going to take over the majority of your screen Like when you move from one screen to the next screen in your app, it’s going to change the vast majority of your screen 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 subsection of your UI We have a word for this called a fragment But a fragment is 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 The thing that isn’t a view itself, but something that owns and changes those views, which are really more about display 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 that shared UI that’s shared across all destinations However, thinking about this, if we’re moving from this activity world to a destination world, we really want to make that world as easy as possible Otherwise, why would we move? And we focused kind of two things One was that global UI kind of thing How can we make that part easy? It’s something that every app kind of has the same kind of patterns And we really don’t want that to be something that takes a lot of effort to do Also, the navigating between destinations, that start ActivityCompat thing, can we make that even easier? So we started the Navigation architecture component and we introduced it to you all in I/O this last year in 2018 It is still in 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, this set content view We’ll set an 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 NavController A NavController 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, Kotlin extensions– and call findNavController

And here we’re just giving it the ID of a NavHostFragment The NavHostFragment is basically that part of your UI that’s going to change whenever you change destinations We’ll also add an app bar configuration This is what controls the up action and what needs to happen when you move up from a destination And how do we hook that up? Well, we have a nice one liner that just says, set up the action bar It gives it a NavController 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 then go to the right place, that’s, again, one line We can do this all because the NavController 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 NavController from basically anywhere We’re in our activity We can use findNavController It’s even easier from a fragment We can just call findNavController And we’ve 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 findNavController from a view So you have this reference to it from basically anywhere And we really tried to think like, all right, well, 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 defined in your navigation graph, and then you just call Navigate And that’s it We’ll take care of all of the fragment transaction, all of that sort of stuff for you So it makes it 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 It knows, I can parse an intent filter, and start an activity But oftentimes, that’s not quite enough You need to go a little bit farther 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 We’ll parse those out, and give you them as arguments to your destination And then, because no one likes writing intent filters, we’ll also generate the intent filters for you by adding a navigation graph So this is something we actually added to manifest merger to kind of generate that for you So all of this layering helps us build nicer APIs But it also makes it easier to test your application If you’re testing at the activity level, all of sudden that means, 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 up these things If we’re moving towards more of a single activity model into the navigation controller world, 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 the destination level It’s really the number one thing with testing, 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 So an example, a view model is a really nice place to put some of your business logic, because you can test it in isolation 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 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? Last– this Monday, we released Fragment 1.1, the first alpha And with this came a new artifact called fragment-testing, which is about like six years overdue And it’s really around being able to test your AndroidX fragments in isolation, separate from an activity, separate from everything else But being able to test and verify that that fragment is doing the right thing So super useful for things like Espresso tests, where you do want to test that logic Your business logic, separate object, but your UI logic, 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 FragmentScenario– it’s actually built on a different class called ActivityScenario, which is part of the AndroidX testing team And actually, the testing team was instrumental in getting FragmentScenario 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’ve got one test framework that works on both of these So a 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 We did our directions thing We’re passing in a fake user ID here And we call launchFragmentInContainer That’s it This one line has both created an empty hosting activity, added the fragment to it, and waited for it to be resumed And now it’s ready You can now use this fragment So if you want to call onFragment, and run some code on your fragment, and say, well, is the fragment in the right state? Great You can do that Here, we’re just going to 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? Did we not mess up on all of that sort of stuff? But you can see you can run any logic, any method on your fragment right from here Or we just run an Espresso test You say, is the user name actually equal to the user ID we passed in? When we click the Subscribe button, does it actually change the text to subscribed? Does it do its thing? We can do this with just that one line of launchFragmentInContainer For Java users, it’ll be FragmentScenario [? launch ?] in container We obviously make that a little bit nicer for you guys But you don’t test a fragment in isolation, because fragments do talk to other fragments And like, I work on navigation So there’s that other bit of testing of, how can we test the links between different destinations, 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 a mockable layer 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 layer is handled for you It’s called NavController We test NavController So now what we can do in our activities is just mock out that NavController 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 viewSubscribers button So you can tell, 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 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 mark out our NavController And now you call onFragment And what we’re doing here is actually just creating our own NavController There’s no nav host here But we can just inject one This is actually what NavHostFragment is doing under the covers It’s calling setNavController on a view, and saying, here’s my NavController But now what we’ve done is from this fragment’s point of view,

it has a navigation controller All those findNavController calls that normally you’d have to inject something in to get your NavController, now it just works They’re in there And now we can just run Espresso tests, and say, click on the viewSubscribers button And the nice part 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? 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 And this makes it so much easier to test those interconnections between each destination So nav control is kind of a special case, because we find a NavController 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 “six years too late” kind of a thing, but we’re finally working on it So there’s a class in Android P called AppComponentFactory, which allows you to construct activities, services, broadcast receivers, and services, all via dependency injection You get a chance of calling the constructor, instead of the system calling your constructor The same thing here with fragments, where now you can actually do constructor injection into fragments You no longer need to only have a no-args constructor to use fragments You can use a FragmentFactory to instantiate your fragments for you So this is really useful also for cases where your fragment was like passing your activity to something I know we probably still have a template that does this We’ll fix that And there’s lot of ways where really we want to inject in all of those external dependencies so we can test again in isolation And FragmentFactory works great with our FragmentScenario So what does this look like? We know how to test a view model It’s just an object You instantiate it You do the thing And it has a real method called Subscribe But really, we want to test our fragment And our fragment has an onSubscribe method that calls viewModel.Subscribe And it does its thing How do we get this viewModel? Well, we can inject the factory itself, inject the viewModelFactory And here we’re using some of the other new stuff in Fragment 1.1 that’s by viewModels, another Kotlin property delegate that does all that viewModel 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, OK, well, did it actually call subscribe? We’re back to the same situation of building testable code We can build a navigation activity This is what it’s going to look like in real life We’re going to inject our viewModelFactory And then because code is hard and I wanted to write things on slides, I built a helper class called Initializer FragmentFactory That basically just calls 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 is 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 FragmentFactory, now whenever your activity creates a profile fragment, instead of using that no-arg constructor, it’s going to use this constructor It’s going to pass in our viewModelFactory So our activity looks fine But our test, how does that look? Well, we create a mock of our profile viewModel And then we can set up a factory for it And then, again, kind of use a FragmentFactory here that, again, does the same type of thing where we’re passing in our mock viewModelFactory And then our scenario looks almost the same We just add it in and add instead of just the arguments, also the FragmentFactory Great Now we can do our same thing on view Perform the click And then verify that, yes, our mocked out viewModel did the subscribe call So now we have a testable fragment, a testable viewModel And we’ve injected all of the dependencies into our fragment We actually have a testable thing Now, we are looking at some improvements to this API,

because we want to make this even easier So in this case, because we know you’re constructing a profile fragment, what 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, launch 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 FragmentFactory 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 I understand a lot of apps if you have multiple activities right now, this isn’t actually an easy sell 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 multiple activities 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 So you’re only just seeing the topmost activity of that stack So each element here is a stack When you’re doing split screen multi window, that’s two tasks side by side On Chrome OS devices, things that support floating multi window, each one of these tasks is a window So a one to one between windows and tasks– not activities and windows, tasks and windows So launching a new task on one of these Chrome OS devices gives you a new window So your app, maybe it doesn’t need multiple activities, but maybe it wants multiple windows So this is a 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? Well, a lot of this is that there are a lot of different ways of saying new task Has anyone looked at all those wonderful launch mode flags and all that fun? Yeah How many people that have used it are still sane? OK Well, I’ll say that there were a lot of good flags out there in Android 1 They were great back in Android 1 Today, in 2018, they’re maybe not the best thing to use What you actually want to use is documentLaunchMode documentLaunchMode was added actually an API 21 So please 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 worth it 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, it maybe doesn’t honor them in the way you want them to honor it They’re certainly a very different kind of thing So what can you actually do with documentLaunchMode? Well, the biggest thing is multitasking If you can have multiple tasks, then you can have multiple windows You can have multiple entries in your Overview screen So the first way of really doing multitasking is intoExisting Now, intoExisting basically means whenever I launch this activity, that activity has its own task 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 So this is really useful for things like documents, conversations, things where someone might want to side-by-side compare two different documents

If they’re copy-pasting from one document to the other, they’re not going to exit out of one doc, open one, copy it, and then open the other one, and then copy it into there 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 intoExisting assumes you have some notion of uniqueness 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, 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 a another task And if you have multiple docs, you can actually load them up side by side on a phone, two different windows on a Chrome OS device And it just works 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 content is a little different, because there’s not really any unique ID But you still want that kind of multitasking behavior where you can reference existing material while you’re creating something new So the Always flag is very similar to into existing, but it just always create something new Wow, it’s like self-descriptive names We can do this, guys And it allows you to do multiple things at once That’s great So one example of 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, right? 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 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-in-picture Now, picture-in-picture actually has two entirely separate modes for how you want to approach picture-in-picture One is using a separate task So this would be a separate activity just for your playback So this is really common on Android TV devices For example, Google Play Movies and 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-In-Picture Mode button in your UI The other mode is using just a single task, one activity 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 activity 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 of your app So in Duo’s 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-in-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 So that’s kind of the differentiator here, where do you want to be able to browse and picture-in-picture at the same time? If you do want to browse, then yes, you are using the Android framework, and therefore they need to position those separately, separate tasks, separate activities But that’s kind of it The one thing I didn’t mention are things like Instant Apps Now, Instant Apps kind of works at the activity level You call startActivity It downloads your Instant App 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 Summit But a lot of it is around the instant experience for your app bundle, making your whole app instant, and also dynamic feature modules These are really interesting ways

of adding things onto the thing that don’t require a specific app architecture to implement We can actually do more things with this So for the instant experience, it’s adding this distribution instant equals true Now, this means that someone can try your entire app, your entire base module, all at once, totally instant And this works really well with things like app links So those deep links that I said you can add to any destination, you can also make them into app links by adding the autoVerify=”true” And that means you skip the disambiguation And when someone launches your deep link on the web, they’re going to open up your instant app experience 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 So you want to dynamically deliver things that are not used very often or rather big things 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 You don’t need to add activities You can add just destinations, just a number of fragments So in this case, you can add new destinations on demand Just because you’ve built out an 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 I’d really like to end this with one note 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 activity is great If I was writing a new activity, it would also be a single activity But I realize that going to your PM and being 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, OK, go for it 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 If something is working, that’s good Keep it working But if you’re finding you’re running into issues, you’re having inconsistent behavior, or you want to do things like share viewModels, maybe that’s the time to think about moving towards a single activity structure So thank you Q&A will be outside I really appreciate you all coming [MUSIC PLAYING]