Table of contents
So as title suggest this are our requirements :
Location tracking service
which runs continuously in the background and records users Lat, Long every 1 minute and stores this data to server every 60 mins.
so the data will be stored in RoomDB for 60 mins
then sent to server in the background if internet connection is active
after sending data to server and receiving success then clearing RoomDB
Step 1: Room DB Setup
LocationData.java:
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "location_data")
public class LocationData {
@PrimaryKey(autoGenerate = true)
private int id;
private double latitude;
private double longitude;
private long timestamp;
// Getters and setters
public LocationData(double latitude, double longitude, long timestamp) {
this.latitude = latitude;
this.longitude = longitude;
this.timestamp = timestamp;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
LocationDao.java:
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface LocationDao {
@Insert
void insert(LocationData locationData);
@Query("DELETE FROM location_data")
void deleteAll();
@Query("SELECT * from location_data ORDER BY timestamp ASC")
List<LocationData> getAllLocations();
}
LocationDatabase.java
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {LocationData.class}, version = 1, exportSchema = false)
public abstract class LocationDatabase extends RoomDatabase {
public abstract LocationDao locationDao();
}
InsertAsyncTask.java
import android.os.AsyncTask;
public class InsertAsyncTask extends AsyncTask<LocationData, Void, Void> {
private LocationDao locationDao;
InsertAsyncTask(LocationDao locationDao) {
this.locationDao = locationDao;
}
@Override
protected Void doInBackground(LocationData... locationData) {
locationDao.insert(locationData[0]);
return null;
}
}
Step 2: Location Service
LocationTrackingService.java
import android.Manifest;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.room.Room;
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 java.util.List;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class LocationTrackingService extends Service {
private LocationDatabase locationDatabase;
private FusedLocationProviderClient mFusedLocationClient;
private LocationCallback mLocationCallback;
private LocationDao locationDao;
private static final String TAG = "LocationTrackingService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate() called");
locationDatabase = Room.databaseBuilder(this, LocationDatabase.class, "location_database")
.build();
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
locationDao = locationDatabase.locationDao();
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
Log.d(TAG, "onLocationResult() called with: locationResult = [" + locationResult + "]");
if (locationResult == null) {
return;
}
for (Location location : locationResult.getLocations()) {
LocationData locationData = new LocationData(location.getLatitude(), location.getLongitude(), location.getTime());
new InsertAsyncTask(locationDao).execute(locationData);
}
startSendingLocationData();
}
};
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return Service.START_NOT_STICKY;
}
mFusedLocationClient.requestLocationUpdates(getLocationRequest(), mLocationCallback, null);
return Service.START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private LocationRequest getLocationRequest() {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(60 * 1000); // 60 seconds
locationRequest.setFastestInterval(30 * 1000); // 30 seconds
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
Log.d(TAG, "getLocationRequest() called");
return locationRequest;
}
private void startSendingLocationData() {
Log.d(TAG, "startSendingLocationData() called");
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(60 * 1000); // check every 60 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
if (isNetworkConnected() && !isDataSentWithinInterval()) {
// Get all location data from Room database
Log.d(TAG, "run() called isNetworkConnected() && !isDataSentWithinInterval()");
List<LocationData> locations = locationDao.getAllLocations();
// Send location data to server using Retrofit
sendLocationDataToServer(locations);
// Store the timestamp of the last sent data in SharedPreferences
storeLastSentTimestamp();
}
}
}
}).start();
}
private boolean isNetworkConnected() {
Log.d(TAG, "isNetworkConnected() called");
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}
// fetch data to send to server
private void sendLocationDataToServer(List<LocationData> locations) {
Log.d(TAG, "sendLocationDataToServer() called with: locations = [" + locations + "]");
//I have commented retrofit code you can do this part just make sure to call deleteAll method from LocationDao only if you want to delete RoomDB.
// Create Retrofit instance
// Retrofit retrofit = new Retrofit.Builder()
// .baseUrl(BASE_URL)
// .addConverterFactory(GsonConverterFactory.create())
// .build();
//
// // Create service
// LocationService service = retrofit.create(LocationService.class);
//
// // Send location data to server
// Call<ResponseBody> call = service.sendLocationData(locations);
// call.enqueue(new Callback<ResponseBody>() {
// @Override
// public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
// if (response.isSuccessful()) {
// Log.d(TAG, "Location data sent to server successfully");
// // Clear location data from Room database
// new ClearLocationsAsyncTask(locationDao).execute();
// } else {
// Log.e(TAG, "Failed to send location data to server");
// }
// }
//
// @Override
// public void onFailure(Call<ResponseBody> call, Throwable t) {
// Log.e(TAG, "Failed to send location data to server", t);
// }
// });
}
private void storeLastSentTimestamp() {
SharedPreferences preferences = getSharedPreferences("location_data", MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putLong("last_sent_timestamp", System.currentTimeMillis());
editor.apply();
Log.d(TAG, "storeLastSentTimestamp() called");
}
private boolean isDataSentWithinInterval() {
Log.d(TAG, "isDataSentWithinInterval() called");
SharedPreferences preferences = getSharedPreferences("location_data", MODE_PRIVATE);
long lastSentTimestamp = preferences.getLong("last_sent_timestamp", 0);
long currentTimestamp = System.currentTimeMillis();
long interval = 60 * 60 * 1000; // 60 minutes in milliseconds
return currentTimestamp - lastSentTimestamp < interval;
}
}
I have commented retrofit code you can do this part just make sure to call deleteAll method from LocationDao only if you want to delete RoomDB.
Step 3: Activate it
Manifest code:
<application>
...
<service
android:name=".Services.Location.LocationTrackingService"
android:enabled="true"
android:exported="false" />
...
</application>
//Permissions
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Application Class:
public class MySuperAppApplication extends Application {
private static Application instance;
@Override
public void onCreate() {
super.onCreate();
instance = this;
// DynamicColors.applyToActivitiesIfAvailable(this);
Intent intent = new Intent(this, LocationTrackingService.class);
startService(intent);
}
public static Context getContext() {
return instance.getApplicationContext();
}
}