HowTo: ActionBarSherlock + MapFragment + ListFragment

Today I’ll write about how to have 2 fragments in one Activity, one of them containing a ListView (for example), and the other one containing a MapView, and all of this with a supercool ActionBarSherlock! The perfect combo!

The problem

It’s quite simple: if you want to use fragments with a MapView, you’re not allowed to do it in an easy way.

There’s no MapFragment that can be held by a FragmentActivity or a MapFragmentActivity that can hold both a MapView and some fragments #fail. So, what’s the solution?

The workaround

Fortunately, there’s always people who do cool libraries. One example is “android-support-v4-googlemaps“. It’s essentially a copy of the Android Compatibility Library but the FragmentActivity class extends MapActivity instead of Activity, so we have functionality of a MapActivity with fragments.

The setting up

There are the steps:

  1. First of all you should take a look at my last post Using an ActionBar in your Android app with ActionBarSherlock and follow the steps 1 to 6 in the part named “The code“, just 1 to 6, not 7. If you already have the ActionBarSherlock in your workspace, you can skip this step.
  2. Create a copy of the ActionBarSherlock library project and rename it to something like “Sherlock-ActionBar-GoogleMaps-lib-4.0.0“, we will work on that copy.
  3. Then download the last version of the android-support-v4-googlemaps library here.
  4. Now remove the android-support-v4.jar file from the library project build path, copy the file you downloaded (which name should be something like android-support-v4-r6-googlemaps.jar) and paste it to the libs folder in the library. It’s also needed to add this new jar to the Build path.
  5. After that, change the target SDK version of the ActionBarSherlock to the Google APIs SDK (for example Google APIs 4.0).
  6. Create a new project called “Maps with fragments”, that targets Google APIs SDK 4.0.
  7. Add the “Sherlock-ActionBar-GoogleMaps-lib-4.0.0” as a library project in the project that we just created and add the android-support-v4-r6-googlemaps.jar library to the new project’s build path.

The codes

We’re done with the setup. Now it’s time for the code. Basically what we’ll be doing is have an Activity that extends SherlockFragmentActivity. Then we’ll need to create a static class with a static MapView in it that we will use as an “Exchanger” between the Activity and the MapFragment. Our activity will create a new MapView object and store it into the Exchanger.

We also need to create a class (the MapFragment) that extends SherlockFragment. This Fragment will use the Exchanger’s MapView as content view.

Then we will be able to replace that fragment with another one (for example a ListFragment).

That was a basic explanation of the “Hack”.

Here you have the source for the class MapswithfragmentsActivity.java:
[sourcecode language="java"]
package com.xrigau.mapsfragments;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;

import com.actionbarsherlock.app.SherlockFragment;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.google.android.maps.MapView;

public class MapswithfragmentsActivity extends SherlockFragmentActivity {

private MapFragment mMapFragment;
private MyListFragment mMyListFragment;

// We use this fragment as a pointer to the visible one, so we can hide it easily.
private Fragment mVisible = null;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// We instantiate the MapView here, it’s really important!
Exchanger.mMapView = new MapView(this, "INSERT_YOUR_MAP_API_KEY_HERE"); // TODO: Replace for API Key!

setupFragments();
// We manually show the list Fragment.
showFragment(mMyListFragment);
}

/**
* This method does the setting up of the Fragments. It basically checks if
* the fragments exist and if they do, we’ll hide them. If the fragments
* don’t exist, we create them, add them to the FragmentManager and hide
* them.
*/
private void setupFragments() {
final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();

// If the activity is killed while in BG, it’s possible that the
// fragment still remains in the FragmentManager, so, we don’t need to
// add it again.
mMapFragment = (MapFragment) getSupportFragmentManager().findFragmentByTag(MapFragment.TAG);
if (mMapFragment == null) {
mMapFragment = new MapFragment();
ft.add(R.id.fragment_container, mMapFragment, MapFragment.TAG);
}
ft.hide(mMapFragment);

mMyListFragment = (MyListFragment) getSupportFragmentManager().findFragmentByTag(MyListFragment.TAG);
if (mMyListFragment == null) {
mMyListFragment = new MyListFragment();
ft.add(R.id.fragment_container, mMyListFragment, MyListFragment.TAG);
}
ft.hide(mMyListFragment);

ft.commit();
}

/**
* This method shows the given Fragment and if there was another visible
* fragment, it gets hidden. We can just do this because we know that both
* the mMyListFragment and the mMapFragment were added in the Activity’s
* onCreate, so we just create the fragments once at first and not every
* time. This will avoid facing some problems with the MapView.
*
* @param fragmentIn
* The fragment to show.
*/
private void showFragment(Fragment fragmentIn) {
if (fragmentIn == null) return;

final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);

if (mVisible != null) ft.hide(mVisible);

ft.show(fragmentIn).commit();
mVisible = fragmentIn;
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu with the options to show the Map and the List.
getSupportMenuInflater().inflate(R.menu.menu, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.ic_list:
// Show mMyListFragment.
showFragment(mMyListFragment);
return true;

case R.id.ic_map:
// Show mMapFragment.
showFragment(mMapFragment);
return true;
}
return super.onOptionsItemSelected(item);
}

/**
* This class acts as an exchanger between the Activity and the MapFragment,
* so if you want, you can put the MapFragment class in a separate java
* file.
*
* @author Xavi
*
*/
public static class Exchanger {
// We will use this MapView always.
public static MapView mMapView;
}

/**
* This is our ListFragment class. You can put it in a separate java file.
*
* @author Xavi
*
*/
public static class MyListFragment extends SherlockListFragment {

public static final String TAG = "listFragment";

private final String[] mItems = { "List Item 1", "List Item 2",
"List Item 3", "List Item 4", "List Item 5", "List Item 6",
"List Item 7", "List Item 8", "List Item 9", "List Item 10" };

public MyListFragment() {}

@Override
public void onCreate(Bundle arg0) {
super.onCreate(arg0);
setRetainInstance(true);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup vg, Bundle data) {
// Inflate the ListView layout file.
return inflater.inflate(R.layout.list_fragment, null);
}

@Override
public void onViewCreated(View arg0, Bundle arg1) {
super.onViewCreated(arg0, arg1);
setListAdapter(new ArrayAdapter<String>(getSherlockActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, mItems));
}
}

/**
* This is the Fragment class that will hold the MapView as its content
* view. You can put it in a separate java file.
*
* @author Xavi
*
*/
public static class MapFragment extends SherlockFragment {

public static final String TAG = "mapFragment";

public MapFragment() {}

@Override
public void onCreate(Bundle arg0) {
super.onCreate(arg0);
setRetainInstance(true);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup vg, Bundle data) {
// The Activity created the MapView for us, so we can do some init stuff.
Exchanger.mMapView.setClickable(true);
Exchanger.mMapView.setBuiltInZoomControls(true); // If you want.

/*
* If you’re getting Exceptions saying that the MapView already has
* a parent, uncomment the next lines of code, but I think that it
* won’t be necessary. In other cases it was, but in this case I
* don’t this should happen.
*/
/*
* final ViewGroup parent = (ViewGroup) Exchanger.mMapView.getParent();
* if (parent != null) parent.removeView(Exchanger.mMapView);
*/

return Exchanger.mMapView;
}
}
}
[/sourcecode]

Here you have the source for the main.xml layout file:
[sourcecode language="xml"]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<!– This FrameLayout will be the PlaceHolder of the Fragments. –>
<FrameLayout android:id="@+id/fragment_container"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

</LinearLayout>
[/sourcecode]

There’s the source for the list_fragment.xml layout file:
[sourcecode language="xml"]
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
[/sourcecode]

That’s the menu.xml file:
[sourcecode language="xml"]
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

<item
android:id="@+id/ic_list"
android:icon="@android:drawable/ic_menu_agenda"
android:showAsAction="ifRoom|withText"
android:title="List"/>
<item
android:id="@+id/ic_map"
android:icon="@android:drawable/ic_menu_mapmode"
android:showAsAction="ifRoom|withText"
android:title="Map"/>

</menu>
[/sourcecode]

Finally the AndroidManifest.xml:
[sourcecode language="xml"]
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xrigau.mapsfragments"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="15"/>

<!– We need Internet in order to load the maps. –>
<uses-permission android:name="android.permission.INTERNET" />

<!– Set the theme in order to use the ActionBarSherlock. –>
<application android:theme="@style/Theme.Sherlock.Light.DarkActionBar"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >

<!– We need to tell Android that we’ll be using Google Maps Library. –>
<uses-library android:name="com.google.android.maps" />

<activity
android:name=".MapswithfragmentsActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
[/sourcecode]

And you can download the complete source for this tutorial in this link http://dl.dropbox.com/u/2397504/xrigau-abs_map_list_v1.zip.

So, the final result is:
The result!
Really nice! :)

To end up, I just hope that this can help somebody and if you have questions, please comment and ask what you want and I’ll try to do my best to help you.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>