Android – Location Update

Android – Location Update
Esta lição é baseada no exemplo Android Play Location que demonstra como usar a Fused Location Provider API para obter atualizações sobre a localização de um dispositivo. Ele fica localizada no Github no repositório do Google Samples (https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates). Alguns pontos desse exemplo foram explicados com maiores detalhe a fim de facilitar o entedimento.
1- Crie um projeto Empty novo chamado ExemploLocationUpdate com uma classe chamada MainActivity.
2- Adicione as dependências abaixo
implementation 'com.android.support:design:28.0.0' implementation 'com.google.android.gms:play-services-location:16.0.0'
3- Adicione um RecyclerView ao arquivo de layout criado
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:baselineAligned="false" android:orientation="horizontal"> <Button android:id="@+id/start_updates_button" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="10dp" android:layout_marginRight="10dp" android:layout_weight="1" android:gravity="center" android:onClick="startUpdatesButtonHandler" android:text="@string/start_updates" /> <Button android:id="@+id/stop_updates_button" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/small_margin" android:layout_marginStart="@dimen/small_margin" android:layout_weight="1" android:enabled="false" android:gravity="center" android:onClick="stopUpdatesButtonHandler" android:text="@string/stop_updates" /> </LinearLayout> <TextView android:id="@+id/latitude_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/small_margin" android:layout_marginStart="@dimen/small_margin" android:textSize="@dimen/default_text_size" /> <TextView android:id="@+id/longitude_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/small_margin" android:layout_marginStart="@dimen/small_margin" android:textSize="@dimen/default_text_size" /> <TextView android:id="@+id/last_update_time_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/small_margin" android:layout_marginStart="@dimen/small_margin" android:textSize="@dimen/default_text_size" /> <TextView android:id="@+id/location_inadequate_warning" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/location_settings_inadequate_warning" android:visibility="invisible" /> </LinearLayout> </android.support.constraint.ConstraintLayout>
No clique do botão START UPDATE o aplicativo começará a requisitar a localização atual e exibir as coordenadas abaixo. O botão STOP UPDATE interrompe o serviço.
4- Na classe MainActivity.java copie e cole o código abaixo.
Explicação 1: O método buildLocationSettingsRequest() cria um objeto LocationSettingsRequest que é usado para verificar se um dispositivo possui as configurações de localização necessárias.
Explicação 2: O método createLocationRequest() cria uma solicitação de localização. Os objetos LocationRequest são usados para solicitar uma qualidade de serviço para atualizações de local do FusedLocationProviderApi. As configurações ACCESS_COARSE_LOCATION e ACCESS_FINE_LOCATION controlam a precisão da localização atual. Quando a configuração ACCESS_FINE_LOCATION é especificada a API do provedor de local retorna as atualizações de local com precisão de alguns pés. A configuração ACCESS_COARSE_LOCATION retorna uma precisão equivalente a uma cidade.
Use o método setinterval () para especificar o intervalo no qual o local é calculado para seu aplicativo.
Use o setFastestInterval () para especificar o intervalo no qual o local computado para outros aplicativos é entregue ao seu aplicativo.
Use o método setPriority () para especificar a precisão da localização passando um dos seguintes valores como o argumento:
PRIORITY_HIGH_ACCURACY fornece a localização mais precisa possível
PRIORITY_BALANCED_POWER_ACCURACY fornece uma localização precisa enquanto otimiza o poder
O PRIORITY_LOW_POWER depende em grande parte das torres de celular e evita entradas GPS e Wi-Fi, fornecendo precisão aproximada (no nível da cidade) com consumo mínimo de bateria.
PRIORITY_NO_POWER recebe locais passivamente de outros aplicativos para os quais a localização já foi calculada.
Explicação 3: O método createLocationCallback() cria um objeto LocationCallback para receber eventos de retorno de chamadas de localização. Toda vez que uma localização for informada para a aplicação esse obejeto receberá a localização e irá guardar a localização em um objeto do tipo Location que representa a localização geográfica.
Explicação 4: o método startLocationUpdates() solicita atualizações de localização do FusedLocationApi. Um obejto FusedLocationProviderClient é criado e o método requestLocationUpdates() é responsável por fazer a solicitação de atualização de localização. Esse método não é chamado a menos que a permissão de tempo de execução da localização tenha sido concedida.
Explicação 5: o método startLocationUpdates() remove as solicitaçãoes de localização do FusedLocationApi. Um obejto FusedLocationProviderClient é criado e o método removeLocationUpdates() é responsável por remover as solicitação de atualização de localização.
Explicação 6: O método checkPermissions() verifica se tem permissão de acessar localização, o método requestPermissions() solicita a permissão caso não tenha e o método onRequestPermissionsResult() recebe a retorno da solicitação de permissão.
Explicação 7: o método updateLocationUI() atualiza as informações de localização na tela.
package com.example.totemarcal.exemplolocationupdate; import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.os.Looper; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import android.widget.Toolbar; import com.google.android.gms.common.api.ApiException; import com.google.android.gms.common.api.ResolvableApiException; import com.google.android.gms.location.FusedLocationProviderClient; import com.google.android.gms.location.LocationCallback; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationResult; import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.location.LocationSettingsResponse; import com.google.android.gms.location.LocationSettingsStatusCodes; import com.google.android.gms.location.SettingsClient; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; import com.google.android.gms.tasks.Task; import java.text.DateFormat; import java.util.Date; import java.util.Locale; /** * Using location settings. * <p/> * Uses the {@link com.google.android.gms.location.SettingsApi} to ensure that the device's system * settings are properly configured for the app's location needs. When making a request to * Location services, the device's system settings may be in a state that prevents the app from * obtaining the location data that it needs. For example, GPS or Wi-Fi scanning may be switched * off. The {@code SettingsApi} makes it possible to determine if a device's system settings are * adequate for the location request, and to optionally invoke a dialog that allows the user to * enable the necessary settings. * <p/> * This sample allows the user to request location updates using the ACCESS_FINE_LOCATION setting * (as specified in AndroidManifest.xml). */ public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); /** * Code used in requesting runtime permissions. */ private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34; /** * Constant used in the location settings dialog. */ private static final int REQUEST_CHECK_SETTINGS = 0x1; /** * The desired interval for location updates. Inexact. Updates may be more or less frequent. */ private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000; /** * The fastest rate for active location updates. Exact. Updates will never be more frequent * than this value. */ private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2; // Keys for storing activity state in the Bundle. private final static String KEY_REQUESTING_LOCATION_UPDATES = "requesting-location-updates"; private final static String KEY_LOCATION = "location"; private final static String KEY_LAST_UPDATED_TIME_STRING = "last-updated-time-string"; /** * Provides access to the Fused Location Provider API. */ private FusedLocationProviderClient mFusedLocationClient; /** * Provides access to the Location Settings API. */ private SettingsClient mSettingsClient; /** * Stores parameters for requests to the FusedLocationProviderApi. */ private LocationRequest mLocationRequest; /** * Stores the types of location services the client is interested in using. Used for checking * settings to determine if the device has optimal location settings. */ private LocationSettingsRequest mLocationSettingsRequest; /** * Callback for Location events. */ private LocationCallback mLocationCallback; /** * Represents a geographical location. */ private Location mCurrentLocation; // UI Widgets. private Button mStartUpdatesButton; private Button mStopUpdatesButton; private TextView mLastUpdateTimeTextView; private TextView mLatitudeTextView; private TextView mLongitudeTextView; // Labels. private String mLatitudeLabel; private String mLongitudeLabel; private String mLastUpdateTimeLabel; /** * Tracks the status of the location updates request. Value changes when the user presses the * Start Updates and Stop Updates buttons. */ private Boolean mRequestingLocationUpdates; /** * Time when the location was updated represented as a String. */ private String mLastUpdateTime; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Locate the UI widgets. mStartUpdatesButton = (Button) findViewById(R.id.start_updates_button); mStopUpdatesButton = (Button) findViewById(R.id.stop_updates_button); mLatitudeTextView = (TextView) findViewById(R.id.latitude_text); mLongitudeTextView = (TextView) findViewById(R.id.longitude_text); mLastUpdateTimeTextView = (TextView) findViewById(R.id.last_update_time_text); // Set labels. mLatitudeLabel = getResources().getString(R.string.latitude_label); mLongitudeLabel = getResources().getString(R.string.longitude_label); mLastUpdateTimeLabel = getResources().getString(R.string.last_update_time_label); mRequestingLocationUpdates = false; mLastUpdateTime = ""; // Update values using data stored in the Bundle. updateValuesFromBundle(savedInstanceState); mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this); mSettingsClient = LocationServices.getSettingsClient(this); // Kick off the process of building the LocationCallback, LocationRequest, and // LocationSettingsRequest objects. createLocationCallback(); createLocationRequest(); buildLocationSettingsRequest(); } /** * Updates fields based on data stored in the bundle. * * @param savedInstanceState The activity state saved in the Bundle. */ private void updateValuesFromBundle(Bundle savedInstanceState) { if (savedInstanceState != null) { // Update the value of mRequestingLocationUpdates from the Bundle, and make sure that // the Start Updates and Stop Updates buttons are correctly enabled or disabled. if (savedInstanceState.keySet().contains(KEY_REQUESTING_LOCATION_UPDATES)) { mRequestingLocationUpdates = savedInstanceState.getBoolean( KEY_REQUESTING_LOCATION_UPDATES); } // Update the value of mCurrentLocation from the Bundle and update the UI to show the // correct latitude and longitude. if (savedInstanceState.keySet().contains(KEY_LOCATION)) { // Since KEY_LOCATION was found in the Bundle, we can be sure that mCurrentLocation // is not null. mCurrentLocation = savedInstanceState.getParcelable(KEY_LOCATION); } // Update the value of mLastUpdateTime from the Bundle and update the UI. if (savedInstanceState.keySet().contains(KEY_LAST_UPDATED_TIME_STRING)) { mLastUpdateTime = savedInstanceState.getString(KEY_LAST_UPDATED_TIME_STRING); } updateUI(); } } /** * Sets up the location request. Android has two location request settings: * {@code ACCESS_COARSE_LOCATION} and {@code ACCESS_FINE_LOCATION}. These settings control * the accuracy of the current location. This sample uses ACCESS_FINE_LOCATION, as defined in * the AndroidManifest.xml. * <p/> * When the ACCESS_FINE_LOCATION setting is specified, combined with a fast update * interval (5 seconds), the Fused Location Provider API returns location updates that are * accurate to within a few feet. * <p/> * These settings are appropriate for mapping applications that show real-time location * updates. */ private void createLocationRequest() { mLocationRequest = new LocationRequest(); // Sets the desired interval for active location updates. This interval is // inexact. You may not receive updates at all if no location sources are available, or // you may receive them slower than requested. You may also receive updates faster than // requested if other applications are requesting location at a faster interval. mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS); // Sets the fastest rate for active location updates. This interval is exact, and your // application will never receive updates faster than this value. mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); } /** * Creates a callback for receiving location events. */ private void createLocationCallback() { mLocationCallback = new LocationCallback() { @Override public void onLocationResult(LocationResult locationResult) { super.onLocationResult(locationResult); mCurrentLocation = locationResult.getLastLocation(); mLastUpdateTime = DateFormat.getTimeInstance().format(new Date()); updateLocationUI(); } }; } /** * Uses a {@link com.google.android.gms.location.LocationSettingsRequest.Builder} to build * a {@link com.google.android.gms.location.LocationSettingsRequest} that is used for checking * if a device has the needed location settings. */ private void buildLocationSettingsRequest() { LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder(); builder.addLocationRequest(mLocationRequest); mLocationSettingsRequest = builder.build(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { // Check for the integer request code originally supplied to startResolutionForResult(). case REQUEST_CHECK_SETTINGS: switch (resultCode) { case Activity.RESULT_OK: Log.i(TAG, "O usuário concordou em fazer as alterações nas configurações de localização necessárias."); // Nothing to do. startLocationupdates() gets called in onResume again. break; case Activity.RESULT_CANCELED: Log.i(TAG, "O usuário optou por não fazer as alterações necessárias nas configurações de localização."); mRequestingLocationUpdates = false; updateUI(); break; } break; } } /** * Handles the Start Updates button and requests start of location updates. Does nothing if * updates have already been requested. */ public void startUpdatesButtonHandler(View view) { if (!mRequestingLocationUpdates) { mRequestingLocationUpdates = true; setButtonsEnabledState(); startLocationUpdates(); } } /** * Handles the Stop Updates button, and requests removal of location updates. */ public void stopUpdatesButtonHandler(View view) { // It is a good practice to remove location requests when the activity is in a paused or // stopped state. Doing so helps battery performance and is especially // recommended in applications that request frequent location updates. stopLocationUpdates(); } /** * Requests location updates from the FusedLocationApi. Note: we don't call this unless location * runtime permission has been granted. */ private void startLocationUpdates() { // Begin by checking if the device has the necessary location settings. mSettingsClient.checkLocationSettings(mLocationSettingsRequest) .addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() { @Override public void onSuccess(LocationSettingsResponse locationSettingsResponse) { Log.i(TAG, "All location settings are satisfied."); //noinspection MissingPermission if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return; } mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, null); updateUI(); } }) .addOnFailureListener(this, new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { int statusCode = ((ApiException) e).getStatusCode(); switch (statusCode) { case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: Log.i(TAG, "As configurações de localização não estão satisfeitas. Tentando atualizar configurações de localização "); try { // Show the dialog by calling startResolutionForResult(), and check the // result in onActivityResult(). ResolvableApiException rae = (ResolvableApiException) e; rae.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS); } catch (IntentSender.SendIntentException sie) { Log.i(TAG, "PendingIntent unable to execute request."); } break; case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: String errorMessage = "As configurações de localização são inadequadas e não podem ser corrigidas aqui. Corrigir em configurações."; Log.e(TAG, errorMessage); Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show(); mRequestingLocationUpdates = false; } updateUI(); } }); } /** * Updates all UI fields. */ private void updateUI() { setButtonsEnabledState(); updateLocationUI(); } /** * Disables both buttons when functionality is disabled due to insuffucient location settings. * Otherwise ensures that only one button is enabled at any time. The Start Updates button is * enabled if the user is not requesting location updates. The Stop Updates button is enabled * if the user is requesting location updates. */ private void setButtonsEnabledState() { if (mRequestingLocationUpdates) { mStartUpdatesButton.setEnabled(false); mStopUpdatesButton.setEnabled(true); } else { mStartUpdatesButton.setEnabled(true); mStopUpdatesButton.setEnabled(false); } } /** * Sets the value of the UI fields for the location latitude, longitude and last update time. */ private void updateLocationUI() { if (mCurrentLocation != null) { mLatitudeTextView.setText(String.format(Locale.ENGLISH, "%s: %f", mLatitudeLabel, mCurrentLocation.getLatitude())); mLongitudeTextView.setText(String.format(Locale.ENGLISH, "%s: %f", mLongitudeLabel, mCurrentLocation.getLongitude())); mLastUpdateTimeTextView.setText(String.format(Locale.ENGLISH, "%s: %s", mLastUpdateTimeLabel, mLastUpdateTime)); } } /** * Removes location updates from the FusedLocationApi. */ private void stopLocationUpdates() { if (!mRequestingLocationUpdates) { Log.d(TAG, "stopLocationUpdates: atualizações nunca solicitadas, no-op."); return; } // It is a good practice to remove location requests when the activity is in a paused or // stopped state. Doing so helps battery performance and is especially // recommended in applications that request frequent location updates. mFusedLocationClient.removeLocationUpdates(mLocationCallback) .addOnCompleteListener(this, new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { mRequestingLocationUpdates = false; setButtonsEnabledState(); } }); } @Override public void onResume() { super.onResume(); // Within {@code onPause()}, we remove location updates. Here, we resume receiving // location updates if the user has requested them. if (mRequestingLocationUpdates && checkPermissions()) { startLocationUpdates(); } else if (!checkPermissions()) { requestPermissions(); } updateUI(); } @Override protected void onPause() { super.onPause(); // Remove location updates to save battery. stopLocationUpdates(); } /** * Stores activity data in the Bundle. */ public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putBoolean(KEY_REQUESTING_LOCATION_UPDATES, mRequestingLocationUpdates); savedInstanceState.putParcelable(KEY_LOCATION, mCurrentLocation); savedInstanceState.putString(KEY_LAST_UPDATED_TIME_STRING, mLastUpdateTime); super.onSaveInstanceState(savedInstanceState); } /** * Shows a {@link Snackbar}. * * @param mainTextStringId The id for the string resource for the Snackbar text. * @param actionStringId The text of the action item. * @param listener The listener associated with the Snackbar action. */ private void showSnackbar(final int mainTextStringId, final int actionStringId, View.OnClickListener listener) { Snackbar.make( findViewById(android.R.id.content), getString(mainTextStringId), Snackbar.LENGTH_INDEFINITE) .setAction(getString(actionStringId), listener).show(); } /** * Return the current state of the permissions needed. */ private boolean checkPermissions() { int permissionState = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION); return permissionState == PackageManager.PERMISSION_GRANTED; } private void requestPermissions() { boolean shouldProvideRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION); // Provide an additional rationale to the user. This would happen if the user denied the // request previously, but didn't check the "Don't ask again" checkbox. if (shouldProvideRationale) { Log.i(TAG, "Exibindo justificativa de permissão para fornecer contexto adicional."); showSnackbar(R.string.permission_rationale, android.R.string.ok, new View.OnClickListener() { @Override public void onClick(View view) { // Request permission ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSIONS_REQUEST_CODE); } }); } else { Log.i(TAG, "Requesting permission"); // Request permission. It's possible this can be auto answered if device policy // sets the permission in a given state or the user denied the permission // previously and checked "Never ask again". ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSIONS_REQUEST_CODE); } } /** * Callback received when a permissions request has been completed. */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { Log.i(TAG, "onRequestPermissionResult"); if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) { if (grantResults.length <= 0) { // If user interaction was interrupted, the permission request is cancelled and you // receive empty arrays. Log.i(TAG, "User interaction was cancelled."); } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (mRequestingLocationUpdates) { Log.i(TAG, "Permission granted, updates requested, starting location updates"); startLocationUpdates(); } } else { // Permission denied. // Notify the user via a SnackBar that they have rejected a core permission for the // app, which makes the Activity useless. In a real app, core permissions would // typically be best requested during a welcome-screen flow. // Additionally, it is important to remember that a permission might have been // rejected without asking the user for permission (device policy or "Never ask // again" prompts). Therefore, a user interface affordance is typically implemented // when permissions are denied. Otherwise, your app could appear unresponsive to // touches or interactions which have required permissions. showSnackbar(R.string.permission_denied_explanation, R.string.settings, new View.OnClickListener() { @Override public void onClick(View view) { // Build intent that displays the App settings screen. Intent intent = new Intent(); intent.setAction( Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null); intent.setData(uri); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }); } } } }
5- Adicione ao projeto os arquivo style.xml, string.xml, colors.xml e dimens.xml.
- Android Layouts (ViewGroups) – (Parte 2 – RalativeLayout )
- Android Toast e AlertDialog
- Android Banco de Dados (Firebase)
- Android ListActivity – (Parte 2 – ListView)
- Android Layouts (ViewGroups) – (Parte 1 – LinearLayout)