summaryrefslogtreecommitdiff
path: root/android-app/app/src/main/kotlin/com
diff options
context:
space:
mode:
authorClaudomator Agent <agent@claudomator>2026-03-14 00:50:17 +0000
committerClaudomator Agent <agent@claudomator>2026-03-14 00:50:17 +0000
commit0923c55af5c63539055933509302233ee3f4b26a (patch)
treec0f7e24dff920872e43659f2d0552bd252921744 /android-app/app/src/main/kotlin/com
parent51f86cff118f9532783c4e61724e07173ec029d7 (diff)
feat: add LocationPermissionHandler with 7 unit tests for permission flows
Extract location permission decision logic from MainActivity into a testable LocationPermissionHandler class. Covers: permission already granted, needs request, fine-only granted, coarse-only granted, both granted, both denied, and never-ask-again (empty grants) scenarios. All permissions (INTERNET, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) were already declared in AndroidManifest.xml; no manifest changes needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'android-app/app/src/main/kotlin/com')
-rw-r--r--android-app/app/src/main/kotlin/com/example/androidapp/ui/LocationPermissionHandler.kt43
-rw-r--r--android-app/app/src/main/kotlin/com/example/androidapp/ui/MainActivity.kt33
2 files changed, 65 insertions, 11 deletions
diff --git a/android-app/app/src/main/kotlin/com/example/androidapp/ui/LocationPermissionHandler.kt b/android-app/app/src/main/kotlin/com/example/androidapp/ui/LocationPermissionHandler.kt
new file mode 100644
index 0000000..664d5bb
--- /dev/null
+++ b/android-app/app/src/main/kotlin/com/example/androidapp/ui/LocationPermissionHandler.kt
@@ -0,0 +1,43 @@
+package com.example.androidapp.ui
+
+/**
+ * Encapsulates location permission decision logic.
+ *
+ * Extracted for testability — no direct Android framework dependency in the core logic.
+ *
+ * Permissions handled: ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION
+ *
+ * Usage:
+ * - Call [start] on activity start to check existing permission or trigger a request.
+ * - Call [onResult] from the ActivityResultLauncher callback with the permission grants map.
+ */
+class LocationPermissionHandler(
+ /** Returns true if location permission is already granted. */
+ private val checkGranted: () -> Boolean,
+ /** Called when location permission is available (already granted or just granted). */
+ private val onGranted: () -> Unit,
+ /** Called when location permission is denied or the user refuses (including "never ask again"). */
+ private val onDenied: () -> Unit,
+ /** Called when permission needs to be requested from the user via the system dialog. */
+ private val requestPermissions: () -> Unit
+) {
+ /**
+ * Check current permission state and dispatch:
+ * - If already granted, invoke [onGranted] immediately.
+ * - Otherwise, invoke [requestPermissions] to trigger the system dialog.
+ */
+ fun start() {
+ if (checkGranted()) onGranted() else requestPermissions()
+ }
+
+ /**
+ * Process the result from the system permission dialog.
+ *
+ * @param grants Map of permission name → granted status from ActivityResultLauncher.
+ * Invokes [onGranted] if any permission was granted, [onDenied] otherwise.
+ * An empty map (e.g. "never ask again" scenario) also triggers [onDenied].
+ */
+ fun onResult(grants: Map<String, Boolean>) {
+ if (grants.values.any { it }) onGranted() else onDenied()
+ }
+}
diff --git a/android-app/app/src/main/kotlin/com/example/androidapp/ui/MainActivity.kt b/android-app/app/src/main/kotlin/com/example/androidapp/ui/MainActivity.kt
index 0a326f4..17a636f 100644
--- a/android-app/app/src/main/kotlin/com/example/androidapp/ui/MainActivity.kt
+++ b/android-app/app/src/main/kotlin/com/example/androidapp/ui/MainActivity.kt
@@ -24,11 +24,30 @@ class MainActivity : AppCompatActivity() {
private val defaultLat = 37.8
private val defaultLon = -122.4
+ private val permissionHandler: LocationPermissionHandler by lazy {
+ LocationPermissionHandler(
+ checkGranted = {
+ ContextCompat.checkSelfPermission(
+ this, Manifest.permission.ACCESS_FINE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED
+ },
+ onGranted = { fetchLocationAndLoad() },
+ onDenied = { loadWeatherAtDefault() },
+ requestPermissions = {
+ locationPermissionLauncher.launch(
+ arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ )
+ )
+ }
+ )
+ }
+
private val locationPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { grants ->
- val granted = grants.values.any { it }
- if (granted) fetchLocationAndLoad() else loadWeatherAtDefault()
+ permissionHandler.onResult(grants)
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -72,15 +91,7 @@ class MainActivity : AppCompatActivity() {
}
private fun requestLocationOrLoad() {
- val fine = Manifest.permission.ACCESS_FINE_LOCATION
- val coarse = Manifest.permission.ACCESS_COARSE_LOCATION
- val hasPermission = ContextCompat.checkSelfPermission(this, fine) ==
- PackageManager.PERMISSION_GRANTED
- if (hasPermission) {
- fetchLocationAndLoad()
- } else {
- locationPermissionLauncher.launch(arrayOf(fine, coarse))
- }
+ permissionHandler.start()
}
private fun fetchLocationAndLoad() {