Skip to content

Commit

Permalink
Fix app crash on app launch with location permissions request (#3478)
Browse files Browse the repository at this point in the history
* Fix app crash on app launch with location permissions request

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

* Fix black screen

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>

---------

Signed-off-by: Elly Kitoto <junkmailstoelly@gmail.com>
  • Loading branch information
ellykits authored Sep 6, 2024
1 parent 92b9fb6 commit 662acfd
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 234 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ object LocationUtils {

fun isLocationEnabled(context: Context): Boolean {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager

return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,63 +18,48 @@ package org.smartregister.fhircore.engine.util.location

import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat

object PermissionUtils {

fun checkPermissions(context: Context, permissions: List<String>): Boolean {
for (permission in permissions) {
if (
ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED
) {
return false
}
fun checkPermissions(context: Context, permissions: List<String>): Boolean =
permissions.none {
ContextCompat.checkSelfPermission(
context,
it,
) != PackageManager.PERMISSION_GRANTED
}
return true
}

fun getLocationPermissionLauncher(
activity: AppCompatActivity,
permissions: Map<String, Boolean>,
onFineLocationPermissionGranted: () -> Unit,
onCoarseLocationPermissionGranted: () -> Unit,
onLocationPermissionDenied: () -> Unit,
): ActivityResultLauncher<Array<String>> {
return activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions(),
) { permissions ->
if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true) {
) {
when {
permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true ->
onFineLocationPermissionGranted()
} else if (permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true) {
permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true ->
onCoarseLocationPermissionGranted()
} else {
onLocationPermissionDenied()
}
}
}

fun getStartActivityForResultLauncher(
activity: AppCompatActivity,
onResult: (resultCode: Int, data: Intent?) -> Unit,
): ActivityResultLauncher<Intent> {
return activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult(),
) { result ->
onResult(result.resultCode, result.data)
else -> onLocationPermissionDenied()
}
}

fun hasFineLocationPermissions(context: Context): Boolean {
return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
fun hasFineLocationPermissions(context: Context): Boolean =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}

fun hasCoarseLocationPermissions(context: Context): Boolean {
return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) ==
fun hasCoarseLocationPermissions(context: Context): Boolean =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}

fun hasLocationPermissions(context: Context) =
checkPermissions(
context,
listOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
),
)
}
1 change: 1 addition & 0 deletions android/engine/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
<string name="weeks">Week(s)</string>
<string name="days">Days(s)</string>
<string name="initializing">Initializing settings &#8230;</string>
<string name="initializing_application">Initializing application &#8230;</string>
<string name="username_sample" translatable="false">e.g JohnDoe</string>
<string name="percentage_progress" translatable="false">%1$d%%</string>
<string name="error_occurred">Something went wrong…</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.getValue
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
Expand All @@ -40,6 +39,7 @@ import dagger.hilt.android.AndroidEntryPoint
import io.sentry.android.navigation.SentryNavigationListener
import java.time.Instant
import javax.inject.Inject
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.hl7.fhir.r4.model.IdType
Expand All @@ -62,6 +62,8 @@ import org.smartregister.fhircore.quest.R
import org.smartregister.fhircore.quest.event.AppEvent
import org.smartregister.fhircore.quest.event.EventBus
import org.smartregister.fhircore.quest.ui.questionnaire.QuestionnaireActivity
import org.smartregister.fhircore.quest.ui.shared.ActivityOnResultType
import org.smartregister.fhircore.quest.ui.shared.ON_RESULT_TYPE
import org.smartregister.fhircore.quest.ui.shared.QuestionnaireHandler
import org.smartregister.fhircore.quest.ui.shared.models.QuestionnaireSubmission
import timber.log.Timber
Expand All @@ -81,13 +83,34 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler,
val appMainViewModel by viewModels<AppMainViewModel>()
private val sentryNavListener =
SentryNavigationListener(enableNavigationBreadcrumbs = true, enableNavigationTracing = true)
private lateinit var locationPermissionLauncher: ActivityResultLauncher<Array<String>>
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>

private val locationPermissionLauncher: ActivityResultLauncher<Array<String>> =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
permissions: Map<String, Boolean> ->
PermissionUtils.getLocationPermissionLauncher(
permissions = permissions,
onFineLocationPermissionGranted = { fetchLocation() },
onCoarseLocationPermissionGranted = { fetchLocation() },
onLocationPermissionDenied = {
showToast(
getString(R.string.location_permissions_denied),
Toast.LENGTH_SHORT,
)
},
)
}

private lateinit var fusedLocationClient: FusedLocationProviderClient

override val startForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
activityResult: ActivityResult ->
val onResultType = activityResult.data?.extras?.getString(ON_RESULT_TYPE)
if (
activityResult.resultCode == Activity.RESULT_OK &&
!onResultType.isNullOrBlank() &&
ActivityOnResultType.valueOf(onResultType) == ActivityOnResultType.QUESTIONNAIRE
) {
lifecycleScope.launch { onSubmitQuestionnaire(activityResult) }
}
}
Expand Down Expand Up @@ -127,6 +150,7 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler,
retrieveAppMainUiState()
withContext(dispatcherProvider.io()) { schedulePeriodicJobs(this@AppMainActivity) }
}

setupLocationServices()

findViewById<View>(R.id.mainScreenProgressBar).apply { visibility = View.GONE }
Expand Down Expand Up @@ -188,92 +212,61 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler,
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

if (!LocationUtils.isLocationEnabled(this)) {
openLocationServicesSettings()
showLocationSettingsDialog(
Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).apply {
putExtra(ON_RESULT_TYPE, ActivityOnResultType.LOCATION.name)
},
)
}

if (!hasLocationPermissions()) {
launchLocationPermissionsDialog()
if (!PermissionUtils.hasLocationPermissions(this)) {
locationPermissionLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
),
)
}

if (LocationUtils.isLocationEnabled(this) && hasLocationPermissions()) {
if (LocationUtils.isLocationEnabled(this) && PermissionUtils.hasLocationPermissions(this)) {
fetchLocation()
}
}
}

fun hasLocationPermissions(): Boolean {
return PermissionUtils.checkPermissions(
this,
listOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
),
)
}

private fun openLocationServicesSettings() {
activityResultLauncher =
PermissionUtils.getStartActivityForResultLauncher(this) { resultCode, _ ->
if (resultCode == RESULT_OK || hasLocationPermissions()) {
Timber.d("Location or permissions successfully enabled")
}
}

val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
showLocationSettingsDialog(intent)
}

private fun showLocationSettingsDialog(intent: Intent) {
AlertDialog.Builder(this)
.setMessage(getString(R.string.location_services_disabled))
.setCancelable(true)
.setPositiveButton(getString(R.string.yes)) { _, _ -> activityResultLauncher.launch(intent) }
.setPositiveButton(getString(R.string.yes)) { _, _ -> startForResult.launch(intent) }
.setNegativeButton(getString(R.string.no)) { dialog, _ -> dialog.cancel() }
.show()
}

fun launchLocationPermissionsDialog() {
locationPermissionLauncher =
PermissionUtils.getLocationPermissionLauncher(
this,
onFineLocationPermissionGranted = { fetchLocation() },
onCoarseLocationPermissionGranted = { fetchLocation() },
onLocationPermissionDenied = {
Toast.makeText(
this,
getString(R.string.location_permissions_denied),
Toast.LENGTH_SHORT,
)
.show()
},
)

locationPermissionLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
),
)
}

private fun fetchLocation() {
val context = this
lifecycleScope.launch {
val retrievedLocation =
if (PermissionUtils.hasFineLocationPermissions(context)) {
LocationUtils.getAccurateLocation(fusedLocationClient)
} else if (PermissionUtils.hasCoarseLocationPermissions(context)) {
LocationUtils.getApproximateLocation(fusedLocationClient)
} else {
null
}
retrievedLocation?.let {
protoDataStore.writeLocationCoordinates(
LocationCoordinate(it.latitude, it.longitude, it.altitude, Instant.now()),
)
}
async(dispatcherProvider.io()) {
when {
PermissionUtils.hasFineLocationPermissions(context) ->
LocationUtils.getAccurateLocation(fusedLocationClient)
PermissionUtils.hasCoarseLocationPermissions(context) ->
LocationUtils.getApproximateLocation(fusedLocationClient)
else -> null
}
}
.await()
?.also {
protoDataStore.writeLocationCoordinates(
LocationCoordinate(it.latitude, it.longitude, it.altitude, Instant.now()),
)
}

if (retrievedLocation == null) {
this@AppMainActivity.showToast("Failed to get GPS location", Toast.LENGTH_LONG)
withContext(dispatcherProvider.main()) {
showToast(getString(R.string.failed_to_get_gps_location), Toast.LENGTH_LONG)
}
}
}
}
Expand Down
Loading

0 comments on commit 662acfd

Please sign in to comment.