Using Android Jetpack Navigation with Navigation Drawers

To me, the best thing to come out of Google IO (for Android devs) is definitely Jetpack

A bunch of well thought out components and patterns to help Android dev can’t be a bad thing, AMIRITE? Anywho, I find the best way to learn something new is to try it by creating a working app. Luck would have it, I’m just about to create a green-fields app for internal use at my day job. What better way to pickup the new and wonderful, than by trying it in an actual app, rather than a demo or play app.

I decided to try the Jetpack Navigation Component first up, as the app I’m making fits the requirements, which are described here – however TLDR; your app must have a single host activity, and additional views are handled by fragments. There are a bunch of other things to think about too, but that’s it in a simplified nutshell.

I followed a video on the excellent RayWenderlich.com site to get going, but if you’re not a subscriber I’ll do a quick run through here.

Basically you need to first download the 3.2 (at least) build of Android studio. It’s in beta at time of writing this.

One you have that up and running (it can live happily with other versions of AS), select File | New Project…

Fill in the fields until you get to this screen:

AS_JP_1

Select the “Activity & Fragment + Viewmodel” option.

Keep the defaults, EXCEPT the last edit box, titled Fragment Package path. There is an error in the beta, so this needs to be removed, so the edit box is blank. Once you’ve done that hit finish and the wizard will complete.

A bit of tidyup work first. Go into main_fragment.xml and remove the extra . under tools:context. Not sure why it’s there, but it creates issues if you don’t remove it. You may have to go into all the Kotlin files and remove the trailing . under package name. Hopefully they’ll fix that bug soon!

Now go into the app module build.gradle file and change the second line that is crossed out. Change jre7 near the end of the line to jdk7.

Now go into MainActivity.kt and remove the lines that are underlined in red (you may have to open the import box first).

Now we’re ready to include the dependencies for Jetpack Navigation. First we need to go into the project build.gradle file and add some items. Change the file so it looks like below:

buildscript {
    ext.kotlin_version = '1.2.41'
    ext.nav_version = '1.0.0-alpha01'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.0-alpha16'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Now go back into you app module build.gradle file and add the navigation dependencies:

implementation "android.arch.navigation:navigation-fragment:$nav_version"
implementation "android.arch.navigation:navigation-ui:$nav_version"

Also go into the gradle-wrapper.properties file and change the Gradle version to 4.7 from 4.6. Now sync the build files to continue.

The first thing we need to do is create a Navigation Graph. This is where the interactions between fragments will happen. To create a Nav Graph, right click on the res folder in the Project View, and select New | Android Resource File. Call it nav_graph and change the type to be Navigation. Don’t worry about this file for now.

Go into main_activity.xml and change the file so it resembles:

<?xml version="1.0" encoding="utf-8"?>

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">



    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary" />
        <fragment
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/nav_host"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:defaultNavHost="true"
            app:navGraph="@navigation/nav_graph" />

    </FrameLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="@android:color/white"
        android:fitsSystemWindows="false"
        android:isScrollContainer="true"
        android:saveEnabled="true"
        android:scrollY="1dp"
        android:scrollbars="none"
        app:elevation="2dp"
        app:insetForeground="@color/colorPrimaryDark"
        app:itemIconTint="@color/colorPrimaryDark"
        app:menu="@menu/drawer_view" />

</android.support.v4.widget.DrawerLayout>

Now create the drawer menu. Right click res again and select New Android Resource file again. This time call it drawer_view and change it’s type to be menu.

Add the following to the menu file

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/main_fragment"
        android:icon="@android:drawable/ic_menu_view"
        android:title="@string/home" />
    <item
        android:id="@+id/settings_fragment"
        android:icon="@android:drawable/ic_menu_preferences"
        android:title="@string/settings" />
</menu>

The key thing to look at above is the ids of the menu items. THESE NEED TO BE THE SAME AS THE FRAGMENT IDS. Read that line again, it’s the secret sauce of getting DrawerLayouts to work with the new Navigation Component. (I’ll leave extracting the magic strings to string resources as a task for the player)

If nav_graph.xml is open, close it and reopen, and it should have MainActivity as the host, because we added NavHost items to the main_activity.xml file above. In there designer of the Nav Graph click add new destination and add the main fragment. It should automatically create it as the start destination, but if not, click the Set Start Destination button under attributes.

While you’re looking at the attributes, make sure the ID field is main_fragment (the id in the main_fragment.xml file). This needs to be set so the NavigationController class can link the menu with the fragment to set it to.

Now in the Nav Graph, click New Destination and call the fragment SettingsFragment. The wizard will do it’s thing and you should see settings fragment in the designer window.

Click on the settingsFragment window, and look at the attributes. You’ll notice that the ID is settingsFragment. We need that to be settings_fragment, as we’ve linked it to that in the drawer menu, created earlier. So change the ID to settings_fragment. One last thing to do. Select mainFragment. You’ll notice a little circle on the right hand side.

AS_JP_2

Click and drag from the circle to the Settings fragment. This is creating a navigation action, linking the two fragments for navigation, well actions. If you look in the Project View you will also see that there are two new files.

The layout xml and the backing Kotlin class. We need to go into the layout file and add the appropriate id to the file. Change the settings_fragment layout file so it matches:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/settings_fragment"
    tools:context=".SettingsFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_blank_fragment"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Phew. That’s a lot of work to get things working. We’re not done yet…

Open MainActivity.kt

The first thing we need to do is remove the if block in the onCreate function. We don’t need that any more as the Navigation Graph is automatically handling things for us now.

Under imports add the Kotlin extensions, so we don’t have to worry about findViewById any more:

import kotlinx.android.synthetic.main.main_activity.*

Now create a “Hamburger” menu. Right click the res folder and select New | Image Asset. Select Clipart, and click the android icon. In the search box type menu and select that. Call the drawable ic_menu.

Now we need to hook up the toolbar, toggle the Navigation drawer to sync, and finally connect the NavigationController to the Navigation Drawer view, so the Navigation Controller can intercept the onSelected calls and forward to the appropriate fragment.

Change MainActivity.kt to look like below, build and run and we’re done!

package softwarex.co.nz.navigationapp

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.app.ActionBar
import android.support.v7.app.ActionBarDrawerToggle
import androidx.navigation.Navigation
import androidx.navigation.ui.NavigationUI

import kotlinx.android.synthetic.main.main_activity.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)

        val navController = Navigation.findNavController(this, R.id.nav_host)

        setSupportActionBar(toolbar)
        val actionbar: ActionBar? = supportActionBar
        actionbar?.apply {
            setDisplayHomeAsUpEnabled(true)
            setHomeAsUpIndicator(R.drawable.ic_menu)
        }

        setupDrawerToggle()

        NavigationUI.setupWithNavController(nav_view, navController)
    }

    private fun setupDrawerToggle() {
        val mDrawerToggle = object : ActionBarDrawerToggle(this, drawer_layout, toolbar, R.string.open, R.string.close) {}
        drawer_layout.addDrawerListener(mDrawerToggle)
        mDrawerToggle.syncState()

    }

}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.