DEV Community

loading...
Mad Devs

Device Location Using LiveData Architecture Component

maddevsio profile image Mad Devs ・3 min read

I had a problem with implementing location detection logic without breaking Android application architecture.

After a couple of hours of researching, I finally came up with a solution to monitor the current location with LiveData usage. This article is a step by step solution to the above-stated problem.

Implementing Location Detection Logic Without Breaking Android Application Architecture.

Step 1. Add dependencies for location listener in Gradle module

implementation 'com.google.android.gms:play-services-location:x.x.x'
Enter fullscreen mode Exit fullscreen mode

Step 2. Create Location Listener

class LocationListener private constructor(private val 
context: Context): LiveData<Location?>()
Enter fullscreen mode Exit fullscreen mode

Inherit the class from LiveData < Location? >

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

Next is a full example of the Location Listener class:

class LocationListener private constructor(private val context: Context): LiveData<Location?>() {
var requestingLocationUpdates: Boolean = true
private var mFusedLocationClient: FusedLocationProviderClient? = null
private var mLocationRequest: LocationRequest? = null

@Synchronized
private fun createLocationRequest() {
    Log.d(TAG, "Creating location request")
    mLocationRequest = LocationRequest.create()
    mLocationRequest?.interval = 20000
    mLocationRequest?.fastestInterval = 5000
    mLocationRequest?.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}

fun startService() {
    onActive()
}


override fun onActive() {
    super.onActive()
    if (ActivityCompat.checkSelfPermission(
            context, Manifest.permission.ACCESS_FINE_LOCATION
       ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
            context,
            Manifest.permission.ACCESS_COARSE_LOCATION
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        return
    }
    fusedLocationProviderClient
    createLocationRequest()
    val looper = Looper.myLooper()
    mFusedLocationClient?.requestLocationUpdates(mLocationRequest, mLocationCallback, looper)
}

override fun onInactive() {
    if (mFusedLocationClient != null) {
        mFusedLocationClient?.removeLocationUpdates(mLocationCallback)
    }
}

val fusedLocationProviderClient: FusedLocationProviderClient?
    get() {
        if (mFusedLocationClient == null) {
            mFusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
        }
        return mFusedLocationClient
    }

private val mLocationCallback: LocationCallback = object : LocationCallback() {
    override fun onLocationResult(locationResult: LocationResult) {
        val newLocation = locationResult.lastLocation
        if (newLocation != null && requestingLocationUpdates){
            value = newLocation
            onInactive()
        }
    }
}

companion object {
    private const val TAG = "LocationListener"
    private var instance: LocationListener? = null
    fun getInstance(appContext: Context): LocationListener? {
        if (instance == null) {
            instance = LocationListener(appContext)
        }
        return instance
    }
}

}
Enter fullscreen mode Exit fullscreen mode

So what we did basically:

  • build FusedLocationProviderClient
  • build location callback
  • build location request
  • create looper
  • request location updates

After we start requesting location updates this will tell the GoogleServices to let the app know whenever there’s a location change. In this example, I disabled location updating after it will found.

override fun onLocationResult(locationResult: LocationResult) {
        val newLocation = locationResult.lastLocation
        if (newLocation != null && requestingLocationUpdates){
            value = newLocation
            onInactive() //Disable location updating
        }
    }
Enter fullscreen mode Exit fullscreen mode

It is possible to use Location Listener in ViewModel to make it easier to use the result of location services.

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.

Example:

class LocationVM: ViewModel() {

var location: MutableLiveData<Location>? = MutableLiveData<Location>()
var locationRepository: LocationListener? = null

fun setLocationRepository(context: Context) {
    locationRepository = LocationListener.getInstance(context)
}

fun enableLocationServices(){
    locationRepository?.let {
        it.startService()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3. To make this works we should create observers.

Observer for the listener:

viewModel.locationRepository?.let {
if (!it.hasObservers()) {
    it.observe(this, Observer<Location?> { location ->
        location?.let {
            viewModel.location?.value = it
            progress_bar.visibility = View.INVISIBLE
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Observer for mutable live data:

val locationObserver = Observer<Location> { location ->
// Update the TextView "location_text" text
Log.i("LiveData location", "" + location.latitude + " / " + location.longitude)
location?.let {
    location_text.text = "" + it.latitude + "\n" + it.longitude
    }
}
viewModel.location?.observe(this, locationObserver)
Enter fullscreen mode Exit fullscreen mode

And that’s it.

How Observers Work in Android Applications.

Check out an example project on GitHub.

Alt Text

Previously published at blog.maddevs.io.

Discussion

pic
Editor guide