Fragments: Past, Present, and Future (Android Dev Summit '19)

IAN LAKE: Thanks for coming. My name is Ian Lake. JEREMY WOODS: And I’m Jeremy Woods. IAN LAKE: And today we’re here to talk to you about Fragments– not just Fragments of today, but Fragments of past, present, and we’re going to give you a little sneak peek about where we’re going with Fragments in the future. So starting with the past, Fragments were introduced in Android Honeycomb API 11. But we don’t really talk about Honeycomb. So we’ll move on to what they were originally supposed to do. And Fragments were really designed from the beginning to kind of be micro activities. Right? Remember way back in the day when everything was within your Activity? And Fragments were really kind of that very first step into moving code out of your activity and into something smaller. But that also meant that we inherited a lot of the API service that was inherent in Activity. So that means that we got things like all the action bar menu stuff because there wasn’t something called Toolbar yet. It also meant that we had things like context menus, which does anyone actually use context menus anymore? They’re a thing. But they don’t really need to be on activities or Fragments, per se. They could be on views themselves. It also meant that we got a lot of custom hooks. So all the things that were normally just sent to your activity are now also sent to your Fragment. For instance, things like onActivity result– that’s pretty useful, people use that, right– things like permissions. When Android came out and we added runtime permissions, of course Fragments got it, because Activity got it. And also things like, On Multi-Window Mode changed. Or On Picture In Picture Mode changed. And these are all kind of things that we just got kind of for free, being Fragments and being this idea that there are micro activities. But when you think about it, a lot of these things aren’t specific to Fragments. It would be nice if anything could get these kind of callbacks. So we’ve kind of gone through this kind of existential crisis on kind of moving away from this idea that, it’s like, oh, just because an Activity can do it, a Fragment can do it. So that kind of leads us to today. That was 2011. And we’ve tried to come a long way since then. And a lot of this is really trying to restructure what is our goal for Fragments? And some of this is really just being a focused API service, really making sure that it’s just the core piece that you need with predictable, sane behavior. That means no surprises, no things that just randomly don’t work. But it also means we don’t want to break existing consumers. Right? And that means binary source or even behavior compatibility. Now along the way, that does mean that we do have times when we introduce a new alternative, a new API, that is predictable and nice. And we deprecate the old API because we can’t actually remove it until we have a good alternative that gives you something much better to work with. But the idea is that we do want to release a 2.0 at some point, where as long as you’re not using the deprecated APIs, if you’re all on the good new API surfaces, then it’ll be an easy transition over and we’ll be able to cut out a lot of code out of Fragments to support these kind of more legacy cases. So what are we doing to get there? Well, the first part of providing kind of a sane API is a testable API. If you’re writing code that you can’t test, that’s no good. And if we’re writing a library that you can’t test, code-written with that library, that’s not going to work either. So in 2019 here, we really want to make that better. So we have a Fragment testing artifact now that offers Fragment scenario. This is really a way of testing just one Fragment in isolation. And we worked really closely with the AndroidX test team. So this is actually built on activity scenario, which means it works for instrumentation tests and it works for robo electric tests. Now we wanted to make sure that this had kind of a nice, small API service. So really, it has one main method of on Fragment, which takes a lambda, gives you the Fragment instance that’s available already. And it also kind of gives you a lot of the hooks that you need that makes it really easy to test things lifecycle and recreating your whole Fragment. So what does this look like? Well, it’s actually pretty easy. You create your scenario using Launch Fragment in Container, which will do all of the creating, your activity, adding the Fragment, moving it to resumed. And then again just do a [? special ?] test, right? You can say On View, click it. And the Fragments hierarchy is already there. It’s already ready. And then we can use on Fragment to then check our internal state, check to see did it actually handle the click correctly? For example, if you’re doing more complicated things, if you want to check how, when you move through lifecycle states, you can just call the scenario Move to State and move it to Start It, or Create It, or Resumed. Same thing with actually testing recreation– do I save and restore my state properly? Just call Recreate. You can check your state before Recreate. You can check your state after Recreate. And that’s all you really need to do. So the other bit is we have this recreation thing. Well, that’s really just one of many ways that you need to instantiate a Fragment. So we also have cases where you’re adding a new Fragment in first place. You need to instantiate a Fragment to give it to Fragment manager. But also, we have things like Fragment Scenario. You need to inflate the Fragment to add it to your UI for testing. So we wanted one way of doing this. And this one way is FragmentFactory. So this gives us kind of a way to now finally be able to do constructor injection into your Fragments and move away from this requirement that you need a no-argument constructor. Now you can actually build something that does all of this for you. So what does this look like? Well, a simple FragmentFactory, it’s really just one method, instantiate, where you’re given a class name. And it’s up to you to load the class. So super dot instantiate will just use reflection, call your no arg constructor. But really, you can do anything you want with this. If you watch the opinionated guide to dependency injection, this is the kind of thing that we’d love to do for you at some point so you don’t have to write this. But even right now, if you just pass in arguments into your factory, then you get a really easy way of just passing those onto the Fragments that care about them, using constructor injection here. So then all you need to do in your activity is just call FragmentFactory and set it equal to your Factory, preferably before super.onCreate, because that’s when you’re going to be reinstantiating Fragments. Now, the other bit that we want to do to kind of fix up all of this consistency everywhere is– sorry, is all the other places that you have Fragment creation. So if you’re doing a Commit and you’re adding a Fragment– now instead of having to do this whole instantiating and doing all that constructor injection, you can just add it with a class name. Here, we’re using the [INAUDIBLE] [? reified ?] version. So now we’ll delegate to your factory. And now you have one code path for doing, instantiating, here for the very first time when you’re adding or replacing a Fragment. Similarly, when you’re doing your Fragment scenario, you just pass the factory. It could be a mock factory that you’ve created just to give mock dependencies. Or it could be a real factory if you’re trying to do more integration test-style work. So I’m going to pass it off to Jeremy to talk about another area we’re working on– consistency. JEREMY WOODS: Thanks. So one [? instance ?] we found between adding a Fragment to something like, say, a frame layout versus using the Fragment tag, is it turns out the Fragment tag adds Fragments using a totally different system from Fragment transactions. To provide consistent behavior, we built a Fragment Container View as the one true container for Fragments. Fragment Container View extends frame layout. But it only allows Fragment views. This means if you’re not a Fragment, you probably shouldn’t be using this. It also can replace the Fragment tag when you add the class or Android name attribute. So in this example, because Fragment Container actually does use a Fragment transaction under it, you don’t have to worry about– you don’t have any issues with replacing this Fragment later. Fragment Container View also gave us the opportunity to address some animation issues, specifically an issue with z ordering of Fragments. So here, we can see in the frame layout example, the entering Fragment instantly pops onto the screen. What’s actually happening here is the entering animation is sliding. But it’s underneath the exiting animation. Fragment Container View ensures the z ordering so we could actually see the sliding animation for the entering Fragment, providing predictable, same behavior across all API levels. Another longstanding issue we had is handling the system back from Fragment. To address this, we added the OnBackPressedDispatcher. Instead of adding a Fragment on the API, now any component can handle a back via a new API on the base activity class component activity. Ideally, you’d inject the OnBackPressedDispatcher for testing purposes. But here, we’re going to show you how to use it in code. So a Fragment would grab a dispatcher from its calling activity or from its activity. And then we’ll create a callback. Here, we create that callback using the [INAUDIBLE].. And we pass it this. And we can pass the Fragment because a Fragment is a lifecycle owner. So for example, in your code, the user presses back. And then we call Show Confirm Dialog when the back button is pressed. And then the user will say they want to exit anyway. We’ll disable the callback. And then we’ll use the dispatcher to actually do the BackPressed. So, no new Fragment API. Instead, we just take advantage of the existing integration between Fragments and architecture components. So an architecture component is something we want to leverage even more going forward. So for example, it should be easy to get a View model for your Fragment. And here, we created the Kotlin Property Extensions so you can get a view model at the Fragment level, the Nav Graph level, or the Activity level so your View model is always properly scoped and you have the tools to provide that. We’re also leaning to the lifecycle as well. For example, instead of using the custom lifecycle map of Set User Visible Hint, you can now just use regular lifecycle methods when you’re adding Fragments to a View page or one adaptor. And so this is currently how View Page 2 works. And only your current Fragment will be resumed. Back to Ian. IAN LAKE: Great. Thanks, Jeremy. That was a really good rundown of what’s available right now. So Fragment 1.1 had a lot of this. Defining Container View is in Fragment 1.2, which is in RC 1 as of yesterday. But the other thing is I wanted to talk a little bit more about where we’re going kind of longer term. There’s a lot of other things that we want to do. So I’m going to share a little bit of a preview of some of the projects that we’re working on. But of course, because none of this is out yet, this is all subject to change. We’re pretty happy with where this is all going. But it’s not something you can play with right today. So the first thing is Multiple Back Stacks. Some of you may have heard of this. It’s a thing. But really, some of this comes down to just that existing legacy on Android. So on Android especially, at like the Activity level, it was always a single stack of activities. And Fragments just ended up inheriting kind of that same structure where the only things that are saved are the things that are on the Back Stack. And we really want to move to a model where we kind of support both, not just the single stack approach but also the multiple stack approach. And really, that just means that we allow you to save the state of Fragments that aren’t actually visible on the screen without losing their state. So this is some of the cases that came up. It was really like, Bottom Nav, things like Navigation Views. And we have a sample app right now that uses kind of a little bit of a work-around. I say work-around– it’s some nasty Fragment code. And really, we just want to make this really easy at kind of the Fragment level. So kind of the approach that we’re taking is kind of that same ability where each one of these tabs can actually have its own stack. So it has its own state. So as you swap between these, it’s really more about saving and then restoring a different stack. And we’re taking care of doing all the state restoration and state saving as you go from stack to stack. The other bit that we want to work around is around returning results. So we’ve heard a lot of information where you’re like, how do I talk between Fragments? How do I talk between anything on Android? It’s always kind of– there’s lots of different ways of doing it, some better than others. And it turns out that the Fragment APIs we have right now are not great. So we have this whole set target Fragment API that allows you to connect one Fragment with another, and basically just keep a hard reference to another Fragment. But it basically does nothing for the lifecycle of what that other Fragment is. So if that other Fragment is on the back stack, it’s actually stopped. It’s not started. And if you actually talk to that Fragment and it’s trying to do like Fragment transactions and stuff, that’s going to fail because you’re not started. So we really don’t have any kind of guarantees around like, what state is that other Fragment actually going to be in? Additionally, we had like this idea that, oh, you can use On Activity Result. And I’m like, intense from Fragment to Fragment? That feels kind of weird. Like, we don’t really need to be reusing some of these API surfaces for callbacks. So we’re really kind of looking at this more holistically, not just Fragment to Fragment, but also things like start startActivityForResult. Can we do kind of the same kind of API surface for startActivityForResult, for Fragment to Fragment communication, from Navigation Destination to Navigation Destination? Can we provide a common API that all these things can [? time ?] to talk at so that when you’re building callbacks, it doesn’t actually matter if it’s coming from a different activity or a different Fragment. So that’s kind of where we kind of see a lot of those APIs going. I’m really excited to share some of that in just a few months, maybe. The other thing is something we’ve constantly heard feedback on Fragments is lifecycle. Like, that’s hard, right? Well, it’s like doubly hard with Fragments because Fragments have two life cycles. The Fragment itself has a lifecycle, which is when the Fragment is actually attached to the Fragment manager until it’s removed from the Fragment manager and gets destroyed. But the Fragments view has a totally separate lifecycle where when you actually put something on the back stack, its View gets destroyed but the Fragment lives on. So crazy idea, what if they were the same? So we have this idea for what if, when the Fragment’s view is destroyed, the Fragment was destroyed at the same time. And then when we want to recreate your View, we recreate your Fragment. And bringing these things together saves so much complexity and means that things like FragmentFactory and all the saving state that you have to do anyways for cases like, oh, I’m on configuration change, the process, death, and coming back, Now, that just becomes the default option. And now we only have to test one code path of like, wait what state am I in? Are my observers doing the wrong thing here because these switching life cycles? Now, I do realize this is a bigger change. So we are looking at making this kind of an opt-in kind of API at the Fragment activity kind of level. So everything in that Fragment activity would all move to this new world where we have a much more simplified lifecycles case. So we’re really looking forward to releasing all of these things. And we’d really appreciate it if you can talk to us in the Sandbox if you’ve got any feedback. Also using the IssueTracker.Google.com if you have any feedback or other feature requests that you’d love to see in Fragments. Thank you. [APPLAUSE] [MUSIC PLAYING]