Show ‘Turn on device location’ Request(like Google Maps) in Jetpack Compose

Show ‘Turn on device location’ Request(like Google Maps) in Jetpack Compose

Output:

Output

Pre-requisites:

  1. Need Locations Permission access already. For details: Check this.

  2. Knowledge of MVVM(We will be using ViewModel only).

  3. Basics of Hilt for Dependency Injection.

  4. Better Testing on physical devices.

  5. We will be observing the location service and anytime if it turns off, we will show the consent.

NOTE: We may NOT be following some best practices and this article is to just show the straightforward implementation of the feature. Use your practices to use this in production applications.

Overview:

In my application, I had one feature/activity that only works with Location services turned on. So,

  1. I made sure already that we will enter that activity with location permission granted.

  2. We will show the request prompt whenever the user turns off the location.

  3. If the user denies to turn on the location, we will leave the activity(Ik it’s not good), and you can implement your actions.

Implementation:

In our ViewModel:

  1. Create an ‘isLocationEnabled’ StateFlow object that will tell us whether Location services are ON or OFF.
val isLocationEnabled = MutableStateFlow(false)

2. Create a ‘enableLocationRequest’ method that will create the request prompt whenever we call it and location services are OFF.

//Not necessarily in VM, you can also create it in your UI.
fun enableLocationRequest(
    context: Context,
    makeRequest: (intentSenderRequest: IntentSenderRequest) -> Unit//Lambda to call when locations are off.
) {
    val locationRequest = LocationRequest.Builder(//Create a location request object
        Priority.PRIORITY_HIGH_ACCURACY,//Self explanatory
        10000//Interval -> shorter the interval more frequent location updates
    ).build()

    val builder = LocationSettingsRequest.Builder()
            .addLocationRequest(locationRequest)

    val client: SettingsClient = LocationServices.getSettingsClient(context)
    val task: Task<LocationSettingsResponse> = client.checkLocationSettings(builder.build())//Checksettings with building a request
    task.addOnSuccessListener { locationSettingsResponse ->
        Log.d(
            "Location",
            "enableLocationRequest: LocationService Already Enabled"
        )
    }
    task.addOnFailureListener { exception ->
        if (exception is ResolvableApiException) {
            // Location settings are not satisfied, but this can be fixed
            // by showing the user a dialog.
            try {
                val intentSenderRequest =
                    IntentSenderRequest.Builder(exception.resolution).build()//Create the request prompt
                makeRequest(intentSenderRequest)//Make the request from UI
            } catch (sendEx: IntentSender.SendIntentException) {
                // Ignore the error.
            }
        }
    }
}

3. Create a LocationHelper that will tell us whether GPS_PROVIDER is active or not as we will not always call this ‘enableLocationRequest’ directly.

This is OPTIONAL to create in a separate class you can also do it directly in VM.

@Singleton
class LocationHelper @Inject constructor(
    @ApplicationContext private val context: Context
) {

    fun isConnected(): Boolean {
        val locationManager =
            context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
        return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
    }

4. Inject the LocationHelper in your VM and set the connection with your ‘isLocationEnabled’.

@HiltViewModel
class MapsViewModel
@Inject constructor(
    ...
    private val locationHelper: LocationHelper
) : ViewModel() {

    val isLocationEnabled = MutableStateFlow(false)
    ...


    init {
        updateLocationServiceStatus()
    }

    private fun updateLocationServiceStatus() {
        isLocationEnabled.value = locationHelper.isConnected()
    }
    ...

In our Activity:

  1. First, we need a Broadcast Receiver that will update us every time for the GPS settings.
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.location.LocationManager
import android.util.Log

class LocationProviderChangedReceiver : BroadcastReceiver() {

    private var isGpsEnabled: Boolean = false
    private var isNetworkEnabled: Boolean = false
    private var locationListener: LocationListener? = null

    fun init(locationListener: LocationListener) {
        this.locationListener = locationListener
    }//Set the callbacks before registering 

    override fun onReceive(context: Context, intent: Intent) {
        intent.action?.let { act ->
            if (act.matches("android.location.PROVIDERS_CHANGED".toRegex())) {
                val locationManager =
                    context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
                isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
                isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)

                Log.i(TAG, "Location Providers changed, is GPS Enabled: $isGpsEnabled")

                if (isGpsEnabled && isNetworkEnabled) {
                    locationListener?.onEnabled()
                } else {
                    locationListener?.onDisabled()
                }
            }
        }
    }

    interface LocationListener {//We need some callback methods
        fun onEnabled()
        fun onDisabled()
    }

    companion object {
        private val TAG = "Location"
    }
}

2. In your activity, create a request/prompt launcher that we will launch whenever the location is OFF.

//In activity
private fun registerLocationRequestLauncher() {
    locationRequestLauncher =//We will create a global var
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
            if (activityResult.resultCode == RESULT_OK)
                mapsViewModel.updateCurrentLocationData(this)//If the user clicks OK to turn on location
            else {
                if (!mapsViewModel.isLocationEnabled.value) {//If the user cancels, Still make a check and then exit the activity
                    Toast.makeText(
                        this,
                        "Location access is mandatory to use this feature!!",
                        Toast.LENGTH_SHORT
                    )
                        .show()
                    finish()
                }
            }
        }
}

3. Create a function to register the broadcast receiver.

private fun registerBroadcastReceiver() {
    br = LocationProviderChangedReceiver()
    br!!.init(
        object : LocationProviderChangedReceiver.LocationListener {
            override fun onEnabled() {
                mapsViewModel.isLocationEnabled.value = true//Update our VM
            }

            override fun onDisabled() {
                mapsViewModel.isLocationEnabled.value = false//Update our VM
            }
        }
    )
    val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
    registerReceiver(br, filter)
}

4. Now, call these methods from onCreate and also start observing our ‘isLocationEnabled’. Call our enableLocationRequest method in case needed.

private val mapsViewModel: MapsViewModel by viewModels()
private var br: LocationProviderChangedReceiver? = null
private var locationRequestLauncher: ActivityResultLauncher<IntentSenderRequest>? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    registerLocationRequestLauncher()
    registerBroadcastReceiver()

    setContent {
        AppTheme {
            val isLocationEnabled by mapsViewModel.isLocationEnabled.collectAsStateWithLifecycle()
            if (!isLocationEnabled) {
                mapsViewModel.enableLocationRequest(this@MapsActivity) {//Call this if GPS is OFF.
                    locationRequestLauncher?.launch(it)//Launch it to show the prompt.
                }
            }
            //Show your UI accordingly.
        }
    }
}

5. Unregister the receiver to avoid memory leaks.

override fun onDestroy() {
    super.onDestroy()
    if (br != null) unregisterReceiver(br)
}

That’s it!!

It may look a little bit complex to consume that much content, but it is working straightforwardly. Let me brief…

  1. Activity is started… => setting up VM >registeringReceiver > registering the Intent Launcher > Observing the ‘isLocationEnabled’

  2. On viewmodel starting… => call init block and update status

  3. If GPS is changed.. => broadcast receiver will be called and the callback will trigger > notify isLocationEnabled > call the enableLocationRequest to show the prompt.

Reference.

I hope you found this helpful. If yes, then do FOLLOW ‘Sagar Malhotra’ for more Android-related content.

#androidWithSagar #android #androiddevelopment #development #compose #kotlin