Home

Android NavigationDrawer with Sliding Tabs

Both the NavigationDrawer and NavigationTabs are great for allowing a user to navigate your app. There are times that you may need to use them in conjunction and possible. However, you can not use the ActionBar Navigation Drawer and ActionBar Navigation Tabs together. In order to use tabs, you must implement them manually in your fagments. 

NavigationDrawer

Google has documented how to implement the NavigationDrawer here. Download the sample application found on the documentation page because there are some files that you will need:

  • res/drawable/xhdpi/ic_drawer.png
  • res/drawable/xhdpi/drawer_shadow.9.png
  • res/values/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">DrawerNavigationTabs</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>

    <string-array name="drawer_titles">
        <item>Tabs</item>
        <item>WebView</item>
    </string-array>

    <string name="drawer_open">Open navigation drawer</string>
    <string name="drawer_close">Close navigation drawer</string>
    <string name="title_section1">Section 1</string>
    <string name="title_section2">Section 2</string>
    <string name="title_section3">Section 3</string>


</resources>

<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- As the main content view, the view below consumes the entire
         space available using match_parent in both dimensions. -->
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- android:layout_gravity="start" tells DrawerLayout to treat
         this as a sliding drawer on the left side for left-to-right
         languages and on the right side for right-to-left languages.
         The drawer is given a fixed width in dp and extends the full height of
         the container. A solid background is used for contrast
         with the content view. -->
    <ListView
        android:id="@+id/left_drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#111"/>
</android.support.v4.widget.DrawerLayout>

Most of the time, a ListView will be enough to accomplish what you want to display in the drawer.

package com.paulusworld.drawernavigationtabs;

import android.os.Bundle;
import android.content.res.Configuration;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends FragmentActivity {

	private static final String TAG = MainActivity.class.getSimpleName();
	
	private DrawerLayout mDrawerLayout;
	private ListView mDrawerList;
	private ActionBarDrawerToggle mDrawerToggle;
	
	private CharSequence mDrawerTitle;
	private CharSequence mTitle;
	private String[] mDrawerItmes;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		mTitle = mDrawerTitle = getTitle();
		
		mDrawerItmes = getResources().getStringArray(R.array.drawer_titles);
		mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
		mDrawerList = (ListView) findViewById(R.id.left_drawer);
		
		// set a custom shadow that overlays the main content when the drawer opens
		mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow,  GravityCompat.START);
		
		// Add items to the ListView
		mDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mDrawerItmes));
		// Set the OnItemClickListener so something happens when a 
		// user clicks on an item.
		mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
		
		// Enable ActionBar app icon to behave as action to toggle the NavigationDrawer
		getActionBar().setDisplayHomeAsUpEnabled(true);
		getActionBar().setHomeButtonEnabled(true);
		
		mDrawerToggle = new ActionBarDrawerToggle(
				this, 
				mDrawerLayout, 
				R.drawable.ic_drawer, 
				R.string.drawer_open, 
				R.string.drawer_close
				) {
			public void onDrawerClosed(View view) {
				getActionBar().setTitle(mTitle);
				invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu
			}
			
			public void onDrawerOpened(View drawerView) {
				getActionBar().setTitle(mDrawerTitle);
				invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu
			}
		};
		
		mDrawerLayout.setDrawerListener(mDrawerToggle);
		
		// Set the default content area to item 0
		// when the app opens for the first time
		if(savedInstanceState == null) {
			navigateTo(0);
		}
	
	}
	
	/*
	 * If you do not have any menus, you still need this function
	 * in order to open or close the NavigationDrawer when the user 
	 * clicking the ActionBar app icon.
	 */
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		if(mDrawerToggle.onOptionsItemSelected(item)) {
			return true;
		}
		
		return super.onOptionsItemSelected(item);
	}
	
	/*
	 * When using the ActionBarDrawerToggle, you must call it during onPostCreate()
	 * and onConfigurationChanged()
	 */
	
	@Override
	protected void onPostCreate(Bundle savedInstanceState) {
		super.onPostCreate(savedInstanceState);
		mDrawerToggle.syncState();
	}
	
	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);
		mDrawerToggle.onConfigurationChanged(newConfig);
	}
	
	private class DrawerItemClickListener implements OnItemClickListener {
		@Override
		public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
			navigateTo(position);
		}
	}
	
	private void navigateTo(int position) {
		
		switch(position) {
		case 0:
			getSupportFragmentManager()
				.beginTransaction()
				.replace(R.id.content_frame, TabbedFragment.newInstance(), TabbedFragment.TAG).commit();
			break;
		case 1:
			getSupportFragmentManager()
				.beginTransaction()
				.replace(R.id.content_frame,
						WebViewFragment.newInstance(),
						WebViewFragment.TAG).commit();
			break;
		}
	}
	
	@Override
	public void setTitle(CharSequence title) {
		mTitle = title;
		getActionBar().setTitle(mTitle);
	}
	
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
package com.paulusworld.drawernavigationtabs;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;

public class WebViewFragment extends Fragment {

	public final static String TAG = WebViewFragment.class.getSimpleName();
	
	public WebViewFragment() {
		// TODO Auto-generated constructor stub
	}

	public static WebViewFragment newInstance() {
		return new WebViewFragment();
	}

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

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		return inflater.inflate(R.layout.fragment_webview, container, false);
	}
	
	@Override
	public void onViewCreated(View view, Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);
		WebView webView = (WebView) view.findViewById(R.id.webView);
		webView.loadUrl("http://www.paulusworld.com");
	}
}

The easiest way to accomplish adding a tabbed activity or fragment in this case, is to create a new activity and selecting the type of navigation you want. In this case, I've chosen Scrollable Tabs + Swipe. Once the activity was created, I had to modify from a FragmentActivity to a Fragment:

  • Remove the <activity> tag for the newly added Activity in the AndroidManifest.xml file
  • Change the onCreate() function to what you see here.
  • Add the onCreateView() function
  • Updated the names to classes and layouts.
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".TabbedFragment" >

    <!--
    This title strip will display the currently visible page title, as well as the page
    titles for adjacent pages.
    -->

    <android.support.v4.view.PagerTitleStrip
        android:id="@+id/pager_title_strip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        android:background="#33b5e5"
        android:paddingBottom="4dp"
        android:paddingTop="4dp"
        android:textColor="#fff" />

</android.support.v4.view.ViewPager>
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".TabbedActivity$DummySectionFragment" >

    <TextView
        android:id="@+id/section_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>
package com.paulusworld.drawernavigationtabs;

import java.util.Locale;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class TabbedFragment extends Fragment {

	public static final String TAG = TabbedFragment.class.getSimpleName();
	SectionsPagerAdapter mSectionsPagerAdapter;
	ViewPager mViewPager;

	
	public static TabbedFragment newInstance() {
		return new TabbedFragment();
	}
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		View v = inflater.inflate(R.layout.fragment_tabbed, container, false);
		mSectionsPagerAdapter = new SectionsPagerAdapter(
				getChildFragmentManager());
		
		mViewPager = (ViewPager) v.findViewById(R.id.pager);
		mViewPager.setAdapter(mSectionsPagerAdapter);
		
		return v;
	}
	
	public class SectionsPagerAdapter extends FragmentPagerAdapter {

		public SectionsPagerAdapter(FragmentManager fm) {
			super(fm);
		}

		@Override
		public Fragment getItem(int position) {
			Fragment fragment = new TabbedContentFragment();
			Bundle args = new Bundle();
			args.putInt(TabbedContentFragment.ARG_SECTION_NUMBER, position + 1);
			fragment.setArguments(args);
			return fragment;
		}

		@Override
		public int getCount() {
			return 3;
		}

		@Override
		public CharSequence getPageTitle(int position) {
			Locale l = Locale.getDefault();
			switch (position) {
			case 0:
				return getString(R.string.title_section1).toUpperCase(l);
			case 1:
				return getString(R.string.title_section2).toUpperCase(l);
			case 2:
				return getString(R.string.title_section3).toUpperCase(l);
			}
			return null;
		}
	}

	public static class TabbedContentFragment extends Fragment {

		public static final String ARG_SECTION_NUMBER = "section_number";

		public TabbedContentFragment() {
		}

		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
				Bundle savedInstanceState) {
			View rootView = inflater.inflate(R.layout.fragment_tabbed_content,
					container, false);
			TextView dummyTextView = (TextView) rootView
					.findViewById(R.id.section_label);
			dummyTextView.setText(Integer.toString(getArguments().getInt(
					ARG_SECTION_NUMBER)));
			return rootView;
		}
	}

}
File Attachment: 

Integrating Google Maps into an Android Application

Setup

Implementing Google Maps is more involved than Google Analytics. Unlike Google Analytics, where you copy a jar file to your lib directory, you must do the following before you can begin using Google Maps in your app:

  1. Download the Google Play Services SDK by launching the SDK manager and selecting the Google Play services under Extras

  2. Import the Google Play Services SDK into your workspace
    • Within Eclipse, go to File -> Import and select Existing Android Code into Workspace
    • Click the Browse button and navigate to the <sdk-dir>/extras/google/google_play_services/libproject/google-play-services_lib
    • Check the Copy projects into workspace box.
    • Click Finish
  3. Make the imported project a library
    • Right click on the project and go down to Properties
    • In the left pane, click on the leaf called Android.
    • Check the box that says is Library

  4. Add a library reference
    • Right click on the project that will use Google Maps and go down to Properties
    • In the left pane, click on the leaf called Android.
    • Within the Library section, click the Add button and select the Google Play services SDK project.
    • Click OK to close the dialog window.

  5. Add the following to the AndroidManifest.xml file:

    <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
    
  6. Get an Android certificate's SHA1 fingerprint; a debug/developer certificate fingerprint will work for testing but you will not be able to upload your app with a debug/developer certificate. A debug/developer certificate is automatically generated when using Eclipse for debugging. To retrieve the debug/developer SHA1 fingerprint:
    • Open Eclipse's preferences
    • Click on the Build leaf
    • Copy the SHA1 fingerprint


    Another way of obtaining the information you need is by running the following command:

    keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
    keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
  7. Get a Google Maps API key which can be obtained by
    • Logging into Google's Developers Console
    • Select a project
    • Clicking on APIs and auth
    • Set the status of Google Maps Android API v2 to On(Only necessary if you haven't enabled it already.)
    • Click Credentials
    • Click CREAT NEW KEY and enter the SHA1 finger print followed by a semicolon (;) and package name. For example if my package name is com.paulusworld.androidmap, I would enter something like 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00;com.paulusworld.androidmap
  8. Add the API key to the AndroidManifest.xml within the <application> tag file

    <meta-data
        android:name="com.google.android.maps.v2.API_KEY"
        android:value="YOUR_API_KEY_HERE"/>
    
  9. Add the following permissions to the AndroidManifest.xml file:
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <!--
    The following two permissions are not required to use
    Google Maps Android API v2, but are recommended.
    -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    
  10. Since Goole Maps v2 uses OpenGL ES 2, if it's not installed on the phone, then it will not work. Add the following to the AndroidManifest.xml within the <application> tags:
    <uses-feature
         android:glEsVersion="0x00020000"
         android:required="true" />

Testing

If you imported Google Play services and configured your project properly, the following example will work. Create a new Android project.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.paulusworld.androidmaps"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="19" />

    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <!--
     The following two permissions are not required to use
     Google Maps Android API v2, but are recommended.
    -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.paulusworld.androidmaps.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="YOUR_API_KEY_HERE" />
    </application>

</manifest>

 

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

    <fragment
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.google.android.gms.maps.SupportMapFragment" />

</LinearLayout>

 

import android.os.Bundle;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {

    /**
     * Note that this may be null if the Google Play services APK is not available.
     */
    private GoogleMap mMap;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	@Override
    protected void onResume() {
        super.onResume();
        setUpMapIfNeeded();
    }
	
    private void setUpMapIfNeeded() {
        // Do a null check to confirm that we have not already instantiated the map.
        if (mMap == null) {
            // Try to obtain the map from the SupportMapFragment.
            mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
                    .getMap();
            // Check if we were successful in obtaining the map.
            if (mMap != null) {
                setUpMap();
            }
        }
    }
    
    private void setUpMap() {
        mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
    }

}

The above code was slightly modified from the example I obtained from the Google Maps documentation.

Using Google Maps

Below, I've modified the setUpMap function to search for "Milwaukee Public Museum". The first result is being used to get the coordinates, place a marker on the map, and zoom in far enough to show the floor picker.

private void setUpMap() {
		Geocoder gc = new Geocoder(this);
		List<Address> results;
		try {
			// Search for "Milwaukee Public Museum" and only except 1 search result.
			results = gc.getFromLocationName("Milwaukee Public Museum", 1);
			Address aMPM = results.get(0);
// The getFeatureName() function returns the name of the place, which is // Milwaukee Public Museum. mMap.addMarker(new MarkerOptions().position( new LatLng(aMPM.getLatitude(), aMPM.getLongitude())).title( aMPM.getFeatureName()));
// Centers the camera over the building and zooms int far enough to // show the floor picker. mMap.moveCamera(CameraUpdateFactory.newLatLngZoom( new LatLng(results.get(0).getLatitude(), results.get(0) .getLongitude()), 18)); } catch (IOException e) { // Normally we'd do more here, but this is an example. } }

Another thing you may want to do is display a Google Map along with some additional content. Here we have a map that is about half the hieight of the screen with three TextViews.

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

    <fragment
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="45"
        class="com.google.android.gms.maps.SupportMapFragment" />

    <TextView 
        android:id="@+id/textViewLocation1"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="15" 
        android:background="#0099FF"
        android:textColor="#000000" />
    
    <TextView
        android:id="@+id/textViewLocation2"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="15"
        android:background="#00CCFF"
        android:textColor="#000000" />
    
    <TextView 
        android:id="@+id/textViewDragging"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="15" 
        android:background="#00FFFF"
        android:textColor="#000000" />
</LinearLayout>

Below will move the camerea to the location specified by lat and long. The third parameter tells the camera how far to zoom in. A value of 18 will display the indoor maps. However, it's not currently possible to select a floor through code.

mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, long, zoom));
mMap.addMarker(new MarkerOptions()
		.position(new LatLng(lat, long)) // Position of where the marker should be placed
		.title("Location 1") // The title of the marker. This will be displayed on the first line with in the bubble
		.snippet("Snippet") // Additional content that will be added to the bubble.
		.draggable(true)); // Allows the marker to be moved.

You can find all the MarkerOptions methods in the API.

OnMarkerClickListener

There are two ways of doing this, the first is as follows:

public class MainActivity extends FragmentActivity implements OnMarkerClickListener {
	private GoogleMap mMap;
	private Marker mLocation1;

	// snipp

	private void setUpMap() {
		mLocation1 = mMap.addMarker(new MarkerOptions()
				.position(new LatLng(lat, long))
				.title("Location 1")
				.snippet("Snippet")
				.draggable(true));

		mMap.setOnMarkerClickListener(this);
		// Snipped more code
	}

	@Override
	public boolean onMarkerClick(Marker marker) {
		// Do something.
	}

	// snipp
}

The mMap variable is the the Google Map object, and the mLocation1 is a marker that will be added to the map when the app runs. You implement the OnMarkerClickListener class and add the onMarkerClick method which handles the click of any marker. The second way you can add an OnMarkerClickListener is as follows:

mMap.setOnMarkerClickListener(new OnMarkerClickListener() {

	@Override
	public boolean onMarkerClick(Marker marker) {
		// TODO Auto-generated method stub
		return false;
	}
				
});

OnMarkerDragListener

If you set up your markers to be draggable, you can add a listener to perform an action when a user starts, is, and stops dragging a marker.

public class MainActivity extends FragmentActivity implements OnMarkerDragListener {
	private GoogleMap mMap;
	private Marker mLocation1;

	// snipp

	private void setUpMap() {
		mLocation1 = mMap.addMarker(new MarkerOptions()
				.position(new LatLng(lat, long))
				.title("Location 1")
				.snippet("Snippet")
				.draggable(true));

		mMap.setOnMarkerDragListener(this);
		// Snipped more code
	}

	@Override
	public void onMarkerDrag(Marker marker) {
		// do something while dragging

	}

	@Override
	public void onMarkerDragEnd(Marker marker) {
		// do something when the user stops dragging

	}

	@Override
	public void onMarkerDragStart(Marker marker) {
		// do something when the user starts dragging

	}

	// snipp
}

As in the OnMarkerClickListener example, you can also accomplish this by:

mMap.setOnMarkerDragListener(new OnMarkerDragListener() {

	@Override
	public void onMarkerDrag(Marker arg0) {
		// do something while dragging
					
	}

	@Override
	public void onMarkerDragEnd(Marker arg0) {
		// do something when the user stops dragging
					
	}

	@Override
	public void onMarkerDragStart(Marker arg0) {
		// do something when the user starts dragging
					
	}
				
});

OnMapClickListener

You can add an OnMapClickListener to add markers at the location where a user has clicked on the map or perform another action.

public class MainActivity extends FragmentActivity implements OnMapClickListener {
	private GoogleMap mMap;

	// snipp

	private void setUpMap() {
		mLocation1 = mMap.addMarker(new MarkerOptions()
				.position(new LatLng(lat, long))
				.title("Location 1")
				.snippet("Snippet")
				.draggable(true));

		mMap.setOnMapClickListener(this);
		// Snipped more code
	}

	@Override
	public void onMapClick(LatLng loc) {
		mMap.addMarker(new MarkerOptions()
				.position(loc));
	}

	// snipp
}

Backing Up with NFS and rsync

Assumptions & Prerequisites

  • Fresh install of RedHat/CentOS/Fedora on the server
  • minimal install profile
  • Security is not taken into account

Setup

A system running RedHat/CentOS/Fedora, but any Linux distribution will work with the proper modifications. Just to get something up and running, I did a minimal install of CentOS 6.4. The CentOS server will have a partition mounted at /mnt/backup which will be shared via NFS.

The machine that contains the files that we want to back up will mount the NFS share which is found on the server (/mnt/backup). I chose to have static IPs for the computers but you can use your own addressing scheme or DHCP.

  • Server Configuration
    • IP Address: 192.168.1.112
    • Netmask: 255.255.255.0
    • Gateway: 192.168.1.1
    • The backup directory that will be shared via NFS is /mnt/backup
    • IPTables is running
    • SELinux is running
  • Client
    • IP Address: 192.168.1.111
    • Netmask: 255.255.255.0
    • Gateway: 192.168.1.1
    • NFS mount directory: /mnt/nfs

Server Configuration

If need be, change eth0 to the name of your network interface.

ONBOOT=yes 
BOOTPROTO=static
IPADDR=192.168.1.112
NETMASK=255.255.255.0
HOSTNAME=backup
GATEWAY=192.168.1.1
ifconfig eth0
route -n
ping google.com

IPTables Configuration

By default, iptables is installed and configured to only allow ssh connections. We will need to create a new rule which allows connections to TCP port 2049 and UDP port 53027.

# iptables -I INPUT -p tcp -s 192.168.1.111 --dport nfs -j ACCEPT
# iptables -I INPUT -p udp -s 192.168.1.111 --dport nfs -j ACCEPT
# service iptables save

If you do not want to use iptables, then stop the service and disable it from starting at boot:

# service iptables stop
#/sbin/chkconfig --level 2345 iptables off

SELInux Configuration

RedHat documentation states that all NFS policies are confined to default, meaning that no additional configuration or booleans need to be set.

NFS Server Setup and Configuration

Install the NFS server and client. If you want to have your backup directory some place else, substitute /mnt/backup with the desired location.

# yum -y install nfs-utils
# mkdir /mnt/backup
# chown nfsnobody:nfsnobody /mnt/backup
# /sbin/chkconfig --level 345 nfs on

On line 3, you're changing the permission of the backup directory so that it can be written to.

/mnt/backup 192.168.1.111(rw,root_squash)
exportfs -a

In the exports file, we're telling NFS to not allowing the root user access. Since we're not allowing root access, we will need to setup a user and create a directory in the exported directory with the proper permissions.

# useradd -s /bin/bash -m paulus
# passwd paulus
# mkdir /mnt/backup/paulus
# chmod paulus:paulus /mnt/backup/paulus

Client Configuration

No configuration is required. However, if you want to mount the NFS share on boot you will need to edit the fstab file.

192.168.1.112:/mnt/backup     /mnt/backup     rw,hard,intr     0     0

You should be familiar with the rw mount option. I'm using hard and intr. The hard option prevents corrupt data being written to the remote server. This can happen if there are any network connectivity issues. In the event that the NFS server is unreachable, then the program accessing the file will wait. However, you won't be able to kill the process except for a sure kill. Adding the intr option allows the process to be killed. To see a list of options view the man pages for the mount and nfs commands.

If you don't want to have the NFS share mounted at boot, run the following command.

mount -t nfs 192.168.1.112:/mnt/backup /mnt/nfs

rsync

Now that the NFS server is up and configured we can start backing up. The example below assumes that we have the NFS share mounted at /mnt/backup.

rsync -az /home /mnt/backup
rsync -az --progress /home/mnt/backup

The above will copy the home directory to the remote server. The resulting tree structure on the remote server will be /mnt/backup/home. If you add a trailing slash, it will copy all the files and directories in /home to /mnt/backup. The -a is saying you want to archive the directory and is equivelent to using -rlptgoD

  • -r, --recursive; recurse into directories
  • -l, --links; copy symlinks as symlinks. (You can use the -L, --copy-links to convert the links into files. Additionally specifying --copy-unsafe-links will trun links pointing to files outside of the tree into files  or --copy-safe-links will ignore links that point outside of the tree.)
  • -p, --perms; preserve permissions
  • -t, --times; preserve modification times
  • -g, --group; preserve group
  • -o, --owner; preserve owner
  • -D; same as --devices (causes rsync to transfer character and block device files to the remote system to recreate these devices.), --specials (causes rsync to transfer special files such as named sockets and fifos.)

Other options that you might find useful are:

  • --progress; Shows the progress of the operation.
  • --delete; Removes any files from the destination that aren't in the source.
  • -n, --dry-run; Shows you what's going to happen with out actually doing anything.
  • --exclude, --exclude-from; List of files and/or directories that you don't want to transfer. The first is the command line version. The second, allows you to create a file with a list of files and directories (one per line)

Troubleshooting

  • If you're unable to see the files, make sure the file system is actually mounted. 

    # cat /proc/mount
    # mount | grep nfs
    
  • Remount the file system
  • If the server is returning Permission denied:
    • Make sure that the volumes are exported. Try re-running exportfs on the server. (exportfs -ra)
    • If you are trying to mount the file system as rw and the export file has the volume exported as ro.
    • Check /proc/fs/nfs/exports or /var/lib/nfs/etab making sure the clients are correct.
    • You can not export a child and a parent. For example:

      /mnt/backup 192.168.1.111(rw)
      /mnt/backup/other_directory 192.168.1.111(rw)
  • Verify that NFS is actually running

       program vers proto   port  service
        100000    4   tcp    111  portmapper
        100000    3   tcp    111  portmapper
        100000    2   tcp    111  portmapper
        100000    4   udp    111  portmapper
        100000    3   udp    111  portmapper
        100000    2   udp    111  portmapper
        100024    1   udp  47994  status
        100024    1   tcp  36487  status
        100005    1   udp  38727  mountd
        100005    1   tcp  50962  mountd
        100005    2   udp  37566  mountd
        100005    2   tcp  39548  mountd
        100005    3   udp  55141  mountd
        100005    3   tcp  46667  mountd
        100003    2   tcp   2049  nfs
        100003    3   tcp   2049  nfs
        100003    4   tcp   2049  nfs
        100227    2   tcp   2049  nfs_acl
        100227    3   tcp   2049  nfs_acl
        100003    2   udp   2049  nfs
        100003    3   udp   2049  nfs
        100003    4   udp   2049  nfs
        100227    2   udp   2049  nfs_acl
        100227    3   udp   2049  nfs_acl
        100021    1   udp  38521  nlockmgr
        100021    3   udp  38521  nlockmgr
        100021    4   udp  38521  nlockmgr
        100021    1   tcp  35931  nlockmgr
        100021    3   tcp  35931  nlockmgr
        100021    4   tcp  35931  nlockmgr
    
  • On the client, make sure that NFS4 is enabled

    ffffffffa1391900 t nfs4_check_openmode  [nfsd]
    ffffffffa1391a70 t __nfs4_file_get_access       [nfsd]
    ffffffffa1391bc0 t nfs4_set_lock_denied [nfsd]
    ffffffffa1391d80 t nfs4_alloc_stid      [nfsd]
    ffffffffa1391ec0 t nfs4_access_to_omode [nfsd]
    ffffffffa1392300 t __nfs4_file_put_access       [nfsd]
    ffffffffa1392370 t nfs4_file_put_access [nfsd]
    ffffffffa1392490 t nfs4_get_vfs_file.isra.76    [nfsd]
    ffffffffa13928f0 t nfs4_share_conflict.isra.68  [nfsd]
    ffffffffa1393290 t nfs4_preprocess_seqid_op     [nfsd]
    ffffffffa13aa650 d nfs4_disable_idmapping       [nfsd]
    ffffffffa13a7248 r __param_nfs4_disable_idmapping       [nfsd]
    ffffffffa13a2000 r __param_str_nfs4_disable_idmapping   [nfsd]
    ... snip

Pages