From c4b7a953f977c6e95a937cbc074970262f3e5e49 Mon Sep 17 00:00:00 2001 From: "gu.martinm@gmail.com" Date: Fri, 5 Sep 2014 19:17:03 +0200 Subject: [PATCH] WeatherInformation Android: MapActivity using GoogleApiClient.Builder --- .../src/de/example/exampletdd/MapActivity.java | 253 +++++++++++++-------- .../gms/GPlayServicesErrorDialogFragment.java | 15 +- 2 files changed, 169 insertions(+), 99 deletions(-) diff --git a/Android/WeatherInformation/src/de/example/exampletdd/MapActivity.java b/Android/WeatherInformation/src/de/example/exampletdd/MapActivity.java index f273d1f..02e70a7 100644 --- a/Android/WeatherInformation/src/de/example/exampletdd/MapActivity.java +++ b/Android/WeatherInformation/src/de/example/exampletdd/MapActivity.java @@ -9,7 +9,7 @@ import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.Intent; -import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; import android.location.Address; import android.location.Geocoder; import android.location.Location; @@ -24,9 +24,12 @@ import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GooglePlayServicesClient; import com.google.android.gms.common.GooglePlayServicesUtil; -import com.google.android.gms.location.LocationClient; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.location.FusedLocationProviderApi; +import com.google.android.gms.location.LocationListener; +import com.google.android.gms.location.LocationRequest; +import com.google.android.gms.location.LocationServices; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener; @@ -42,44 +45,49 @@ import de.example.exampletdd.model.WeatherLocation; import de.example.exampletdd.model.WeatherLocationDbHelper; import de.example.exampletdd.model.WeatherLocationDbQueries; -/** - * {@link http://developer.android.com/training/location/retrieve-current.html} - * - */ public class MapActivity extends FragmentActivity implements - GooglePlayServicesClient.ConnectionCallbacks, - GooglePlayServicesClient.OnConnectionFailedListener { + GoogleApiClient.ConnectionCallbacks, + GoogleApiClient.OnConnectionFailedListener, + LocationListener { private static final String TAG = "MapActivity"; - private static final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000; + // Request code to use when launching the resolution activity + private static final int REQUEST_RESOLVE_ERROR = 1001; private WeatherLocation mRestoreUI; // Google Play Services Map private GoogleMap mMap; - // TODO: read and store from different threads + // TODO: read and store from different threads? Hopefully always from UI thread. private Marker mMarker; // Google Play Services Location - // Stores the current instantiation of the location client in this object - private LocationClient mLocationClient; + private GoogleApiClient mGoogleApiClient; + // Boolean to track whether the app is already resolving an error + // TODO: Store/read from UI thread? + private boolean mResolvingError; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.weather_map); - // Google Play Services Location - /* - * Create a new location client, using the enclosing class to - * handle callbacks. - */ - mLocationClient = new LocationClient(this, this, this); - + // Google Play Services + // Restore resolving error (for example if user rotates the screen) + // It must be done (I guess) before onRestoreInstanceState because its value + // is used by onStart method. + if (savedInstanceState != null) { + mResolvingError = savedInstanceState.getBoolean("mResolvingError", false); + } + // Google Play Services Location + mGoogleApiClient = new GoogleApiClient.Builder(this) + .addApi(LocationServices.API) + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .build(); // Google Play Services Map final MapFragment mapFragment = (MapFragment) this.getFragmentManager() .findFragmentById(R.id.map); - this.mMap = mapFragment.getMap(); this.mMap.setMyLocationEnabled(false); this.mMap.getUiSettings().setCompassEnabled(false); @@ -141,6 +149,12 @@ public class MapActivity extends FragmentActivity implements build(); savedInstanceState.putSerializable("WeatherLocation", location); } + + // Google Play Services + // To keep track of the boolean across activity restarts (such as when + // the user rotates the screen), save the boolean in the activity's saved + // instance data using onSaveInstanceState(): + savedInstanceState.putBoolean("mResolvingError", this.mResolvingError); super.onSaveInstanceState(savedInstanceState); } @@ -150,23 +164,17 @@ public class MapActivity extends FragmentActivity implements } public void onClickGetLocation(final View v) { + // TODO: Somehow I should show a progress dialog. // If Google Play Services is available if (servicesConnected()) { - if (mLocationClient.isConnected()) { - // Get the current location - // TODO: DO NOT USE IT!!! http://stackoverflow.com/questions/17265722/locationclient-does-not-update-getlastlocation-on-emulator - // USE THIS INSTEAD: http://developer.android.com/reference/com/google/android/gms/location/LocationClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest, com.google.android.gms.location.LocationListener) - final Location currentLocation = mLocationClient.getLastLocation(); - - if (currentLocation != null) { - // Display the current location in the UI - this.getAddressAndUpdateUI(currentLocation.getLatitude(), currentLocation.getLongitude()); - } else { - // Location is not available - // TODO: string resource - Toast.makeText(this, "Location is not available.", Toast.LENGTH_LONG).show(); - } + if (this.mGoogleApiClient.isConnected()) { + // TODO: Hopefully there will be results even if location did not change... + final LocationRequest locationRequest = LocationRequest.create(); + locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); + locationRequest.setInterval(1000); + final FusedLocationProviderApi fusedLocationApi = LocationServices.FusedLocationApi; + fusedLocationApi.requestLocationUpdates(mGoogleApiClient, locationRequest, this); } else { // TODO: string resource Toast.makeText(this, "You are not yet connected to Google Play Services.", Toast.LENGTH_LONG).show(); @@ -275,10 +283,10 @@ public class MapActivity extends FragmentActivity implements } } - return new WeatherLocation.Builder(). - setLatitude(latitude).setLongitude(longitude). - setCity(city).setCountry(country). - build(); + return new WeatherLocation.Builder() + .setLatitude(latitude).setLongitude(longitude) + .setCity(city).setCountry(country) + .build(); } } @@ -299,7 +307,11 @@ public class MapActivity extends FragmentActivity implements } - /************************ Google Play Services ************************/ + /***************************************************************************************************** + * + * Google Play Services + * + * ***************************************************************************************************/ /** * Getting the address of the current location, using reverse geocoding only works if @@ -310,27 +322,24 @@ public class MapActivity extends FragmentActivity implements // In Gingerbread and later, use Geocoder.isPresent() to see if a geocoder is available. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && Geocoder.isPresent()) { - if (servicesConnected()) { - - // Start the background task - final GetAddressTask geocoderAsyncTask = new GetAddressTask(this); - geocoderAsyncTask.execute(latitude, longitude); - } + // Start the background task + final GetAddressTask geocoderAsyncTask = new GetAddressTask(this); + geocoderAsyncTask.execute(latitude, longitude); } else { // No geocoder is present. Issue an error message. // TODO: string resource Toast.makeText(this, "Cannot get address. No geocoder available.", Toast.LENGTH_LONG).show(); - } - - // Default values - final String city = this.getString(R.string.city_not_found); - final String country = this.getString(R.string.country_not_found); - final WeatherLocation weatherLocation = new WeatherLocation.Builder(). - setLatitude(latitude).setLongitude(longitude). - setCity(city).setCountry(country). - build(); - - updateUI(weatherLocation); + + // Default values + final String city = this.getString(R.string.city_not_found); + final String country = this.getString(R.string.country_not_found); + final WeatherLocation weatherLocation = new WeatherLocation.Builder(). + setLatitude(latitude).setLongitude(longitude). + setCity(city).setCountry(country). + build(); + + updateUI(weatherLocation); + } } /** @@ -355,7 +364,7 @@ public class MapActivity extends FragmentActivity implements // Display an error dialog final Dialog dialog = GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0); if (dialog != null) { - final GPlayServicesErrorDialogFragment errorFragment = new GPlayServicesErrorDialogFragment(); + final GPlayServicesErrorDialogFragment errorFragment = new GPlayServicesErrorDialogFragment(this); errorFragment.setDialog(dialog); errorFragment.show(getSupportFragmentManager(), TAG); } @@ -363,7 +372,14 @@ public class MapActivity extends FragmentActivity implements } } - /************************ Google Play Services Location ************************/ + /***************************************************************************************************** + * + * Google Play Services + * + * GoogleApiClient.ConnectionCallbacks + * GoogleApiClient.OnConnectionFailedListener + * + * ***************************************************************************************************/ /* * Called when the Activity is started/restarted, even before it becomes visible. @@ -376,7 +392,9 @@ public class MapActivity extends FragmentActivity implements * Connect the client. Don't re-start any requests here; * instead, wait for onResume() */ - mLocationClient.connect(); + if (!mResolvingError) { + mGoogleApiClient.connect(); + } } /* @@ -388,11 +406,20 @@ public class MapActivity extends FragmentActivity implements // Disconnecting the client invalidates it. // After disconnect() is called, the client is considered "dead". - mLocationClient.disconnect(); + mGoogleApiClient.disconnect(); super.onStop(); } + @Override + public void onConnectionSuspended(int cause) { + // The connection has been interrupted. + // Disable any UI components that depend on Google APIs + // until onConnected() is called. + + // TODO: What to do here? The same as onDisconnected? + } + @Override public void onConnected(final Bundle bundle) { // TODO: Why should some user care about this? Could I skip this message? @@ -400,43 +427,43 @@ public class MapActivity extends FragmentActivity implements Toast.makeText(this, "Connected to Google Play Services.", Toast.LENGTH_SHORT).show(); } - @Override - public void onDisconnected() { - // TODO: What should the user do in this case? Perhaps this method is called after onStop() by mLocationClient.disconnect() - // TODO: string resource - Toast.makeText(this, "Client disconnected from Google Play Services.", Toast.LENGTH_SHORT).show(); - } - /* * Called by Location Services if the attempt to * Location Services fails. */ @Override - public void onConnectionFailed(final ConnectionResult connectionResult) { + public void onConnectionFailed(final ConnectionResult result) { + // TODO: Hopefully being called from UI see onDialogDismissed for concerns... /* * Google Play services can resolve some errors it detects. * If the error has a resolution, try sending an Intent to * start a Google Play services activity that can resolve * error. */ - if (connectionResult.hasResolution()) { + if (mResolvingError) { + // Already attempting to resolve an error. + return; + } else if (result.hasResolution()) { + mResolvingError = true; try { // Start an Activity that tries to resolve the error - connectionResult.startResolutionForResult( - this, CONNECTION_FAILURE_RESOLUTION_REQUEST); + result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR); /* * Thrown if Google Play services canceled the original * PendingIntent */ - } catch (final IntentSender.SendIntentException e) { - // Log the error + } catch (final SendIntentException e) { + // There was an error with the resolution intent. Try again. + mGoogleApiClient.connect(); } } else { + // Show dialog using GooglePlayServicesUtil.getErrorDialog() /* * If no resolution is available, display a dialog to the * user with the error. */ - showErrorDialog(connectionResult.getErrorCode()); + showErrorDialog(result.getErrorCode()); + mResolvingError = true; } } @@ -454,27 +481,32 @@ public class MapActivity extends FragmentActivity implements switch (requestCode) { // If the request code matches the code sent in onConnectionFailed - case CONNECTION_FAILURE_RESOLUTION_REQUEST : + case REQUEST_RESOLVE_ERROR : + mResolvingError = false; switch (resultCode) { - // If Google Play services resolved the problem - case Activity.RESULT_OK: - // Log the result - Log.d(TAG, "Error resolved. Please re-try operation"); + // If Google Play services resolved the problem + case Activity.RESULT_OK: + // Log the result + Log.d(TAG, "Error resolved. Please re-try operation"); - // TODO: Should I call mLocationClient.connect() ? I GUESS SO!!!! - // TODO: Display the result - //mConnectionState.setText("Client connected"); - //mConnectionStatus.setText("Error resolved. Please re-try operation."); - break; - // If any other result was returned by Google Play services - default: - // Log the result - Log.d(TAG, "Google Play services: unable to resolve connection error."); - - // TODO: Display the result - //mConnectionState.setText("Client disconnected"; - //mConnectionStatus.setText("Google Play services: unable to resolve connection error."); - break; + // Make sure the app is not already connected or attempting to connect + if (!mGoogleApiClient.isConnecting() && !mGoogleApiClient.isConnected()) { + mGoogleApiClient.connect(); + } + + // TODO: Display the result? + //mConnectionState.setText("Client connected"); + //mConnectionStatus.setText("Error resolved. Please re-try operation."); + break; + // If any other result was returned by Google Play services + default: + // Log the result + Log.d(TAG, "Google Play services: unable to resolve connection error."); + + // TODO: Display the result? + //mConnectionState.setText("Client disconnected"; + //mConnectionStatus.setText("Google Play services: unable to resolve connection error."); + break; } // If any other request code was received default: @@ -489,19 +521,46 @@ public class MapActivity extends FragmentActivity implements // Get the error dialog from Google Play services final Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( - errorCode, this, CONNECTION_FAILURE_RESOLUTION_REQUEST); + errorCode, this, REQUEST_RESOLVE_ERROR); // If Google Play services can provide an error dialog if (errorDialog != null) { // Create a new DialogFragment in which to show the error dialog - final GPlayServicesErrorDialogFragment GPSErrorFragment = new GPlayServicesErrorDialogFragment(); + final GPlayServicesErrorDialogFragment GPSErrorFragment = new GPlayServicesErrorDialogFragment(this); // Set the dialog in the DialogFragment GPSErrorFragment.setDialog(errorDialog); // Show the error dialog in the DialogFragment - GPSErrorFragment.show(this.getSupportFragmentManager(), TAG); + GPSErrorFragment.show(this.getSupportFragmentManager(), "GPlayServicesErrorDialog"); } } + + /* Called from GPlayServicesErrorDialogFragment when the dialog is dismissed. */ + public void onDialogDismissed() { + mResolvingError = false; + } + + /***************************************************************************************************** + * + * Google Play Services LocationListener + * + *****************************************************************************************************/ + @Override + public void onLocationChanged(final Location location) { + // It was called from onClickGetLocation (UI thread) This method will run in the same thread (the UI thread) + // so, I do no think there should be any problem. + + if (mGoogleApiClient.isConnected()) { + final FusedLocationProviderApi fusedLocationApi = LocationServices.FusedLocationApi; + // TODO: if user clicks many times onClickGetLocation I may not assure how many times + // onLocationChanged will be called. Is it a problem? AFAIK it isn't. + fusedLocationApi.removeLocationUpdates(mGoogleApiClient, this); + } + + // Display the current location in the UI + // TODO: May location not be null? + this.getAddressAndUpdateUI(location.getLatitude(), location.getLongitude()); + } } diff --git a/Android/WeatherInformation/src/de/example/exampletdd/gms/GPlayServicesErrorDialogFragment.java b/Android/WeatherInformation/src/de/example/exampletdd/gms/GPlayServicesErrorDialogFragment.java index 0d76616..4c88516 100644 --- a/Android/WeatherInformation/src/de/example/exampletdd/gms/GPlayServicesErrorDialogFragment.java +++ b/Android/WeatherInformation/src/de/example/exampletdd/gms/GPlayServicesErrorDialogFragment.java @@ -1,6 +1,9 @@ package de.example.exampletdd.gms; +import de.example.exampletdd.MapActivity; import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; import android.os.Bundle; import android.support.v4.app.DialogFragment; @@ -9,14 +12,15 @@ import android.support.v4.app.DialogFragment; * showErrorDialog. */ public class GPlayServicesErrorDialogFragment extends DialogFragment { - + private final Context localContext; // Global field to contain the error dialog private Dialog mDialog; /** * Default constructor. Sets the dialog field to null */ - public GPlayServicesErrorDialogFragment() { + public GPlayServicesErrorDialogFragment(final Context context) { + this.localContext = context; mDialog = null; } @@ -36,4 +40,11 @@ public class GPlayServicesErrorDialogFragment extends DialogFragment { public Dialog onCreateDialog(final Bundle savedInstanceState) { return mDialog; } + + @Override + public void onDismiss(DialogInterface dialog) { + // TODO: Hopefully being called from UI see onConnectionFailed for concerns... + final MapActivity activity = (MapActivity) this.localContext; + activity.onDialogDismissed(); + } } -- 2.1.4