Use safeargs | mad skills when navigating in the app

Time:2021-11-25

This is a new series of articles, which we call “modern Android development skills”, or “mad skills”. This series of articles is dedicated to helping developers create a better modern Android development experience. Please pay attention.

Today, I’ll release the third article in this series: using safeargs when navigating in applications. If you want to review the content published in the past, please refer to the following link:

This article mainly introduces safeargs, which is a navigation component and can provide more convenient data transfer between different application destinations (interfaces).

brief introduction

When you navigate to different destinations in your application, you may need to transfer data. In order to avoid using global object references, better code encapsulation structure can be realized through data transmission, so that different fragments or activities only need to share the data they need.

The navigation component can beBundlesThis mechanism can also be used to transfer data across activities in Android.

Here, we can also use the same method to create a bundle for the data to be transmitted, and then extract the data on the receiving side.

However, the navigation component has a better method:SafeArgs

Safeargs is a gradle plug-in that helps youNavigation mapEnter the data information to be transferred in. It then generates code to help you solve the lengthy process of creating a bundle, and extracts data on the receiving side.

You can also use bundle directly, but we recommend using safeargs. It not only makes the code more concise, but also adds type security to the data, making the code more robust.

In order to show you the effect of safeargs, I will continue to use it beforeDialog DestinationsDemonstrated donut tracker application. If you want to synchronize with the article, please downloadApplication source code, and open it in Android studio.

It’s time to make doughnuts

Here comes our donut tracking application:

Use safeargs | mad skills when navigating in the app

Donut track: that’s the app. It’s coming again

Donut tracker will display a list of doughnuts. Each list item contains name, description and rating information. Some of these contents are added by me, and some are added by clickingSuspension operation button (FAB)Fill in the pop-up dialog box.

Use safeargs | mad skills when navigating in the app

Click the suspension operation button to pop up a dialog box to fill in the new doughnut information

It’s not enough to just add new doughnut information. I also hope to modify the information of existing doughnuts. Maybe I get a picture of a doughnut, or I want to improve my previous score.

The more natural implementation method is to click the list item, and then open the dialog box when adding doughnuts before. Then I can modify the doughnut information here. But how does the app know which doughnut is displayed in the dialog box? The code needs to pass the information of the clicked list item. Here, it needs to transfer the ID of the corresponding table item from the fragment where the list is located to the fragment where the dialog box is located, and then the dialog box can find the information of the corresponding doughnut from the database according to the ID and fill it in the form.

To pass an ID, here we useSafeArgsTo achieve.

Using safeargs

Here I need to explain that I have completed all the code, which can be found in GitHubExample Complete code found in. So next, I will explain each step to you and let you see the effect of the sample code, rather than simply taking you to complete the code.

First, I need to add some dependent libraries.

Unlike other modules of the navigation component, safeargs is not an API in itself, but a gradle plug-in that can generate code. Therefore, it needs to be set as a gradle dependency and run correctly at build time to generate the required code.

First, I added the following content to the dependency section of the project level build.gradle file:

def nav_version = "2.3.0"
//Get the latest version number https://developer.android.google.cn/jetpack/androidx/releases/navigation
classpath “androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version”

Version 2.3.0 is used here. If you see this article later, there should be an updated version for you to use. As long as it is consistent with the version of other modules of the navigation component API you use.

Then I added the following to the build. Gradle file of the app module. It makes it possible to generate the required code when calling safeargs.

apply plugin: "androidx.navigation.safeargs.kotlin"

Here, gradle indicates that synchronization is required, so I click “sync now”.

Use safeargs | mad skills when navigating in the app

This is a hint that you should not ignore

Next, create and pass the required data in the navigation diagram.

Use safeargs | mad skills when navigating in the app

The target interface that needs data is the dialog boxdonutEntryDialogFragment, it needs to know the information of the object to be displayed. Click the target interface to display relevant attributes on the right.

Use safeargs | mad skills when navigating in the app

Click the target interface to display the attribute list of the interface, where you can enter the data to be transferred

stayArgumentsClick + in the pane to add data, and the dialog box shown below will pop up. Here, I want to pass the doughnut information to be displayed, so the data type is set to long, which is consistent with the data type of ID in the database.

Use safeargs | mad skills when navigating in the app

This dialog box will be displayed when adding data, where you can enter data type, default value and other required information

It should be noted that when I define the data type as long,NullableThe position of the will turn gray. This is because in the Java programming language, the basic data type(IntegerBooleanFloatLong)Is based on the original data type(intboolfloatlong)The original data type cannot be empty, so we need to ensure that the data is not empty when using the basic data type.

Also note that the application now uses the dialog box to add new elements (I wrote in the previous article)Using the navigation component: dialog destination | mad skillsAs described in), you can also use this dialog box to edit existing elements. Therefore, the element ID is not necessarily passed. When the user creates a new element, the code should be able to judge that there is no element information to display. So I’m in the dialogDefault value– 1 was entered at the location of because – 1 is not a valid index value. When the code navigates to the interface and there is no data transfer, – 1 will be passed as the default value. The code at the receiving end needs to use this value to judge that the user needs to create a new doughnut.

Here, we execute the build operation, and gradle will generate the corresponding code for the input data. This is important because otherwise, Android studio cannot know the location of the function you want to call in the automatically generated code.

You can find the execution results of the code generated in the above process under the “Java (generated)” branch of the project structure tree. In the subdirectory, you can see that new files are generated, which are responsible for passing and obtaining data.

stayDonutListDirectionsIn, you can findcompanionObject, which is an API for navigating to dialog boxes.

companion object {
    fun actionDonutListToDonutEntryDialogFragment(
        itemId: Long = -1L): NavDirections =
        ActionDonutListToDonutEntryDialogFragment(itemId)
}

herenavigate()Did not use the originalActionInstead, it usesNavDirectionsObject. It encapsulates both the action (we can navigate to the dialog box through the action) and the variables created earlier.

It should be noted that the aboveactionDonutListToDonutEntryDialogFragment()The function needs a parameter of type long. We created the related variable and assigned it – 1. So if we call the function without parameters, the method will return aNavDirectionsObject and its itemid is – 1.

In another generated fileDonutEntryDialogFragmentArgsIn, you can seefromBundle()The function contains code to get data from the target dialog box:

fun fromBundle(bundle: Bundle): DonutEntryDialogFragmentArgs {
    // ...
    return DonutEntryDialogFragmentArgs(__itemId)
}

Now I can use the generated code to successfully transfer and obtain data. First, I’m hereDonutEntryDialogFragmentClass to getitemIdData and determine whether the user intends to add a new doughnut or edit an existing doughnut:

val args: DonutEntryDialogFragmentArgs by navArgs()
val editingState =
    if (args.itemId > 0) EditingState.EXISTING_DONUT
    else EditingState.NEW_DONUT

The first line of code uses an attribute delegate provided by the navigation component library, which can simplify the process of obtaining data from the bundle. Through it, you canargsDirectly find the name corresponding to the data in the variable.

If the user is editing an existing doughnut information, the code here will get the information of the element and fill the UI with the obtained information:

if (editingState == EditingState.EXISTING_DONUT) {
    donutEntryViewModel.get(args.itemId).observe(
        viewLifecycleOwner,
        Observer { donutItem ->
            binding.name.setText(donutItem.name)
            binding.description.setText(donutItem.description)
            binding.ratingBar.rating = donutItem.rating.toFloat()
            donut = donutItem
        }
    )
}

It should be noted that the code here requests information from the database, and we hope that the whole request process can be carried out outside the UI thread. So the code will listenViewModelProvidedLiveDataObject, and processes the request asynchronously, populating the view when the data is returned.

When the user clicks on theDoneButton, you need to store the information entered by the user. The following code will update the corresponding data in the database and close the dialog box:

binding.doneButton.setOnClickListener {
    donutEntryViewModel.addData(
        donut?.id ?: 0,
        binding.name.text.toString(),
        binding.description.text.toString(),
        binding.ratingBar.rating.toInt()
    )
    dismiss()
}

The above code mainly focuses on processing data in the target interface. Now let’s take a look at how to send data to the target interface.

stayDonutListThere are two ways to turn to the dialog box. One is when the user clicksSuspension operation button(FAB):

binding.fab.setOnClickListener { fabView ->
    fabView.findNavController().navigate(DonutListDirections
        .actionDonutListToDonutEntryDialogFragment())
}

It should be noted that this code is creatingNavDirectionsObject, so the variable will be assigned to – 1 by default (to indicate that this is a new doughnut), which is also the effect we want to achieve by clicking the suspension operation button.

Another way is to open a dialog box when the user clicks on an existing element in the list. It can be implemented by the following lambda expression, which will beDonutListAdapterPassed in during the build of (i.eonEditParameter), and thenonClickCalled when triggered:

donut ->
    findNavController().navigate(DonutListDirections
        .actionDonutListToDonutEntryDialogFragment(donut.id))

The code here is similar to the code for the user to click the suspension operation button, except that the ID of the table item is passed in to tell the dialog box that it wants to edit an existing element. And as we saw in the previous code, it will fill the dialog box with the information of existing elements, and the changes made to the table item will update the corresponding item in the database accordingly.

summary

That’s all about safeargs. It is very simple to use (much simpler than bundle), because the dependency library will help you generate code to simplify data transmission and ensure data type security. In this way, you can make better use of data encapsulation to transfer only the required data between destinations without exposing the data on a larger scale.

Please continue to pay attention to our subsequent content on navigation components. Next, we will introduce how to use deep link.

More information

For more details on the navigation components, please seeGetting started with navigation components

See the complete code of donuttracker applicationGitHub example

For more modern Android development skills (mad skills) series, please seeAndroid developers channel

Recommended Today

Seven solutions for distributed transactions

1、 What is distributed transaction Distributed transaction means that transaction participants, transaction supporting servers, resource servers and transaction managers are located on different nodes of different distributed systems. A large operation is completed by more than n small operations. These small operations are distributed on different services. For these operations, either all of them are […]