Switch Maps

This sample shows a way to switch between two different maps in a single app by leveraging the Android Fragments pattern.

Features

  • ArcGISTiledMapServiceLayer
  • Fragment

Sample Design

In this app, a single MapActivity contains a MapFragment that in turn contains a MapView. When the button on the action bar is pressed, the existing MapFragment is replaced by a new instance of the MapFragment that has different map contents. The current extent of the MapView is preserved when switching between fragments, by making use of the retainState and restoreState methods on the MapView.

Sample Code

/* Copyright 2012 ESRI
 *
 * All rights reserved under the copyright laws of the United States
 * and applicable international laws, treaties, and conventions.
 *
 * You may freely redistribute and use this sample code, with or
 * without modification, provided you include the original copyright
 * notice and use restrictions.
 *
 * See the Sample code usage restrictions document for further information.
 *
 */

package com.esri.android.samples.switchmaps;

import com.esri.android.map.MapView;
import com.esri.android.samples.R;

import android.app.ActionBar;
import android.app.Activity;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

/* This sample shows a way to switch between two different maps in a single app by leveraging the Android Fragments. 
 * In this app, a single MapActivity contains a MapFragment that in turn contains a MapView. When the button on the
 * action bar is pressed, the existing MapFragment is replaced by a new instance of the MapFragment that has 
 * different map contents. The current extent of the MapView is preserved when switching between fragments, by 
 * making use of the retainState and restoreState methods on the MapView. This sample requires Android 4.0 and 
 * above, although you can adapt it for use with older versions by using the Android Support Library.
 */
public class MapActivity extends Activity {

  // URLs of services added as layers to the maps.
  private final String MAP1_URL = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer";

  private final String MAP2_URL = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer";

  // Action bar, and items for switching between maps. Visibility of these items
  // is changed, so that only one option is available at a time.
  private ActionBar mActionBar = null;

  private MenuItem mSwitchTo1MenuItem = null;

  private MenuItem mSwitchTo2MenuItem = null;

  // Current map fragment state.
  private boolean map1Active = true;

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

    if (savedInstanceState == null) {
      // If no saved state exists, add a map fragment with the initial map
      // service layer.
      MapFragment mapFrag = new MapFragment();
      // arguments to send to MapFragment
      Bundle args = new Bundle();
      // service url
      args.putString("MAPURL", MAP1_URL);
      // opening extent null as it is set by view xml
      args.putString("MAPSTATE", null);

      mapFrag.setArguments(args);

      getFragmentManager().beginTransaction().add(R.id.fragmentContainer, mapFrag).commit();
    } else {
      // If there is saved state, then the fragment will be re-created by the android framework.
      // Extract the saved state of the activity from the bundle parameter.
      map1Active = savedInstanceState.getBoolean("map1Active", true);
    }

    // Set up the action bar.
    mActionBar = getActionBar();
    mActionBar.setTitle(R.string.app_name);
  }

  // Store temporary state so that this can be reinstated, for example if the
  // device is rotated.
  @Override
  protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    // Save the activity state - which map fragment is currently active.
    outState.putBoolean("map1Active", map1Active);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {

    // Inflate the menu; this adds items from the Menu XML to the action bar, if
    // present.
    getMenuInflater().inflate(R.menu.main, menu);

    // Get references to the buttons on the menu for use in onOptionsItemSelected.
    mSwitchTo1MenuItem = menu.findItem(R.id.switchToMap1Button);
    mSwitchTo2MenuItem = menu.findItem(R.id.switchToMap2Button);

    // Initialize the state of the subtitles and buttons from the saved state.
    updateActionBar();

    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {

    // Handle user pressing the action bar menu items.
    boolean retVal = false;

    // Get the current map directly from this activity.
    MapView currentMap = (MapView) this.findViewById(R.id.map);

    // Based on the menu item selected, switch the map fragment.
    String mapUrl = null;
    switch (item.getItemId()) {
    case R.id.switchToMap2Button:
      // Use the alternative map service as a layer.
      mapUrl = MAP2_URL;
      map1Active = false;
      break;

    case R.id.switchToMap1Button:
      // Use the first map service as a layer.
      mapUrl = MAP1_URL;
      map1Active = true;
      break;

    default:
      retVal = super.onOptionsItemSelected(item);
      break;
    }

    if (mapUrl != null) {
      // Create a new fragment with a specific map service layer.
      MapFragment mapFrag = new MapFragment();
      // arguments to be sent to MapFragment
      Bundle args = new Bundle();
      // service url
      args.putString("MAPURL", mapUrl);
      // current extent
      args.putString("MAPSTATE", currentMap.retainState());
      mapFrag.setArguments(args);

      // Create a transaction and replace existing fragment with this new
      // fragment.
      FragmentTransaction ft2 = getFragmentManager().beginTransaction();
      ft2.replace(R.id.fragmentContainer, mapFrag);
      ft2.commit();

      // Update ActionBar subtitle, and action bar button visibility.
      updateActionBar();
      retVal = true;
    }

    return retVal;
  }

  // Update the action bar based on the saved state.
  private void updateActionBar() {
    // Update the action bar subtitle to indicate the current map.
    if (mActionBar != null) {
      mActionBar.setSubtitle(map1Active ? R.string.map1 : R.string.map2);
    }
    // Update the visible menu items to allow correctly switching maps.
    if (mSwitchTo1MenuItem != null) {
      mSwitchTo1MenuItem.setVisible(!map1Active);
    }
    if (mSwitchTo2MenuItem != null) {
      mSwitchTo2MenuItem.setVisible(map1Active);
    }
  }
}
package com.esri.android.samples.switchmaps;

import android.app.Fragment;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.esri.android.map.MapView;
import com.esri.android.map.ags.ArcGISTiledMapServiceLayer;
import com.esri.android.samples.R;

/* This fragment contains a MapView, and is added to the MapActivity. Temporary state such as map contents
 * and extent are preserved if, for example, the device is rotated.
 * */
public class MapFragment extends Fragment {

  // MapView in this fragment.
  private MapView mMapView = null;

  // URL of the map service that is added as a layer to the MapView.
  private String mTiledServiceUrl = null;

  // Center and resolution of the MapView.
  private String mMapState = null;

  // Keys used to store temporary map state and map data url.
  private final String SERVICE_URL = "serviceUrl";
  private final String MAP_STATE = "mapState";

  public MapFragment() {
    // Empty constructor is required for when the fragment is re-created by the
    // system.
    super();

  }

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

  }

  @Override
  public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    // Save the URL of map service layer, and map state (map center and
    // resolution).

    outState.putString(SERVICE_URL, mTiledServiceUrl);
    outState.putString(MAP_STATE, mMapView.retainState());
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    // Inflate the map fragment from the XML layout and get the MapView.
    View v = inflater.inflate(R.layout.fragment_map, container, false);
    mMapView = (MapView) v.findViewById(R.id.map);

    if (savedInstanceState != null) {
      // If there is saved state, then the fragment will be re-created by the
      // android framework. Extract the saved state
      // of the fragment from the bundle parameter.
      mTiledServiceUrl = savedInstanceState.getString(SERVICE_URL, null);
      mMapState = savedInstanceState.getString(MAP_STATE, null);
    } else {
      // Retrieve the service url and extent from arguments provided by
      // MapActivity
      Bundle args = getArguments();
      mTiledServiceUrl = args.getString("MAPURL");
      mMapState = args.getString("MAPSTATE");
    }

    // If a service URL has been set, add a map layer based on that service.
    // After the layer is added, this will ensure
    // the map has a spatial reference, and the full extent covers the entire
    // world, so wrap around map can be set.
    if (!TextUtils.isEmpty(mTiledServiceUrl)) {
      mMapView.addLayer(new ArcGISTiledMapServiceLayer(mTiledServiceUrl));
      mMapView.enableWrapAround(true);
    }
    // If map state (center and resolution) has been stored, update the MapView
    // with this state.
    if (!TextUtils.isEmpty(mMapState)) {
      mMapView.restoreState(mMapState);
    }

    // Return the view for this Fragment.
    return v;
  }

  @Override
  public void onPause() {
    super.onPause();

    // Call MapView.pause to suspend map rendering while the activity containing
    // this fragment is paused, which can save
    // battery usage.
    mMapView.pause();
  }

  @Override
  public void onResume() {
    super.onResume();

    // Call MapView.unpause to resume map rendering when the activity containing
    // this fragment returns to the
    // foreground.
    mMapView.unpause();
  }

}
Feedback on this topic?