LocationService – Documentation – Zaigo Infotech Software Solutions

Let’s craft brilliance together!

Request a free consultation and get a no-obligation quote for your project within one working day.

Company-Logo

Error: Contact form not found.

LocationService – Documentation

Flutter

Mobile App Android

Overview

LocationService is an Android foreground service designed to continuously track the user’s location, handle GPS and internet outages, detect mock locations, and send location data to a server using Socket.IO. The service is resilient to process death and network issues and includes a periodic watchdog via WorkManager to restart itself if needed.


Components

1. Service Initialization

override fun onCreate() {
    super.onCreate()
    log("Service onCreate called")
    setupSocket()
    acquireWakeLock()
    registerReceivers()
    schedulePeriodicWork()
}
  • Called when the service is first created.
  • Initializes the socket connection, acquires a partial wake lock (to keep CPU on), registers receivers (if any), and schedules periodic monitoring via WorkManager.
private fun setupSocket() {
    try {
        socket = IO.socket("https://route.getfieldy.com")
        socket.connect()
    } catch (e: URISyntaxException) {
        log("Socket error: ${e.message}")
    }
}
  • Initializes and connects the Socket.IO instance to the backend server.

2. Service Lifecycle

@RequiresApi(Build.VERSION_CODES.O)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    val action = intent?.action
    log("onStartCommand called. Intent action: $action")

    if (action == null && !isRunning) {
        startTracking()
    } else {
        when (action) {
            ACTION_START -> startTracking()
            ACTION_STOP -> {
                prefs.edit().putBoolean("IS_RUNNING", false).apply()
                stopTracking()
            }
        }
    }
    return START_STICKY
}
  • Handles service start and stop commands.
  • Ensures the service continues to run (START_STICKY) even if the system kills it.

3. Location Tracking Setup

private fun startTracking() {
    isRunning = true
    prefs.edit().putBoolean("IS_RUNNING", true).apply()
  • Marks the tracking as active in SharedPreferences.
    val locationDataString = prefs.getString("LOCDATA", "_")
    if (locationDataString != "_") {
        val locData = JSONObject(locationDataString ?: "{}")
        userName = locData.getString("name")
        userImageUrl = locData.getString("image")
        tenantId = locData.getString("tenant")
        technicianId = locData.getString("technician")
        metaDataId = locData.getString("metaId")
        metaDataType = locData.getString("metaType")
    }
  • Loads stored user/technician and tracking metadata from preferences.
    setupNotification()
    locationClient = LocationServices.getFusedLocationProviderClient(this)
  • Builds the notification and initializes the FusedLocationProviderClient.
    val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000L)
        .setMinUpdateIntervalMillis(5000L)
        .setWaitForAccurateLocation(true)
        .setGranularity(Granularity.GRANULARITY_FINE)
        .setMinUpdateDistanceMeters(5f)
        .build()
  • Configures a high-accuracy location request with a 5-second interval and 5-meter minimum movement.
    locationCallback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult) {
            result.locations.forEach { location ->
                processLocation(location, result)
            }
        }
    }
  • Defines how to handle new location updates.
    locationClient.requestLocationUpdates(
        locationRequest,
        locationCallback,
        Looper.getMainLooper()
    )
}
  • Starts receiving location updates from the fused location provider.

4. Location Handling and Emission via Socket

private fun processLocation(location: Location, result: LocationResult) {
    val isMockLocation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        location.isMock
    } else {
        location.isFromMockProvider
    }
  • Determines if the current location is mocked (fake GPS).
    batteryPercentage = getBatteryPercentage()
  • Retrieves the current device battery percentage.
    val message = when {
        batteryPercentage < 20 -> "⚠️ Low battery: $batteryPercentage%"
        isMockLocation -> "⚠️ Mock location detected."
        else -> "Tracking location... ${location.latitude}, ${location.longitude}"
    }
  • Decides what message to show based on mock/battery status.
    notification = buildNotification(message)
    notificationManager.notify(1, notification)
  • Updates the foreground notification dynamically.
    prepareData(location, result)
}
  • Passes data to be structured and sent.
private fun prepareData(location: Location, result: LocationResult) {
    val isMockLocation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) location.isMock else location.isFromMockProvider
    val isInternetAvailable = isInternetAvailable()
  • Checks mock location and current network availability.
    val locationData = buildLocationData(
        location.latitude,
        location.longitude,
        0.0,
        batteryPercentage,
        event = if (isMockLocation) "location_mocked"
                else if (isGPSoutage) "gps_outage"
                else if (!isInternetAvailable) "internet_disconnected"
                else if (metaDataType == "availability_on_traveling") "availability_on_traveling"
                else "traveling",
        mocked = isMockLocation,
        accuracy = location.accuracy,
        speed = location.speed,
        bearingDiff = location.bearingAccuracyDegrees.toString(),
        locationResult = result
    )
  • Builds a JSON object with the current location context.
    if (isInternetAvailable) {
        sendToSocket(locationData)
        prefs.getString("OFFLINE_DATA", null)?.let {
            sendToSocket(JSONObject(it))
            val recoveryData = JSONObject(locationData.toString()).apply {
                put("event", "internet_resumed")
            }
            sendToSocket(recoveryData)
            prefs.edit().remove("OFFLINE_DATA").apply()
        }
    } else {
        prefs.edit().putString("OFFLINE_DATA", locationData.toString()).apply()
    }
}
  • Sends live data via socket if online, or stores it offline if not.
  • If offline data exists when internet resumes, it also gets sent.

5. Send to Server via Socket.IO

private fun sendToSocket(locationData: JSONObject) {
    if (!socket.connected()) {
        try {
            socket.connect()
        } catch (e: Exception) {
            log("Socket reconnect failed: ${e.message}")
        }
    }
    when (metaDataType) {
        "travel_history_full_day_tracking" -> socket.emit("travel_history_full_day_tracking", locationData)
        "availability_on_traveling" -> socket.emit("current_location", locationData)
        else -> socket.emit("travel_history", locationData)
    }
}
  • Reconnects socket if disconnected.
  • Emits different event types depending on the tracking mode (metaDataType).

Monitoring & Utilities

6. Background Watchdog

class ServiceMonitorWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
    override fun doWork(): Result {
        val prefs = applicationContext.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
        if (!prefs.getBoolean("IS_RUNNING", false)) {
            val intent = Intent(applicationContext, LocationServiceV2::class.java).apply {
                action = LocationServiceV2.ACTION_START
            }
            ContextCompat.startForegroundService(applicationContext, intent)
        }
        return Result.success()
    }
}
  • Periodic WorkManager worker that checks if the service is still running.
  • If stopped, it restarts the service using ContextCompat.startForegroundService.

Conclusion

This version of LocationService uses Socket.IO for real-time transmission of location data, suitable for applications that need low-latency live tracking, such as fieldwork or logistics. Socket reconnection and offline buffer handling are included to ensure robustness.

Can't find what you are looking for?

Post your query now, and we will get in touch with you soon!

    Want to start a project?

    Our team is ready to implement your ideas. Contact us now to discuss your roadmap!

    GET IN TOUCH

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    INDIA

    9thfloor, (9A & 9B) Sapna Trade Centre, 135,
    Old 109, Poonamallee High Rd, Egmore,
    Chennai, Tamil Nadu 600084

    +91 9884783216

    marketing@zaigoinfotech.com

    USA

    170 Post Rd #211, Fairfield,
    CT 06824,
    USA

    +1 904-672-8617

    sales@zaigoinfotech.com