Giới thiệu
Ví dụ này minh họa cách thêm dependency trong Android Studio để xây dựng một ứng dụng theo dõi vị trí sử dụng Google Play Services và thư viện Dexter để quản lý quyền runtime.
Bước 1: Tạo dự án mới
Tạo một dự án mới trong Android Studio: chọn File → New Project và điền các thông tin bắt buộc.
Bước 2: Cập nhật layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_start_location_updates"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:onClick="startLocationButtonClick"
android:text="@string/start_updates" />
<Button
android:id="@+id/btn_stop_location_updates"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:enabled="false"
android:onClick="stopLocationButtonClick"
android:text="@string/stop_updates" />
<Button
android:id="@+id/btn_get_last_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:onClick="showLastKnownLocation"
android:text="@string/get_last_location" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:gravity="center_horizontal"
android:text="Cập nhật vị trí chỉ được nhận khi ứng dụng ở foreground" />
<TextView
android:id="@+id/location_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textColor="#333"
android:textSize="18sp" />
<TextView
android:id="@+id/updated_on"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:textSize="12sp" />
</LinearLayout>
Bước 3: Cập nhật MainActivity (src/MainActivity.java)
package app.com.sample;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.annotation.SuppressLint;
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.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
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 com.karumi.dexter.Dexter;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.single.PermissionListener;
import java.text.DateFormat;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
TextView txtLocationResult;
TextView txtUpdatedOn;
Button btnStartUpdates;
Button btnStopUpdates;
private String mLastUpdateTime;
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;
private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = 5000;
private static final int REQUEST_CHECK_SETTINGS = 100;
private FusedLocationProviderClient mFusedLocationClient;
private SettingsClient mSettingsClient;
private LocationRequest mLocationRequest;
private LocationSettingsRequest mLocationSettingsRequest;
private LocationCallback mLocationCallback;
private Location mCurrentLocation;
private Boolean mRequestingLocationUpdates;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
restoreValuesFromBundle(savedInstanceState);
}
private void init() {
txtLocationResult = findViewById(R.id.location_result);
txtUpdatedOn = findViewById(R.id.updated_on);
btnStartUpdates = findViewById(R.id.btn_start_location_updates);
btnStopUpdates = findViewById(R.id.btn_stop_location_updates);
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
mSettingsClient = LocationServices.getSettingsClient(this);
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
mCurrentLocation = locationResult.getLastLocation();
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
updateLocationUI();
}
};
mRequestingLocationUpdates = false;
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
mLocationSettingsRequest = builder.build();
}
private void restoreValuesFromBundle(Bundle savedInstanceState) {
if (savedInstanceState != null) {
if (savedInstanceState.containsKey("is_requesting_updates")) {
mRequestingLocationUpdates = savedInstanceState.getBoolean("is_requesting_updates");
}
if (savedInstanceState.containsKey("last_known_location")) {
mCurrentLocation = savedInstanceState.getParcelable("last_known_location");
}
if (savedInstanceState.containsKey("last_updated_on")) {
mLastUpdateTime = savedInstanceState.getString("last_updated_on");
}
}
updateLocationUI();
}
private void updateLocationUI() {
if (mCurrentLocation != null) {
txtLocationResult.setText("Lat: " + mCurrentLocation.getLatitude() + ", Lng: " + mCurrentLocation.getLongitude());
txtLocationResult.setAlpha(0);
txtLocationResult.animate().alpha(1).setDuration(300);
txtUpdatedOn.setText("Cập nhật lần cuối: " + mLastUpdateTime);
}
toggleButtons();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("is_requesting_updates", mRequestingLocationUpdates);
outState.putParcelable("last_known_location", mCurrentLocation);
outState.putString("last_updated_on", mLastUpdateTime);
}
private void toggleButtons() {
if (mRequestingLocationUpdates) {
btnStartUpdates.setEnabled(false);
btnStopUpdates.setEnabled(true);
} else {
btnStartUpdates.setEnabled(true);
btnStopUpdates.setEnabled(false);
}
}
private void startLocationUpdates() {
mSettingsClient
.checkLocationSettings(mLocationSettingsRequest)
.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
@SuppressLint("MissingPermission")
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
Log.i(TAG, "Tất cả cài đặt vị trí đều thỏa mãn.");
Toast.makeText(getApplicationContext(), "Đã bắt đầu cập nhật vị trí!", Toast.LENGTH_SHORT).show();
mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper());
updateLocationUI();
}
})
.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, "Cài đặt vị trí không thỏa mãn. Đang cố gắng nâng cấp cài đặt vị trí");
try {
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS);
} catch (IntentSender.SendIntentException sie) {
Log.i(TAG, "PendingIntent không thể thực thi yêu cầu.");
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
String errorMessage = "Cài đặt vị trí không đủ và không thể sửa ở đây. Hãy sửa trong Cài đặt.";
Log.e(TAG, errorMessage);
Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show();
}
updateLocationUI();
}
});
}
public void startLocationButtonClick(View view) {
Dexter.withActivity(this)
.withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
.withListener(new PermissionListener() {
@Override
public void onPermissionGranted(PermissionGrantedResponse response) {
mRequestingLocationUpdates = true;
startLocationUpdates();
}
@Override
public void onPermissionDenied(PermissionDeniedResponse response) {
if (response.isPermanentlyDenied()) {
openSettings();
}
}
@Override
public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
token.continuePermissionRequest();
}
}).check();
}
public void stopLocationButtonClick(View view) {
mRequestingLocationUpdates = false;
stopLocationUpdates();
}
public void stopLocationUpdates() {
mFusedLocationClient
.removeLocationUpdates(mLocationCallback)
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
Toast.makeText(getApplicationContext(), "Đã dừng cập nhật vị trí!", Toast.LENGTH_SHORT).show();
toggleButtons();
}
});
}
public void showLastKnownLocation(View view) {
if (mCurrentLocation != null) {
Toast.makeText(getApplicationContext(), "Lat: " + mCurrentLocation.getLatitude() + ", Lng: " + mCurrentLocation.getLongitude(), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), "Vị trí đã biết cuối cùng không khả dụng!", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CHECK_SETTINGS) {
switch (resultCode) {
case Activity.RESULT_OK:
Log.e(TAG, "Người dùng đồng ý thực hiện thay đổi cài đặt vị trí bắt buộc.");
break;
case Activity.RESULT_CANCELED:
Log.e(TAG, "Người dùng chọn không thực hiện thay đổi cài đặt vị trí bắt buộc.");
mRequestingLocationUpdates = false;
break;
}
}
}
private void openSettings() {
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);
}
@Override
public void onResume() {
super.onResume();
if (mRequestingLocationUpdates && checkPermissions()) {
startLocationUpdates();
}
updateLocationUI();
}
private boolean checkPermissions() {
int permissionState = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
return permissionState == PackageManager.PERMISSION_GRANTED;
}
@Override
protected void onPause() {
super.onPause();
if (mRequestingLocationUpdates) {
stopLocationUpdates();
}
}
}
Bước 4: Thêm dependency vào build.gradle (cấp app)
// Dịch vụ vị trí Google Play Services
implementation 'com.google.android.gms:play-services-location:17.0.0'
// Dexter cho quyền runtime
implementation 'com.karumi:dexter:4.2.0'
Bước 5: Cập nhật AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="app.com.sample">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Chạy ứng dụng
Kết nối thiết bị Android thật với máy tính. Trong Android Studio, mở file activity và nhấn biểu tượng Run. Chọn thiết bị của bạn và quan sát màn hình thiết bị.
