📜 ⬆️ ⬇️

How we implemented the navigation from Jetpack into the combat application. Report Yandex. Food

Mobile applications are increasingly using deep links. These are links that allow you not only to go into the application from the outside, but to get to a specific screen. Android developer from Yandex. Edy Vladislav Kozhushko explained why we implemented navigation from Jetpack to implement deep links, what problems we encountered, how they were solved and what happened in the end.


- Hello! My name is Vlad. I’m interested in Android development since 2013, in Yandex. I’m working since last summer. I will tell you about our way of introducing the Navigation Components library into the combat application.

It all started with the fact that we had a technical task of refactoring navigation. Then product managers came to us and said that we will make diplinks, there will be a lot of them, they will lead to different screens.
')
And then we thought - navigation, presented on Google I / O 2018, is very well suited for the implementation of tasks on diplinks. We decide to see what happens. Our colleagues with iOS in Xcode have a convenient graphical editor in which they can use the mouse to stumble across the entire layout of the screens, as well as set the transitions between the screens. Now we also have this opportunity, we can use the mouse to set transitions, diplinks to the screens and link them with fragments. In addition, we can set arguments on the screen.



That is, the arguments must either be stumbled into the UI editor, or entered into XML. In addition to the transition between screens, an action tag appeared in which we indicate its id and the screen to which we need to go.



We also specified the diplink that we want to open the screen. In this case, there is an itemId parameter, if we pass a parameter of this type, and it will lead to a fragment, then the value of this parameter will be passed to the fragment's arguments, and by the itemId key we will be able to get it and use it. Another library supports uplinks. If we set uplink, for example, navdemo.ru/start/{itemId}, then we will no longer need to take care to register http / https schemes to open such diplinks. The library will do everything for us.

Now let's talk about the arguments. We added two arguments, integer and boolean, and also gave them default values.



After that, when assembling the fragment, we will have the NextFragmentArgs class. It has a Builder, with which you can build and set the arguments we need. Also in the NextFragmentArgs class itself, there are getters for getting our arguments. You can build NextFragmentArgs from the bundle and convert the bundle, which is very convenient.



About the main features of the library. She has a convenient UI-editor in which we can do all the navigation. We have a readable XML markup that inflates the same way as Views. And we have no such pains as iOS developers, when they corrected something in a graphic editor, and a lot of things have changed.

Deeplinks can work both with fragments and with Activity, and for this you do not need to register a large amount of IntentFilter in the manifest. We also support uplinks, and with Android 6 you can enable autoverify for verification. In addition, when assembling projects, code generation takes place with arguments to the desired screen. Navigation supports nested graphs and nested navigation, which allows you to logically decompose all navigation into separate subcomponents.



Now we will talk about our path, which we have passed, introducing the library. It all started with alpha version 3. We implemented everything, we replaced all the navigation with Navigation components, everything is super, everything works, the diplinks open, but problems appear.



The first problem is IllegalArgumentException. It appeared in two cases: inconsistency of the graph, because there was a desynchronization of the representation of the graph and fragments in the stack, because of this, this exception occurred. The second problem is double clicks. When we made the first click, we are navigating. The transition to the next screen occurs, and the state of the graph changes. When we make a second click, the graph is already in a new state, and it is trying to make the old transition, which no longer exists, so we get such an exception. In this version, there were no diplinks opened, the scheme of which contains a dot, for example, project.company. This was decided in the next versions of the library, and in the stable version everything works well.

Also not supported shared elements. You've probably seen how Google Play works: there is a list of applications, you click on the application, you have a screen, and a beautiful animation of moving the icon takes place. We also have this in the application on the list of restaurants, but we needed the support of shared elements. Also, SafeArgs did not work for us, so we lived without them.



It was easy to fix the diplink. It was necessary to replace the scheme, which was in the library, with its own scheme, which supports the point. With reflection, we knock on the class, change the value of the regex, and everything works.



To correct a double click, we used the following method. We have extension functions to set navigation clicks. After clicking on a button or another item, we update ClickListener and navigate to avoid double transition. Or, if you have RxJava in your project, I recommend using the RxBindingsgs library from Jake Worton, and using it you can handle events from View in a reactive style, using the operators available to us.



Let's talk about shared elements. Since they appeared a little later, we decided to finish the navigator, add navigation to it. We are programmers, why not?



The revision was as follows: we inherit our navigator from the navigator that is in the library. Here, not all the code is presented, but this is the main part that we have finalized. I want to note, before doing the navigation, the state of the FragmentManager is checked. If it was saved, then we lose our teams. In my opinion, this is a flaw.

Also, when we start a fragment transaction, we create a transaction and set all our Views that need to be searched. But the question arises, what kind of class is this TransitionDestination? This is our custom class in which it is possible to set Views. We inherit it from Destination and extend the functionality. Set Views and our Destination is ready to share.



The next part - we need to make navigation. We are looking for when you click on the button on the id destination, pull out the top of the graph to which you want to go. After that we convert it to our TransitionDestination, in which we have Views. Further we set all our Views for animation of transitions, and we do navigation. Everything works, everything is super. But then alpha06 appeared.

This does not mean that we did jumps between versions. We tried to update the libraries as necessary, but perhaps these are the most basic changes we have encountered.

There are problems with alpha06. Since this was the alpha version of the library, there were constant changes related to renaming methods, callbacks, interfaces, not to mention the fact that parameters were added and removed in methods. Since we wrote our own navigator, we had to synchronize the library navigator code with ours in order to also fill in bugfixes and new features.

Also in the library itself, as the transition from early alpha versions to stable versions, behavior changed, some possibilities were removed. There used to be a launchDocument flag, but it was never used, then it was removed.



For example, there was a change in which the developers said that the navigateUp () method that works with DrawerLayout is outdated, use another one in which the parameters were simply swapped.





Our next big migration was on alpha11. Here fixed the main problems with navigation when working with the graph. We finally removed our doped controller and used everything that was out of the box. Safe args still didn't work for us, and we were upset.

Then the beta01 version came out, and in this version nothing actually changed with the navigation behavior, but the following problem appeared: if a number of screens are open in the application, then we clean up the stack before opening our diplink. This behavior did not suit us. Safe args still did not work.



We wrote an issue in Google, to which we were told that all the rules were originally conceived, and this was done in the code itself because before going to diplink, we returned to the root fragment using the id of the graph that lies at the root. Also, in the setPopUpTo () method, the flag is passed, saying that by returning to this screen, we also need to drop it from the stack.



We decided to return our doped navigator and correct what we consider wrong.



Here it is, the original problem that caused the stack to be cleared. We solved it as follows. We check if startDestination is equal to zero, the initial screen, then we will use it, take as the IT manager the IT leader of the graph. If our startDestination aydishnik is not zero, then we will take this aydishnik from the graph, thanks to which we can not clean the stack and open the diplink on top of the content that we have. Or, alternatively, you can simply remove the pop-up true in the navigation options. In theory, everything should work too.

And finally comes the stable version. We were delighted, we thought that everything was fine, but in a stable version the behavior, by and large, did not change. They just made it finalized. We finally got safe args, so we actively began to add arguments on our screens and use them everywhere in the code. We also found that navigation does not work with DialogFragments. Since we had DialogFragments, we wanted to transfer all of them into a graph, into XML, and describe the transitions between them. But we did not.



Double opening. We also had a problem that haunted us from the very first version, the double opening of the Activity during the cold start of the application.



It happens as follows. There is an excellent handle method deeplink, which we can call from our code, for example, when in activation we get onNewIntent () in order to intercept the diplink. Here, the ACTIVITY_NEW_TASK flag comes to the Intent when the application is launched by diplink, so the following happens: a new Activity is started, and if there is a current Activity, it is killed. Therefore, if we start the application, the white screen starts up first, then it disappears, another screen appears, and they look very beautiful.

As a result, having introduced this library, we received the following advantages.



We have documentation on our navigation, as well as a graphic representation of the transitions between screens, and if a person comes to us in a project, he quickly understands this by looking at the graph, opening his presentation in the Studio.

We have SingleActivity. All screens are made on fragments, all diplinks lead to fragments, and I find it convenient.

It turned out a simple linking of the diplink with the fragments, just add the deeplink tag to the fragment, and the library does everything for us. We also broke our navigation into nested subgraphs, made nested navigation. These are different things. Just a nested graph, in fact, is include within the graph, and nested navigation is when a separate navigator is used to walk on the screens.

We also change the graph dynamically in the code, we can add vertices, we can delete vertices, we can change the start screen - it all works.

We almost forgot how to work with FragmentManager, since all the logic of working with it is encapsulated inside the library, and the library does all the magic for us. The library also works with DrawerLayout, if you have a root fragment specified, the library itself will draw a hamburger on it, and when moving to the next screens, it will draw an arrow and do it animated when returning from the last but one fragment.

We also moved all the arguments, most of them, to SafeArgs, and everything is created when we build the project. In addition, we coped with all the problems that we were pursued, and finalized the library to fit our needs.

SafeArgs can generate code on Kotlin, for this a separate plugin is used.



In addition, the library has drawbacks. The first is that a clean version has been delivered to the stable version. I don’t know why it was done, maybe it’s so convenient for someone, but in the case of our application, we would like to open diplinks above the content that we have.

The fragments themselves are created by the fragment navigator, and are created through reflection. I do not think this is a plus in implementation. The condition for diplinks is not supported. Your application may have secret screens that are accessible only to authorized users. And in order to open diplinks by condition, you need to write crutches for this, since you cannot set a diplink handler in which we would get control and tell what to do, either open the diplink screen or open another screen.

Also, the transition commands are lost, all because the fragment of the navigator checks the state, whether it is saved or not, and if it is saved, then we simply do nothing.

We also had to modify the library with a file, this is not a plus. And another major drawback - we do not have the opportunity to open a chain of screens in front of a link. In the case of our application, we would like to make a diplink on the basket, in front of which we open all the previous screens: restaurants, a particular restaurant, and only after that the basket.

Also, to work with navigation, you must have an instance of View. To get a navigator, you need to refer to View - the navigation library itself will contact the parents of this View - and try to find a navigator in it. If she finds it, then the transition to the desired screen occurs.

But the question arises: is it worth to use the library in battle? I will say yes if:



If we need to get a quick result. The library is implemented very quickly, it can be inserted into the project in about 20 minutes, all transitions are poked with the mouse, everything is convenient. Just as quickly it is written in XML, quickly assembled, works quickly. Everything is super.

If you need to have in the application a lot of screens that work with diplinks, and there are no conditions under which they should open.

No single Activity. Here I mean not only single Activity. We can navigate both on fragments with one Activity, and on a mixture of fragments and an Activity, just in the column it will also be possible to describe an Activity. To do this, use the provider inside the navigator, in which there are two implementations of navigation. One is for Activity, which creates Intents to go to the necessary Activities, the second implementation is a fragment-navigator, which works inside itself with a fragment-manager.

If you do not have complex logic to switch between screens. Each application is unique in its own way, and if there is a complex logic for opening screens, this library is not for you.

You are ready to go into the sources and modify it as we do. Actually it is very interesting.



I will say no, if the application has complicated navigation logic, if you need to open a chain of screens in front of the link, if you need difficult conditions for opening screens through the link. In principle, using the example of authorization, this can be done by simply opening the screen you need, and on top of it - the authorization screen. If the user is not authorized by you, then you drop to the stack to the previous screen, in front of which the secret screen was opened. Such a crutch decision.

Losing teams is critical for you. The same Cicerone can save commands to the buffer, and when the navigator becomes available, it executes them.

If you do not want to finish the code, it does not mean that you, as a developer, do not want to do something. Suppose you have a deadline, you product managers say that you need to cut features, business wants to roll out features. You need a turnkey solution that works out of the box. Navigation Components is not about this case.

Another critical thing is that DialogFragments are not supported. Navigator, which would work with them, you could add, but for some reason did not add. The problem is that, in themselves, they open like normal fragments. The checkbox on isShowing is not set and, accordingly, the DialogFragments life cycle for creating a dialog is not executed. In fact, DialogFragment opens as a normal fragment, the entire screen without creating a window.

I have it all. Dig the source, it is really interesting and fun. Here are useful materials for those who are interested in working with the navigation library. Thanks for attention.

Source: https://habr.com/ru/post/455479/


All Articles