MapFragment in Action Bar Tabs

hermann picture hermann · Dec 28, 2012 · Viewed 7.8k times · Source

I am trying to build an app that will implement Action Bar tabs. One of the tabs should contain a MapFragment.

How can I implement an action bar with tabs, under one of which is a map Fragment?

Can you help me with how to proceed with this?

Here is what I have so far :

main class

package com.nfc.demo;

import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;

public class NFCDemoActivity extends Activity {

  Tab selectedTab = null;

  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ActionBar bar = getActionBar();
    bar.setDisplayShowHomeEnabled(false);
    bar.setDisplayShowTitleEnabled(false);

    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

    bar.setCustomView(R.layout.main);

    ActionBar.Tab mapTab = bar.newTab().setText("Map");
    ActionBar.Tab settingsTab = bar.newTab().setText("Settings");
    ActionBar.Tab aboutTab = bar.newTab().setText("About");

    MapFragment mapFragment = new MapFragment();
    SettingsFragment settingsFragment = new SettingsFragment();
    AboutFragment aboutFragment = new AboutFragment();

    mapTab.setTabListener(new TabListener(mapFragment));
    settingsTab.setTabListener(new TabListener(settingsFragment));
    aboutTab.setTabListener(new TabListener(aboutFragment));

    Tab selectedTab = (Tab) getLastNonConfigurationInstance();

    if (selectedTab == null) {
      bar.addTab(mapTab, false);
      bar.addTab(settingsTab, false);
      bar.addTab(aboutTab, true);
    }

    setContentView(R.layout.main);

  }

  public Object onRetainNonConfigurationInstance() {
    return selectedTab;
  }

  protected boolean isRouteDisplayed() {
      return false;
  }

  protected class TabListener implements ActionBar.TabListener {
    private Fragment fragment;

    public TabListener(Fragment fragment) {
        this.fragment = fragment;
    }

    public void onTabSelected(Tab tab, FragmentTransaction fragmentTransaction) {
        fragmentTransaction.replace(R.id.mainFragment, this.fragment, null);
        selectedTab = tab;
    }

    public void onTabUnselected(Tab tab, FragmentTransaction fragmentTransaction) {
        fragmentTransaction.remove(this.fragment);
    }

    public void onTabReselected(Tab tab, FragmentTransaction fragmentTransaction) {
        //do nothing
    }
  }
}

The Fragment classes are all just returning an inflater with an .xml layout.

XML Layouts :

main.xml ( map should be on this XML file )

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mainFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

</LinearLayout>

settings.xml AND about.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <TextView
        android:id="@+id/textView123"
        android:text="asdfg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

But adding the MapFragment thows a error:

Error inflating class fragment 
error caused by java.lang.IllegalArgumentException: 
Binary XML file line #2: Duplicate id 0x7f040005, tag null, or parent id 0x1020002 with another fragment for com.google.android.gms.maps.MapFragment 12-28 21:14:07.991: E/AndroidRuntime(26189): at android.app.Activity.onCreateView(Activity.java:4722)

I've been trying to figure out how to proceed for a couple of days but I am really confused. Any help/tips would be greatly appreciated.

Also, what about getLastNonConfigurationInstance()? It is deprecated.

Answer

Matt picture Matt · Jan 22, 2013

In the following solution, it is possible to add a GoogleMap to an Action Bar tab/dropdown. The key to doing this lies in correctly setting up your fragment to destroy the MapFragment when switching to another fragment in the Action Bar.

Create an Activity that implements the Action Bar functionality:

  1. Create a project in Eclipse that uses Tabs for the main activity. If you don't do this, proceed to steps 2-5.
  2. Create a class that extends Activity and implements ActionBar.OnNavigationListener.
  3. Create a layout XML file that is a container for your tab fragments when you switch between them.
  4. Implement/override the following method in your Activity class: public boolean onNavigationItemSelected(int position, long id).
  5. In this method, switch between the position object to determine the selected tab and set the fragment to a new instance using the FragmentManager like this: getFragmentManager().beginTransaction().replace(R.id.container, fragment).commit().

Create a fragment that holds the map:

  1. Create a class that extends Fragment to use as your tab's fragment. Read [1] to better understand the MapFragment.
  2. Create a layout XML file that contains a fragment element (as seen in [1]).Use the XML in that page to create a layout XML file and use it in your fragment class.
  3. Inflate that layout XML file in your fragment class by overriding onCreateView.
  4. Your app should now display a map in the tab that uses your fragment class, however, switching to another tab and back to the map tab will result in a duplicate view ID. To overcome this, go on to the next step.
  5. In your fragment class, override the following method to specifically destroy the underlying GoogleMap object so that it can be recreated when the map tab loads your fragment class again:

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        MapFragment f = (MapFragment) getFragmentManager().findFragmentById(R.id.map);
        if (f != null) 
            getFragmentManager().beginTransaction().remove(f).commit();
    }