Android Jetpack: manage UI navigation with Navigation Controller (Google I/O '18)

good morning thanks for getting up early to be here with us even despite the fact that some of our thunder might have been stolen by the three or four presentations before this that demoed the navigation editor luckily we have time to go into a little bit more detail than any of the previous presentations so there's a lot of good material here and I'm Lucas product manager for architecture components and with me I have Ian and Sergey who built the navigation component and navigation if you think about it is a problem that pretty much every app on Android has to solve but until now we haven't really given you anything to do that with other than start activity which for various reasons is not the best way to go about that so if you think about yeah our job of making Android development easier navigation was a common problem with no real solution and that means that there are a lot of things that you have to solve on your own and those range from how to commit fragment transactions hopefully without throwing an exception how to test that navigation is happening correctly and that the right things are happening when navigation occurs how to map deep links to various places in your app and how to keep that deep those deep link schemes up-to-date as your app navigational structure changes passing arguments from place to place again we don't give you a type safe way to do that today how to make sure that up and back take users to the right places particularly in sort of more difficult situations like someone deep linking deep into your apps navigation hierarchy and so what this means is by the time you've finished solving these problems you typically gone one of two directions you've either written 60% of a navigation framework or you've got a lot of error-prone boilerplate everywhere navigation needs to happen in your app so you've got a bunch of parallel lines of code solving these various problems and the whole structure is fairly brittle and that's why we're oh sorry and individually these problems are pretty tractable but if you look at kind of a real-world example you can see that they can get pretty hairy so say that I have an item screen in my app and it's accessible via deep link so far so good but if someone had navigated to this screen via opening the app from the home screen they would have a couple other screens on the back stack and so hitting it up we want them to take that not out of the app from the item screen we want them to go to the category screen and then the home screen and that means that if someone deep links into the app we need to synthesize these screens and add them to the up stack before showing the item screen and talking to a third-party developer he told me it's when you're in the middle of writing the code to do this to synthesize these screens and add them to the up and back stack but only on a deep link that's when you start to feel like maybe you're solving a failure of the framework so it's just help solve problems like that that we're launching navigation and what we're giving you is a visual tool that lets you edit the navigation graph of your app which is represented in XML and that lets you define a set of available navigation actions arguments you can pass from place to place things like visual transitions and then a single navigate call activates all that at runtime and so last but not least that means one thing you never have to worry about again is touching a fragment transaction with your bare hands [Applause] so what the navigation graph is essentially just a set of what are the possible destinations people can reach in my app and those usually correspond to screens but not always so you can have the navigation control or just changing the contents of a smaller part of the screen but it's a set of navigation destinations and the actions that link them and the actions really represent how you can get from place to place so to actually navigate from point A to point B in an app you're just gonna call the correct navigation action at runtime so let's take a look at what this looks like and let's switch over to the demo okay so what we see here is a set of navigation destinations and these are fragment destinations although other options are possible and the lines connecting them with the arrowheads are our actions and those actions actually generate methods that you can call at runtime this whole thing is backed by XML as you all know and love and the XML and the navigation editor have the same set of capabilities so you can use either one we're gonna add a screen here and what you're seeing is a set of available activities and fragments in my app ok so we just added an option for them to actually answer this question successfully and win the game so now we're gonna add an action to navigate to that screen great and so that navigation action has a bunch of options that you can set obviously we're going to talk about those a little bit more for now we're gonna do one thing so we're gonna say if they've gotten to this congratulations screen that means the game is over so hitting back shouldn't take them back into a game that no longer exists so what that means is we want to on that action say let's pop to the match screen and that means I'm going to pop off everything on the back stack in between this destination and the match screen so when the user gets to the congratulations screen when they hit back they're just going to go straight to the match screen so a lot of other options I can set but let's talk about that for now let's go back and look at the congratulations screen again one other thing to mention that the the key thing that is set here is the fragment class and that's what's actually instantiated at runtime and we see layout previews here because the layout the Navigation editor knows what layout is associated with that navigation graph and because it knows what layout is associated with that navigation graph I can double click on that fragment destination to get here into the layout editor great and everything that I've just done here adding this new destination to the navigation graph an action changing what that pop behavior of that action is all this stuff I can also do programmatically at runtime so navigation editor XML or programmatic all work just fine great and now I'm gonna hand this off to get to walk you through this in more detail so the first question that might come to mind is wow this is a totally new way of kind of structuring the UI every rap and kind of brings up this question immediately like okay so what what is my activity actually meant to do right so every kind of app has a very different starting point from where you guys are right now or maybe as of two days ago on how you've kind of structured the UI of your app some may be very activity heavy very fragment heavy very much in a different system all those are certainly very valid places to be but we're moving towards a model where the activity is more just an entry point into your app rather than the activity being the owner of the content of your app it's actually just what's going to store kind of that global state so the global navigation so if you have bottom navigation drawers you're still using an action bar those are the kind of things that activities manage but it delegates to what we call a nav host for content so our nice super simple activity here we have an action bar on the top the bottom nav on the bottom and this big white box in the middle that's our nav host so in the world of navigation when you navigate between different destinations in your app we're actually doing is just replacing everything inside that box right so you have kind of that global navigation outside of that and we have hooks that you can hook that up to stay in sync with the actual nav host so what does a super simple version of this look like well if you're using fragments for destinations you want to use our nav host fragment so here we just have a activity layout that is literally just our one fragment so you do this by including the navigation fragment dependency as you might expect if you're using a totally different kind of destination it would probably also have a different kind of nav host that you add here whether it's a custom view or whatever other item that they need but for a nav host fragment we've kind of set up a few convenience methods the ability to set what navigation graph you're using in XML so you don't need to do anything programmatically to set it up it'll just go to the start destination of your graph by default and then for fragments we can actually hook this up to the system back button so we offer a method to do exactly that so you don't have to override on back pressed specifically for navigation will kind of hook up all that stuff using the magic of fragments through this default nav host option so that means that our activity actually gets to be two lines of code it would be two lines but it doesn't fit horizontally on a slide but all we need to do is just inflate our layouts that content view and then hook up the UP button now normally this is a large operation but because you've told us about the structure of your graph what we can do is in this case we're using a Kotlin extension method on activity it'll just be a static method for Java users that allows us to find the navigation controller by passing in the ID in this case the ID of our nav host fragment and that just gives us access to navigate up and navigate up is going to do the right thing based on your navigation graph you don't need to actually do a lot of extra work here by giving us your graph we are able to do this for you but for most EPS just having a single thing is not actually what you have maybe you have something a little bit more so we've kind of set up a second dependency navigation UI which is really just a set of static methods that connect your navigation component with some of the material design components some of the things like bottom nav and things like that that are very much in that kind of global navigation space but of course it it's 2018 so we have KTX one that changes those static methods into extension methods on the classes that they operate on so this is really easy for Kotlin users to kind of integrate it into your app and have navigation feel like it's just something that exists in all of the components that are out there so what does this look like if we again kind of make our navigation or our activity a little bit more complicated without a toolbar on the top and we'll add a bottle navigation view and in this case we still have the same kind of app menu that you had before on a bottom navigation view but what we do to kind of hook those things up it takes two parts one your menu here is actually going to be using the same IDs that you've had on each destination so each destination has a unique ID and we can actually use those same IDs on your menu items so that kind of builds this implicit link of oh well if you click the home menu item you're going to go to the home destination of your app so in code we're just going to set up our action bar using our toolbar and then we can do the same find nav controller to get our access to the nav controller object and then we just have a column extension for activity that allows you to say set up action bar with nav controller and this does Oh quite a bit of magic but what it's doing is it's actually going to be receiving events on when you've navigated in your nav controls and using the labels that you've set up in your navigation graph to update the title of your action bar and we also have another helper method if you're using a drawer layout to automatically change it from a hamburger button into a back arrow based on what destination you're on so really kind of just those helpful patterns to make sure that those things stay in sync similarly for the bottom nav it it's just you just call set up you call set up with nav controller and redo the two-way syncing here so as you click on things in the now if it'll change the graph and do the correct transition based on the material guidelines as well as as you navigate between your app if you have separate buttons it'll actually update the selected item in the bottom navigation so this gives us a lot of power but not everyone is just using pre-built components that another team has provided you have your own custom UI right so at that point we really need to go deeper into what mouse controller actually gives you and for the super simple case you have a button you want it to go somewhere we have a convenience method create navigate onclicklistener you give it the ID of where you want to go what destination what action you want to trigger and we'll do all the work for you now this is perhaps a little bit too magical so you can't unroll it just a little bit in this case we're using another extension method on view so from any view that's been created by nav controller you can actually get a reference to your nav controller just by calling fine nav controller as you might expect and use that nav controller to call navigate and just navigate to an ID of a destination or an action in your graph nut that's it like under the covers this navigate is actually doing a lot of work so the nav controller is talking to what we call a navigator and so for fragments we were talking to a fragment navigator that we provide and that navigator knows everything about oh you called navigate I know what fragment you're going to because you gave us your class name and it's going to build all of the fragment transactions for you it's going to call add to back stack it's going to do all the things you told us to do by putting that information in your navigation graph so if you had pop up too it's going to do all that sort of stuff transitions all of that is in this one line of code and all of it can be determined either programmatically we can add additional options to this method or something that you determine ahead of time as part of your navigation graph but for a lot of these kind of places it's not actually just a navigate right you have some information to pass to the next source so for this you need to pass a bundle of information so here we're passing a string and an int and we're using our nice helpful bundle of from Android KTX and it works it's fine this is really useful for a lot of things but at the same point it's not very safe if you do a Mis type here like what are you gonna do so we really want to make this a lot easier Sergei is gonna talk about what we did here yeah we built something called save ours Gradle plugin and it will help you a little bit but first of all let's see what they're trying to resolve and let's go back to our sample our fragment we helped fragment where we try to navigate actually it requires us to pass a screen name argument and optionally you can pass category which has integer type and let's go back to the calling side well in our slice we made everything correctly we passed screen and it has proper type we passed category test proper type as well but actually junk refactorings or some other medications you can forget the best screen name it's a result in run time exception and don't get me wrong it's super easy to fix but it feels annoying it feels like what am i JavaScript developer why this is this compile time check like I don't know so yeah you decided okay now we have this navigation graph let's put all about navigation there including clear arguments that of your destinations so let's see how it looks like an XML and it's super simple we just specify your argument name it's type additionally can specify default value it means that your argument is optional and this allows us to build tooling that once you have an action that leads to this fragment or our action or activity we can check and make you the best proper arguments so look let's take a look how it looks likely for our Gradle plugin now syndicate we use this special object which instead of passing ID and a bundle of course internally the subject incorporates the same ID and same arguments but to get that object we use home fragment Direction class it's generated for you you know it's just a fragment name plus suffix directions and this has static methods for every action defined for this destination and those static methods make you pass required arguments so in our case with it makes us the best home argument there and yeah optionally can but later set up your other additional arguments and everything is type safe and receiving side after that is super simple we have this bodger we generate for you its arcs class so for our case it's called fragment arcs and you just have all your arguments that you defined in a type safe manner and from this relatively small life improvement we go to the bigger one which is deep links yes so deep links are traditionally something that Android has supported for the longest time so you can add intent filters to activities and you know take over a web URL as well as like deep linking is super useful for notifications and things like that to link back into your app but this gets a lot more complicated as you get a more complicated app like how you structure these things and how you say like alright I need to build a notification what is all of the code needed to actually get into the correct place in my app and past the right kind of information here so for navigation who really made deep linking kind of a first-class citizen in our structure so there's really two kinds of deep links the explicit kind so these are things like for notifications app shortcuts app widgets and the new actions and slices things that are things that you create and are usually pending intent based these are all things that you're passing to another app or to the system to say I want to go to this specific place in my app the implicit side of things are more around the web URLs and custom scheme URLs so these would be other apps triggering your app to launch and we handle both of these for navigation for explicit deep links we have a specific class called Navdeep link builder and its sole goal in life is to deep link to a specific destination in your navigation graph by its ID but it's going to do all of that work like it's easy to say but it's a little bit harder to actually make sure that all works in the system but if we create a nav deep link builder you create a context you give it your graph your destination any arguments you have and then you can just call create pending intent and we're doing all the work here to create the right synthetic back stack both within your graph and if you're using multiple activities your parent activities as well and we're gonna pass that along and create the correct intent that gives you to the right place when you trigger this intent and then you just pass it through to your notification you don't actually need to do more than this to get all the correct behavior for implicit deep links these are again kind of links to web URLs right so in this case instead of it being something that you create programmatically it's information you include in your navigation graph so here we're adding a deep link element just like we added the app arguments and actions to our graph these are just a deep link and of course do all of this in the visual editor as part of the properties for a destination and it's really just as simple as an app : URI and you pass in a URI now this is a static URI which is boring and dumb and you know there's only so many apps that have just one URI they do so we of course support some wildcards so if you want to do a dot star for a wild card totally supported if you want to fill in the arguments for your destination you can actually use curly braces and will parse the URL for you and extract those values and give them to you for your arts so again kind of in that same type safe args now you can get those directly from your URL and not have to reparse things like you already know what this is supposed to be similarly you can kind of combine the two if you want to make more complicated patterns totally can we also have support for auto verify if you're using app links to skip that disambiguation screen we wanted to make sure that you could do the same kind of thing if you're using navigation as well and you'll note here that we left off the HTTP HTTPS so what we're doing here is actually doing both we're saying HTTP HTTPS now I assume your servers are all HTTPS but you can't really control the URLs that other apps are including to your app maybe they accidentally took baths off of your URL we still want to support both of those so we use this just as a convenience method instead of having two lines step one and of course it also works with custom schemes so if you have your own scheme that you set up specifically for your app you can also attach those two deep links now the best part is we've kind of worked across the tool space so besides just the navigation editor we've also worked with the manifest merger team so you can add just a single nav graph element to an activity in your manifest pointing to your graph and all of the deep links in that graph will then get expanded out to be the correct intent filter we will build all those for you and if you go to the manifest merger view in Android studio you actually see the exact line from your navigation file that generated not intent filter so this means that we now have a single source of truth in your navigation graph that you know this is not gonna get out of sync with what you've expect it's not gonna get out of sync as you change argument names in your XML file all of this is kind of one central place to do things and we think this is a lot easier for basically all of the implicit deep link kind of cases of course we do do things where this is all action view URLs as they would be for web URLs so you can see it's added directly to the line and if you have multiple of them it'll actually tell you exactly what line if you have multiple graphs associated with different activities those will all work just fine so one of the other subjects that is really important to all of architecture components is testing and testing of navigation is very hard and this is something that we're going to continue to look at over the alpha period and we really want all of your feedback as well but I wanted to kind of discuss what we think testing in a navigation world should look like so a lot of it is if all of the links between your destinations are through navigation then it's a lot easier to test a destination in isolation right you can test each destination by itself and then test just the outgoing edges or just the incoming arguments and not have to deal with oh did it actually do the right fragment transaction because we can test that just at the navigation controller level so this is something that we are going to spend a lot more time on and in the fragment talk yesterday we actually talked about really trying to make fragments themselves much more testable in isolation so it's kind of a package deal where we are trying to build testing into navigationcontroller but then also trying to build testable destinations as well so you might be interested to do something right now so figure out an espresso test and you want to test oh when I get to something does it go to the right place well we actually have a add-on navigator navigated listener so you can actually hook it up to the nav controllers navigator and get a callback of oh did you go to the right place when I click this button so this is one method that we found pretty successful in kind of testing things completely black box outside of things obviously if you want to inject a nav controller or use any other method those are also perfectly valid ways of kind of setting things up so what can you play with today and what can you play with you had two days must have looked at it right so it is an alpha right now one point zero zero alpha zero one we're giving ourselves a long runway of bug fixes and improvements here and it really comes down to two main artifact the navigation fragment which includes the navigation runtime dependency as a transitive dependency and also the nav host fragment and the fragment navigator that you need to use fragment destinations as well as the navigation UI dependency which has those static methods and for every one of the dependencies for navigation we have a dash KTX version of them if you using Colin so we really tried to make Colin a first-class citizen in the navigation world especially for some of the things like if you are doing programmatic graph construction like say you're reading your whole navigation graph from a server we have a Colin DSL as part of our column extensions to make that a lot easier if you're using Colin so there is more to do and I'll have Lukas talk about kind of where we're going yeah OneNote you are gonna need Android studio 3.

2 preview canary 14 to use this and please do download it and try it out there's a lot of great stuff there so this is obviously going to become a really core part of not just architecture components but jetpack overall so with jetpack we're gonna take the same approach we took with architecture components I can sort of a blank sheet of paper what do we want the Android developer experience to be and apply that much more broadly and navigation will be eventually part of the default experience so when we get to stable then you know creating a new project in Android studio is going to by default start you up in the navigator and should be a pretty great world and jetpack is available for you to try right now it's right now it's sort of got a much nicer introduction to what all the key pieces of Android development are so it's a much easier on ramp and we're looking forward to expanding this story over ton there are more talks for you to go to a lot more detail on Android KTX and paging paging in particular is a really cool deep library that does a lot for you by tying together different pieces of architectural components in jetpack so I encourage you if you ever have a ListView with more stuff than you can hold and memory at any given time you really want to go to this paging Talk and we want your feedback we want your feedback on this session first of all but more importantly we want your feedback on the navigation component the reason that we're launching to alpha is not because we think that this is sort of you know really untested and untried we've actually done a lot of pre-release testing of this but we were launching it to alpha because we want to get a lot of feedback from the community on it before we lock down the API and switch over to beta so this is a great time for you to try it out tell us what works for you tell us what doesn't and you know either communicate with us directly or on our public issue tracker we would love to hear from you your feedback has been really critical in every point of this journey and making sure that we're attacking the right problems with the right solutions so please do try it out and tell us what you think and thank you [Applause] [Music] you [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.


*