Selfad logo

Android Tabs, Navigation drawer and fragments

First, a word of warning about the examples on the Internet. There appears to be a lot of confusion, outdated examples (this is written in June, 2017) and version-mismatches and all kinds of attempts to provide low-quality Android TabLayouts along with the Navigation bar. What happens is that you may get the navigation drawer but no working Tabs and vice versa. A lot of energy may go wasted quickly if a wrong path is taken. There isn't many proper ways but I guarantee, there's an endless amount of chaotic ways to waste your time! The purpose here is not to disclose the whole SelfAd app, but rather, this could work as a guide to intruct how this all could be done. Here we're going to cover an approach that works - but it's not guaranteed whether it's the most efficient way. What is meant by TabLayouts and the Navigation Drawer is shown below with a several screenshots:


Screenshot of navigation bar
Navigation Drawer
Screenshot of login button
Login Screen
Screenshot of visit profile
Visit Profile?
Screenshot of about
About Fragment

Proper design of Navigation drawer and Tab fragments

The navigation drawer opens nicely by touching the hamburger icon on the top left of the "Login Screen" screenshot (three horizontal lines on top of each other) or by swiping from the far left to right. We have only two tabs: ADVERTISE and SCAN. The navigation drawer on the demo app has currently four entries. It opens up new fragments on top of the existing ones. If they were replaced, then extra work would need to be done when the tab fragments were on focus again. More of this later.

We want to design the system so that no re-writing of code is needed. Here we have a robust app called SelfAd as a reference. If one example is taken from the web and another elsewhere, the risk that they won't work together is quite impressive. Again, this example introduces a way to integrate swiping, tabs and the navigation drawer.

This is what we call from the MainActivity.java file to build the TabFragment which handles the tabs:

 
        tabFragment = new TabFragment(); 
        getSupportFragmentManager() 
                .beginTransaction() 
                .replace(R.id.frame, tabFragment) 
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) 
                .commit();

This is the definition of the class TabFragment.java:

package fi.offcode.selfad; 
 
import android.os.Bundle; 
import android.support.annotation.Nullable; 
import android.support.design.widget.TabLayout; 
import android.support.v4.app.Fragment; 
import android.support.v4.view.ViewPager; 
import android.view.LayoutInflater; 
import android.view.View; 
import android.view.ViewGroup; 
 
public class TabFragment extends Fragment { 
 
    public TabLayout tabLayout; 
    public ViewPager viewPager; 
    public TabsPagerAdapter tabsPagerAdapter; 
 
    @Nullable 
    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
        /** 
         * Inflate tab_layout and setup Views. 
         */ 
        View x = inflater.inflate(R.layout.tab_layout, container, false); 
        tabLayout = (TabLayout) x.findViewById(R.id.tabs); 
        viewPager = (ViewPager) x.findViewById(R.id.viewpager); 
 
        final int[] ICONS = new int[]{ 
                R.drawable.ic_tx, 
                R.drawable.ic_scan 
        }; 
 
        tabsPagerAdapter = new TabsPagerAdapter(getChildFragmentManager()); 
        viewPager.setAdapter(tabsPagerAdapter); 
 
        tabLayout.setupWithViewPager(viewPager); 
 
        tabLayout.getTabAt(0).setText(R.string.tab_advertise); 
        tabLayout.getTabAt(0).setIcon(ICONS[0]); 
        tabLayout.getTabAt(1).setText(R.string.tab_scan); 
        tabLayout.getTabAt(1).setIcon(ICONS[1]); 
 
        return x; 
    } 
}

And this is the tab_layout.xml file which is being referenced above (R.layout.tab_layout):

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:orientation="vertical"> 
 
    <android.support.design.widget.TabLayout 
        android:id="@+id/tabs" 
        app:tabGravity="fill" 
        android:layout_width="match_parent" 
        android:layout_height="wrap_content" 
        android:background="#3b5998" 
        android:minHeight="?attr/actionBarSize" 
        app:tabIndicatorColor="#c8130d" 
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> 
 
    <android.support.v4.view.ViewPager 
        android:id="@+id/viewpager" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent"> 
    </android.support.v4.view.ViewPager> 
 
</LinearLayout>

This is the activity_main.xml file:

<android.support.v4.widget.DrawerLayout 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:id="@+id/drawer_layout" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:fitsSystemWindows="true" 
    tools:context=".MainActivity"> 
 
    <include 
        layout="@layout/app_bar_main" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" /> 
 
    <android.support.design.widget.NavigationView 
        android:id="@+id/nvView" 
        android:layout_width="wrap_content" 
        android:layout_height="match_parent" 
        android:layout_gravity="start" 
        android:background="@android:color/white" 
        app:headerLayout="@layout/nav_header" 
        app:itemTextColor="#3b5998" 
        app:menu="@menu/drawer_view" /> 
</android.support.v4.widget.DrawerLayout>

This is the app_bar_main.xml file:

<?xml version="1.0" encoding="utf-8"?> 
<android.support.design.widget.CoordinatorLayout 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:fitsSystemWindows="true" 
    tools:context=".MainActivity"> 
 
    <android.support.design.widget.AppBarLayout 
        android:id="@+id/appbar" 
        android:layout_width="match_parent" 
        android:layout_height="wrap_content" 
        android:theme="@style/AppTheme.AppBarOverlay" 
        app:elevation="0dp"> 
 
        <android.support.v7.widget.Toolbar 
            android:id="@+id/toolbar" 
            android:layout_width="match_parent" 
            android:layout_height="?attr/actionBarSize" 
            android:background="#3b5998" 
            app:popupTheme="@style/ThemeOverlay.AppCompat.Dark" /> 
 
    </android.support.design.widget.AppBarLayout> 
 
    <FrameLayout 
        android:id="@+id/frame" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" 
        app:layout_behavior="@string/appbar_scrolling_view_behavior" /> 
 
</android.support.design.widget.CoordinatorLayout>

Adding navigation drawer menu fragments

When the hamburger icon is touched or a swipe is performed from the far left, the navigation drawer is opened. It contains menu items which, if touched, will launch new fragments (Settings, About etc.). These fragments differ from the ones contained in the tabs. These navigation view menu fragments are actually added on top, not replaced. If replacing was used, then the view would need to be literally replaced. When closing the Settings fragment, the tabs would need to be recreated again. This is not what we want as it would consume extra CPU cycles as we would need to recreate the fragment and handle the save/restore of all the data so that the view would look the same prior to entering Settings, About etc. Instead, we add the fragment:

fragmentTransaction.add(R.id.frame, fragment, tagString);

Where the fragment is something we just created, be it the About, Settings or whatever view. Before calling the above, we set the reference view to whatever it was with fragmentTransaction.addToBackStack(null); Now, if we enter one of the menu fragments and press back, it will kill that particular fragment. It simply disappears from the view and the view which was before entering the fragment will be shown exactly as it was. Nice?


Navigation Drawer

Screenshot of scan navbar
Drawer Open

There are numerous examples on the Internet how to create the navigation drawer. In the above xml files the you see the generic structure:


<DrawerLayout>
   <CoordinatorLayout />
   <NavigationView />
</DrawerLayout>

It follows the well-known way of getting the Navigation bar to work. On the right there's the example navigation drawer open. The structuring of your xml files is very important. I think you need the CoordinatorLayout, you need the NavigationView and the DrawerLayout. Once the structure is okay, it should not be challenging to build the drawer.


Tab Fragments

The tab fragments (the app has 2 of them, ADVERTISE and SCAN) embed a lot of functionalities. The most important thing is to unregister all listeners upon onDestroy()! Otherwise, when the fragment is destroyed by the system, the listeners may be still there. What happens is that the fragment is kept to some extent, but it has no activity attached. Now, when the listener callbacks are called, you get a number of exceptions. To me it looks like the fragments are not completely killed if they have listeneres registered. Instead, you might have a bunch of instances running in parallel, wasting system resources and causing a lot of trouble.

This was a brief story on how SelfAd app is structured. Get yours and check it out how it works from the Google Play store. You need to have at least Android 5.1 in order to get the app up and running. This is very unfortunate because the Bluetooth Low Energy (BLE) doesn't provide advertising features on adequate level prior to SDK version 22. If you wish to send feedback, please do it via the contact information provided in the Google play store (email address is there).

Sincerely, Eero Nurkkala June 20, 2017.


Back to the introduction of SelfAd