Writing a Real Android App from Scratch: Part 3/9 – GPS, LocationManager and Geocoding
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:
Read previous parts of this series:
Read the official Android tutorial on making an app location aware.
Do some readings on LocationManager.
Clone this project from GitHub; start with the
v0.2
tag:git checkout –b current_tab v0.2
Plan of Action:
- Write a reusable
LocationService
class that runs on a timer and calls us back as and when current location is available. - Add a menu item to the Action Bar for initiating a request to fetch the current location.
- Add required widgets to CURRENT fragment for displaying current address, and a button that will (in the next part) bring up another view for adding details and a picture.
- After a location is available, translate the latitude and longitude coordinates to human-readable address.
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
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.