Writing a Real Android App from Scratch: Part 3/9 – GPS, LocationManager and Geocoding

January 23, 2013

Welcome to part 3 of the Writing a Real Android App from Scratch series.

In the previous part we added three containers for hosting our contents. The first of the contents is a view that displays current address info, which we will implement in this part. The idea is to fetch the current location (latitude and longitude), wait for the location to arrive, ask a ‘translator’ to translate the latitude and longitude to something that we can easily comprehend such as city, state, country, zip code etc, wait for the translator to give us the human-readable address, and eventually display the address. This will also be our first interaction with the underlying hardware – GPS radio and Wi-Fi radio. When hardware is involved, expect some delays to complete your requests. That’s why we have to wait for each response after making a request. We will see how to perform/ simulate a ‘wait’ in Android.

This part is going to be a bit longer and a bit involved than the previous one. Again, if you got stuck on anything, don’t hesitate to ask for help.

Prerequisite:

Plan of Action:

Fetching the Current Location:

We need a LocationService class that is responsbile for serving us the current location. It has to do so without blocking our main thread. You might need something like this class in your future projects as well. So, we will put some extra efforts to make this class reusable.

1. Create a new interface LocationResultListener with just one public that takes a single parameter of type Location:

1 //file: src/main/java/com.ashokgelal.tagsnap/main/listeners/LocationResultListener.java
2 public interface LocationResultListener {
3     public void onLocationResultAvailable(Location location);
4 }

The idea here is to have the CurrentFragment implement this interface so that when the location is available, we get back the result asynchronously. We will make CurrentFragment implement this interface later. For now let’s create the actual service class that is responsible for giving us back the location.

2. Add a new class LocationService and add a method getLocation():

1 //file: src/main/java/com.ashokgelal.tagsnap/main/services/LocationService.java
2 public class LocationService {
3     private LocationResultListener mLocationResultListener;
4     public boolean getLocation(Context context, LocationResultListener locationListener) {
5         mLocationResultListener = locationListener; 
6         return false;
7     }
8 }

Before we move on to fill up the getLocation() method, let’s talk a little bit about what and how we want to get the location. In Android (and almost all of the other mobile platforms), there are two ways of getting a device’s current location – with the help of a GPS radio and/ or with the help of a Wi-Fi radio. A GPS’s location is more accurate than a Wi-Fi’s but GPS might not be always available. Which means we need to make two requests for getting current location. We will do so by creating two instances of LocationListener and listen to OnLocationChanged() callback. However, you shouldn’t be too optimist about these requests. What if the location is not currently available because, may be, both radios are off? Or there are no GPS signals? For such cases, we will try to get last known locations from both these providers and return the latest one. We will do this with a timer with some delay — as we want to give getting current location a try, and get the last known location. This will also free the UI thread. In any case — location immediately available or running timer to get the last known locations — when we receive the location from either of the two providers, we will cancel the other one, stop the timer, and call the LocationResultListener passing the actual location. Let’s implement all these one by one. We will start with the constructor where we will create two instances of LocationListener:

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/services/LocationService.java
 2 ...
 3     private final LocationListener mGpsLocationListener;
 4     private final LocationListener mNetworkLocationListener;
 5     private LocationResultListener mLocationResultListener;
 6     private LocationManager mLocationManager;
 7     private Timer mTimer;
 8 
 9     public LocationService() {
10         mGpsLocationListener = new LocationListener() {
11             @Override
12             public void onLocationChanged(Location location) {
13                 mTimer.cancel();
14                 mLocationManager.removeUpdates(this);
15                 mLocationManager.removeUpdates(mNetworkLocationListener);
16                 mLocationResultListener.onLocationResultAvailable(location);
17             }
18 
19             @Override
20             public void onStatusChanged(String s, int i, Bundle bundle) {
21             }
22 
23             @Override
24             public void onProviderEnabled(String s) {
25             }
26 
27             @Override
28             public void onProviderDisabled(String s) {
29             }
30         };
31 
32         mNetworkLocationListener = new LocationListener() {
33             @Override
34             public void onLocationChanged(Location location) {
35                 mTimer.cancel();
36                 mLocationManager.removeUpdates(this);
37                 mLocationManager.removeUpdates(mGpsLocationListener);
38                 mLocationResultListener.onLocationResultAvailable(location);
39 
40             }
41 
42             @Override
43             public void onStatusChanged(String s, int i, Bundle bundle) {
44             }
45 
46             @Override
47             public void onProviderEnabled(String s) {
48             }
49 
50             @Override
51             public void onProviderDisabled(String s) {
52             }
53         };
54     }
55 ...

All we did was to create two instances of LocationListener and overrode the required methods. Inside onLocationChanged() callback, we cancel the timer, remove both the location listeners from getting any future updates (after all, we need the address only once), and set the result.

Now, we are ready to fill the getLocation() method. We don’t want to blindly request location updates from the provider before making sure that at least one of them is available. If only one of the providers is enabled then it’s okay but if both of them are disabled or not available, we will return a false indicating a failure.

3. Change the getLocation() method to:

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/services/LocationService.java
 2 ...
 3     private boolean mGpsEnabled;
 4     private boolean mNetworkEnabled;
 5 
 6     public boolean getLocation(Context context, LocationResultListener locationListener) {
 7           mLocationResultListener = locationListener;
 8           if (mLocationManager == null)
 9               mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
10 
11           try {
12               mGpsEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
13           } catch (Exception ex) { }
14 
15           try {
16               mNetworkEnabled = mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
17           } catch (Exception ex) { }
18 
19           if (!mGpsEnabled && !mNetworkEnabled)
20               return false;
21 
22           if (mGpsEnabled)
23               mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mGpsLocationListener);
24 
25           if (mNetworkEnabled)
26               mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, mNetworkLocationListener);
27 
28           mTimer = new Timer();
29           mTimer.schedule(new LastLocationFetcher(), 20000);
30           return true;
31     }
32 ...

Now, all that is required is the LastLocationFetcher class that extends TimerTask. In the overridden Run() method we will first remove both listeners from receiving any updates (after all, the timer runs after 20 seconds, which means we gave up on getting the current location and falling back to last location). We will then check the latest of the two locations and set the result.

4. Add a private inner class LastLocationFetcher, make it extend TimerTask, and override Run() method:

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/services/LocationService.java
 2 ...
 3     private class LastLocationFetcher extends TimerTask {
 4 
 5         @Override
 6         public void run() {
 7 
 8             // remove GPS location listener
 9             mLocationManager.removeUpdates(mGpsLocationListener);
10             // remove network location listener
11             mLocationManager.removeUpdates(mNetworkLocationListener);
12 
13             Location gpsLoc = null, netLoc = null;
14 
15             // if we had GPS enabled, get the last known location from GPS provider
16             if (mGpsEnabled)
17                 gpsLoc = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
18 
19             // if we had WiFi enabled, get the last known location from Network provider
20             if (mNetworkEnabled)
21                 netLoc = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
22 
23 
24             // if we had both providers, get the newest last location
25             if (gpsLoc != null && netLoc != null) {
26                 if (gpsLoc.getTime() > netLoc.getTime())
27                     // last location from the GPS provider is the newest
28                     mLocationResultListener.onLocationResultAvailable(gpsLoc);
29                 else
30                     // last location from the Network Provider is the newest
31                     mLocationResultListener.onLocationResultAvailable(netLoc);
32                 return;
33             }
34 
35             // looks like network provider is not available
36             if (gpsLoc != null) {
37                 mLocationResultListener.onLocationResultAvailable(gpsLoc);
38                 return;
39             }
40 
41             // looks like GPS provider is not available
42             if (netLoc != null) {
43                 mLocationResultListener.onLocationResultAvailable(netLoc);
44                 return;
45             }
46 
47             mLocationResultListener.onLocationResultAvailable(null);
48         }
49     }
50 ... 

That’s all you need to create a reusable location service class that you can, if you want, use in your other projects.

We now need a button that our user can select to get the location. We will achieve this by adding a menu item to the Action Bar.

5. Under the res folder, create a new folder – menu.

6. Under the res/menu folder, create a new menu resource file – current_location.xml:

1 <!-- file: res/layout/current_location.xml -->
2 <menu xmlns:android="http://schemas.android.com/apk/res/android">
3     <item
4         android:id="@+id/find_current_location"
5         android:icon="@drawable/current_location"
6         android:showAsAction="always"/>
7 </menu>

7. Open CurrentFragment.java, override three methods – onActivityCreated(), onCreateOptionsMenu, and onOptionsItemSelected(). The last two methods are required to make the menu item work:

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/CurrentFragment.java
 2 ...
 3     @Override
 4     public void onActivityCreated(Bundle savedInstanceState) {
 5         super.onActivityCreated(savedInstanceState);
 6         setHasOptionsMenu(true);
 7     }
 8 
 9     @Override
10     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
11         inflater.inflate(R.menu.current_location, menu);
12         super.onCreateOptionsMenu(menu, inflater);
13     }
14 
15     @Override
16     public boolean onOptionsItemSelected(MenuItem item) {
17         if (item.getItemId() == R.id.find_current_location) {
18             // user wants to fetch the location
19             return true;
20         }
21         return super.onOptionsItemSelected(item);
22     }
23 ...

Inside the onOptionsItemSelected() method, we check the id of the menu item that is selected. If it is of the find_current_location menu item, we need to fetch the location. But first, run the app. You should see a GPS icon on the right hand side of the Action Bar.

8. Let’s call our location service from within the onOptionsItemSelected() method:

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/CurrentFragment.java
 2 ...
 3     @Override
 4     public boolean onOptionsItemSelected(MenuItem item) {
 5         if (item.getItemId() == R.id.find_current_location) {
 6             if (mLocationService == null)
 7                 mLocationService = new LocationService();
 8             mLocationService.getLocation(getActivity(), this);
 9             return true;
10         }
11         return super.onOptionsItemSelected(item);
12     }
13 ...

We lazily created an instance of LocationService class that we wrote earlier and call getLocation() method. We are passing this as the last parameter of getLocation() method, which means our CurrentFragment class has to implement LocationResultListener. Let’s do that.

9. Make CurrentFragment implement LocationResultListener, and override the only onLocationResultAvailable() method:

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/services/LocationService.java
 2 ...
 3     @Override
 4     public void onLocationResultAvailable(Location location) {
 5         if (location == null) {
 6              // TODO: [ASSIGNMENT]
 7         } else {
 8             // reverse geocode location
 9         }
10     }
11 ...

Getting ready to display the address:

Now that we have the location with latitude and longitude, next task is to reverse geocode this location to get the human-readable address. But first let’s add the widgets necessary to display this human-readable address. We will start by adding a layout for CurrentFragment.

10. Under res/layout folder, create a new layout resource file – current_frag.xml, and replace the contents with:

 1  <!-- file: res/layout/current_frag.xml -->
 2     <LinearLayout
 3         xmlns:android="http://schemas.android.com/apk/res/android"
 4         android:layout_width="fill_parent"
 5         android:layout_height="fill_parent"
 6         android:orientation="vertical"
 7         android:weightSum="100">        
 8         
 9         <RelativeLayout
10             android:layout_width="fill_parent"
11             android:layout_height="wrap_content"
12             android:layout_weight="50">
13             <ImageView
14                 android:id="@+id/locationIcon"
15                 android:layout_width="wrap_content"
16                 android:layout_height="wrap_content"
17                 android:src="@drawable/unknown_location"
18                 android:layout_centerVertical="true"
19                 android:layout_marginLeft="40dip"/>
20             <TextView
21                 android:id="@+id/address1"
22                 android:layout_width="wrap_content"
23                 android:layout_height="wrap_content"
24                 android:layout_toRightOf="@+id/locationIcon"
25                 android:layout_alignTop="@+id/locationIcon"
26                 android:layout_marginLeft="30dip"
27                 android:textColor="#0099cc"
28                 android:textSize="22sp"
29                 android:layout_alignParentTop="false"
30                 android:layout_marginTop="5dip"/>
31             <TextView
32                 android:id="@+id/address2"
33                 android:layout_width="wrap_content"
34                 android:layout_height="wrap_content"
35                 android:textColor="#0099cc"
36                 android:textSize="18sp"
37                 android:layout_alignLeft="@+id/address1"
38                 android:layout_below="@+id/address1"/>
39 
40             <TextView
41                 android:id="@+id/latitude"
42                 android:layout_width="wrap_content"
43                 android:layout_height="wrap_content"
44                 android:textColor="#dddddd"
45                 android:textSize="14sp"
46                 android:layout_alignLeft="@+id/address1"
47                 android:layout_below="@+id/address2"/>
48 
49             <TextView
50                 android:id="@+id/longitude"
51                 android:layout_width="wrap_content"
52                 android:layout_height="wrap_content"
53                 android:textColor="#dddddd"
54                 android:textSize="14sp"
55                 android:layout_alignLeft="@+id/address1"
56                 android:layout_below="@+id/latitude"/>
57 
58         </RelativeLayout>
59 
60         <ImageButton
61             android:id="@+id/tagButton"
62             android:layout_width="wrap_content"
63             android:layout_height="wrap_content"
64             android:layout_marginTop="0dp"
65             android:layout_weight="0"
66             android:src="@drawable/tag_button_disabled"
67             android:clickable="false"
68             android:layout_centerInParent="true"
69             android:layout_gravity="center_horizontal"/>
70 
71     </LinearLayout>
72     

There is no rocket science here. All we did was to add a bunch of text views and an image view for displaying different address fields. We also added an image button that the user, after we finish it in the next part, can select to add details and a picture for the ‘snapped’ address.

Over in CurrentFragment let’s set this layout as the root view and save pointers to all the widgets that we need to reference later.

11. Override onCreateView() method in CurrentFragment:

1 //file: src/main/java/com.ashokgelal.tagsnap/main/CurrentFragment.java
2 ...
3     @Override
4     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
5         return inflater.inflate(R.layout.current_frag, container, false);
6     }
7 ...

12. Modify onActivityCreated() method to:

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/CurrentFragment.java
 2 ... 
 3     private ImageView mLocationIcon;
 4     private ImageButton mTagButton;
 5     private TextView mAddress1;
 6     private TextView mAddress2;
 7     private TextView mLat;
 8     private TextView mLon;
 9 
10     @Override
11     public void onActivityCreated(Bundle savedInstanceState) {
12         super.onActivityCreated(savedInstanceState);
13         setHasOptionsMenu(true);
14         mLocationIcon = (ImageView) getView().findViewById(R.id.locationIcon);
15         mTagButton = (ImageButton) getView().findViewById(R.id.tagButton);
16         mAddress1 = (TextView) getView().findViewById(R.id.address1);
17         mAddress2 = (TextView) getView().findViewById(R.id.address2);
18         mLat = (TextView) getView().findViewById(R.id.latitude);
19         mLon = (TextView) getView().findViewById(R.id.longitude);
20     }
21 ... 

Run the app, the CURRENT tab should show one image, a red ‘pin’, and a grayed out image button.

Reverse Geocoding the Location:

Now that we have the widgets necessary to show the address details, let’s translate the location, which we got from the LocationService class earlier, to a human-readable address. Just like getting the location from the location providers, reverse geocoding takes some time to return you the result. You don’t want to freeze your UI thread during this time otherwise Android may kill your app thinking it is frozen. The better option is to have it extend an AsyncTask and do the geocoding task in the background. After the geocoding is done, it can call the CurrentFragment back passing the actual address (or null if it cannot geocode the location). You might have guessed it already; we are going to need another interface for this AsyncTask to be able to call back the CurrentFragment. Let’s implement that interface first.

13. Add a new interface ReverseGeocodingListener, and a public method onAddressAvailable():

1 //file: src/main/java/com.ashokgelal.tagsnap/main/listeners/ReverseGeocodingListener.java
2 public interface ReverseGeocodingListener {
3     public void onAddressAvailable(Address address);
4 }

14. Add a new class ReverseGeocodingService that extends AsyncTask<Location, Void, Void>, and override doInBackground() method:

1 //file: src/main/java/com.ashokgelal.tagsnap/main/services/ReverseGeocodingService.java
2 public class ReverseGeocodingService extends AsyncTask<Location, Void, Void> {
3     @Override
4     protected Void doInBackground(Location... locations) {
5         // do actual geocoding
6         return null;
7     }
8 }

15. Add a constructor that takes two parameters – a Context and a ReverseGeocodingListener:

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/services/ReverseGeocodingService.java
 2 ...
 3     private final ReverseGeocodingListener mListener;
 4     private final Context mContext;
 5 
 6     public ReverseGeocodingService(Context context, ReverseGeocodingListener listener) {
 7         mContext = context;
 8         mListener = listener;
 9     }
10 ...

16. Modify doInBackgroundMethod():

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/services/ReverseGeocodingService.java
 2 ...
 3     private Address mAddress;
 4 
 5     @Override
 6     protected Void doInBackground(Location... locations) {
 7         // create an instance of a Geocoder
 8         Geocoder geocoder = new Geocoder(mContext, Locale.getDefault());
 9 
10         // get the first location
11         Location loc = locations[0];
12         // a location might be associated with multiple addresses; so we need a list
13         List<Address> addresses = null;
14 
15         try {
16             // ask the Geocoder to give a list of address for the given latitude and longitude
17             // 1 means max result - we need only 1
18             addresses = geocoder.getFromLocation(loc.getLatitude(), loc.getLongitude(), 1);
19         } catch (IOException e) {
20             mAddress = null;
21         }
22 
23         // get the first address
24         if (addresses != null && addresses.size() > 0) {
25             mAddress = addresses.get(0);
26         }
27 
28         return null;
29     }
30 ...

We created a new instance of Geocoder class and get all the addresses for the given latitude and longitude. We are only interested in the first address so we set the last parameter, MaxResult, to 1. If we get back one, we save it to a field.

We cannot call onAddressAvailable() method from within doInBackground() method because this method is running on a different thread, and you cannot touch the widgets from any other threads than the one that created it — in our case the UI thread. We will do that from a different method that is guaranteed to be run on the UI thread – onPostExecute().

17. Override onPostExecute() method:

1 //file: src/main/java/com.ashokgelal.tagsnap/main/services/ReverseGeocodingService.java
2 ...
3     @Override
4     protected void onPostExecute(Void aVoid) {
5         mListener.onAddressAvailable(mAddress);
6     }
7 ...

That’s all we need to reverse geocode a location. Next task is to call this AsyncTask from within our CurrentFragment.

18. Open CurrentFragment and modify onLocationResultAvailable() method:

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/CurrentFragment.java
 2 ...
 3     @Override
 4     public void onLocationResultAvailable(Location location) {
 5         if (location == null) {
 6             // TODO: [ASSIGNMENT]
 7         } else {
 8             new ReverseGeocodingService(getActivity(), this).execute(location);
 9         }
10     }
11 ...

19. Make CurrentFragment implement ReverseGeocodingListener:

1 //file: src/main/java/com.ashokgelal.tagsnap/main/CurrentFragment.java
2 public class CurrentFragment extends SherlockFragment implements LocationResultListener, ReverseGeocodingListener {
3 
4     ...
5 }

20. Override the required onAddressAvailable() method:

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/CurrentFragment.java
 2 ...
 3     private Address mLastKnownAddress;
 4     @Override
 5     public void onAddressAvailable(Address address) {
 6       if (address == null) {
 7           // TODO: [ASSIGNMENT]
 8       } else {
 9         mLastKnownAddress = address;
10         setAddressDetails(address);
11       }
12     }
13 ...

21. Add a new method – setAddressDetails():

 1 //file: src/main/java/com.ashokgelal.tagsnap/main/CurrentFragment.java
 2 ...
 3     private void setAddressDetails(Address address) {
 4       if (address.getMaxAddressLineIndex() > 0)
 5           mAddress1.setText(address.getAddressLine(0));
 6 
 7       mAddress2.setText(String.format("%s, %s, %s", address.getLocality(), address.getAdminArea(), address.getCountryName()));
 8       Resources res = getResources();
 9       mLat.setText(String.format(res.getString(R.string.lat_val), address.getLatitude()));
10       mLon.setText(String.format(res.getString(R.string.lon_val), address.getLongitude()));
11 
12       mTagButton.setImageResource(R.drawable.tag_button);
13       mLocationIcon.setImageResource(R.drawable.known_location);
14       mTagButton.setClickable(true);
15     }
16 ...

22. Add two new string resources to res/values/strings.xml file:

1 <!-- file: res/values/strings.xml -->
2 ...
3     <string name="lat_val">Lat: %1$f </string>
4     <string name="lon_val">Lon: %1$f </string>
5 ...

Is that all we need to get the location, reverse geocode it and display the address? Run it, click the GPS menu item, and see it yourself. Not quite! You will get an exception. You need to get permission to use any protected Android features such as locations, reverse geocoding (you actually don’t need permission to use reverse geocoding, but you do need Internet to perform geocoding, so you need permission to use Internet instead). You do so by adding <uses-permission> tag for each of the features that your app requires in the app’s AndroidManifest.xml file.

23. Open AndroidManifest.xml file, and add the following tags right after the tag:

1 <!-- file: AndroidManifest.xml -->
2 ...
3     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
4     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
5     <uses-permission android:name="android.permission.INTERNET"/>
6 ... 

Run your app again, click the GPS menu item, and wait few seconds to see your current address being displayed. If you are using an emulator for debugging then it might not work. I never got reverse geocoding to work on an Android emulator.

Saving and Restoring the Address on Config Changes:

Similar to the selected tab index problem we had in the previous part, the CURRENT tab’s widget values reset back when you change the orientation mode. Try it. We are going to fix this problem using the same technique we used before – saving the current address in onSaveInstanceState() callback and restoring it from onActivityCreated() method. The Address class implements android.os.Parcelable making it easier to put it in a Bundle.

24. Override onSaveInstanceState() method:

1 //file: src/main/java/com.ashokgelal.tagsnap/main/CurrentFragment.java
2 ...
3     @Override
4     public void onSaveInstanceState(Bundle outState) {
5         super.onSaveInstanceState(outState);
6         if(mLastKnownAddress != null)
7             outState.putParcelable("last_known_address", mLastKnownAddress);
8     }
9 ...

25. Now to restore the address, append following lines at the end of the onActivityCreated() method:

1 //file: src/main/java/com.ashokgelal.tagsnap/main/CurrentFragment.java
2 ...
3   ...
4       if (savedInstanceState != null)
5         mLastKnownAddress = savedInstanceState.getParcelable("last_known_address");
6       if(mLastKnownAddress != null)
7         setAddressDetails(mLastKnownAddress);
8   ...
9 ...

We have one final task remaining in CurrentFragment –- hooking up the big TagButton to take the user to a screen for adding a description, select category, and attach a picture. We already had enough to do in this part, so we will hook up the button as well as add the details screen in the next part of this series.

Assignments:

1. I’ve left three TODOs in the code above all of which are about notifying the user when we don’t get the location or the human-readable address. Try to implement these on your own. To start with you can use a Toast, or even better, an AlertDialog.

2. After the user selects the GPS button from the Action Bar, the app doesn’t give any feedback about fetching the location and reverse geocoding it in the background. Use a ProgressDialog, or even better, a ProgressBar to notify user about the on going operation.

We did a handful of coding today but don’t let the total lines of code intimidate you. If you think about it, we not only displayed reverse geocoded address but also ended up writing a reusable LocationService class that you can use in other projects.

In the next part, we will allow our user to add details. Among others, we will learn how to capture a picture from a device camera, as well as how to pick up a saved picture from the gallery. We will also learn how to modify our Action Bar by replacing its content with a completely new custom view.

You can follow me on Twitter, or add me on Google+.

See you in the next part.  

Discussion, links, and tweets

By day, I ship code at MetaGeek, by night, I hack on my personal projects, and finally, when I get some off time in between, I also serve as a CTO for ClockworkEngine, LLC where so far we have launched two products - Spyglass and LightPaper. Call be a serial coder if you want.

comments powered by Disqus