Skip to content

Commit

Permalink
#106 Remove activityKtx dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexey Nesterov committed Apr 18, 2024
1 parent f4921fa commit 5f7f6c4
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 22 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ override fun onCreate(savedInstanceState: Bundle?) {
}

// Binds the permissions controller to the activity lifecycle.
viewModel.permissionsController.bind(lifecycle, supportFragmentManager)
viewModel.permissionsController.bind(activity)
}
```

Expand Down
6 changes: 4 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ kotlinVersion = "1.9.10"
androidAppCompatVersion = "1.6.1"
composeMaterialVersion = "1.4.1"
composeActivityVersion = "1.7.0"
activityVersion = "1.7.0"
materialDesignVersion = "1.8.0"
androidLifecycleVersion = "2.2.0"
androidCoreTestingVersion = "2.2.0"
Expand All @@ -11,13 +12,15 @@ mokoMvvmVersion = "0.16.0"
mokoPermissionsVersion = "0.17.0"
composeJetBrainsVersion = "1.5.1"
lifecycleRuntime = "2.6.1"
activityKtxVersion = "1.7.2"
composeUiVersion = "1.0.1"

[libraries]
appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidAppCompatVersion" }
material = { module = "com.google.android.material:material", version.ref = "materialDesignVersion" }
composeMaterial = { module = "androidx.compose.material:material", version.ref = "composeMaterialVersion" }
composeActivity = { module = "androidx.activity:activity-compose", version.ref = "composeActivityVersion" }
activity = { module = "androidx.activity:activity", version.ref = "activityVersion" }
composeUi = { module = "androidx.compose.ui:ui", version.ref = "composeUiVersion" }
lifecycle = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "androidLifecycleVersion" }
lifecycleRuntime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntime" }
coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutinesVersion" }
Expand All @@ -34,4 +37,3 @@ mobileMultiplatformGradlePlugin = { module = "dev.icerock:mobile-multiplatform",
kotlinSerializationGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlinVersion" }
composeJetBrainsGradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "composeJetBrainsVersion" }
detektGradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version = "1.22.0" }
activityKtx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtxVersion" }
5 changes: 3 additions & 2 deletions permissions-compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ android {
dependencies {
commonMainApi(projects.permissions)
commonMainApi(compose.runtime)

androidMainImplementation(libs.composeActivity)
androidMainImplementation(libs.activity)
androidMainImplementation(libs.composeUi)
androidMainImplementation(libs.lifecycleRuntime)
}
4 changes: 2 additions & 2 deletions permissions/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ android {

dependencies {
commonMainImplementation(libs.coroutines)
androidMainImplementation(libs.activityKtx)
androidMainImplementation(libs.activity)
androidMainImplementation(libs.lifecycleRuntime)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package dev.icerock.moko.permissions
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
Expand All @@ -24,8 +23,11 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import java.util.UUID
import kotlin.coroutines.suspendCoroutine

Expand All @@ -37,17 +39,17 @@ class PermissionsControllerImpl(

private val mutex: Mutex = Mutex()

private var launcher: ActivityResultLauncher<Array<String>>? = null
private val launcherHolder = MutableStateFlow<ActivityResultLauncher<Array<String>>?>(null)

private var permissionCallback: PermissionCallback? = null

private val key = UUID.randomUUID().toString()

override fun bind(activity: ComponentActivity) {
this.activityHolder.value = activity
val activityResultRegistryOwner = activity as ActivityResultRegistryOwner

val key = UUID.randomUUID().toString()

launcher = activityResultRegistryOwner.activityResultRegistry.register(
val launcher = activityResultRegistryOwner.activityResultRegistry.register(
key,
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
Expand All @@ -67,7 +69,7 @@ class PermissionsControllerImpl(
if (success) {
permissionCallback.callback.invoke(Result.success(Unit))
} else {
if (shouldShowRequestPermissionRationale(permissions.keys.first())) {
if (shouldShowRequestPermissionRationale(activity, permissions.keys.first())) {
permissionCallback.callback.invoke(
Result.failure(DeniedException(permissionCallback.permission))
)
Expand All @@ -79,10 +81,13 @@ class PermissionsControllerImpl(
}
}

launcherHolder.value = launcher

val observer = object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
this@PermissionsControllerImpl.activityHolder.value = null
this@PermissionsControllerImpl.launcherHolder.value = null
source.lifecycle.removeObserver(this)
}
}
Expand All @@ -92,9 +97,11 @@ class PermissionsControllerImpl(

override suspend fun providePermission(permission: Permission) {
mutex.withLock {
val launcher = awaitActivityResultLauncher()
val platformPermission = permission.toPlatformPermission()
suspendCoroutine { continuation ->
requestPermission(
launcher,
permission,
platformPermission
) { continuation.resumeWith(it) }
Expand All @@ -103,12 +110,43 @@ class PermissionsControllerImpl(
}

private fun requestPermission(
launcher: ActivityResultLauncher<Array<String>>,
permission: Permission,
permissions: List<String>,
callback: (Result<Unit>) -> Unit
) {
permissionCallback = PermissionCallback(permission, callback)
launcher?.launch(permissions.toTypedArray())
launcher.launch(permissions.toTypedArray())
}

private suspend fun awaitActivityResultLauncher(): ActivityResultLauncher<Array<String>> {
val activityResultLauncher = launcherHolder.value
if (activityResultLauncher != null) return activityResultLauncher

return withTimeoutOrNull(AWAIT_ACTIVITY_TIMEOUT_DURATION_MS) {
launcherHolder.filterNotNull().first()
} ?: error(
"activityResultLauncher is null, `bind` function was never called," +
" consider calling permissionsController.bind(activity)" +
" or BindEffect(permissionsController) in the composable function," +
" check the documentation for more info: " +
"https://github.com/icerockdev/moko-permissions/blob/master/README.md"
)
}

private suspend fun awaitActivity(): Activity {
val activity = activityHolder.value
if (activity != null) return activity

return withTimeoutOrNull(AWAIT_ACTIVITY_TIMEOUT_DURATION_MS) {
activityHolder.filterNotNull().first()
} ?: error(
"activity is null, `bind` function was never called," +
" consider calling permissionsController.bind(activity)" +
" or BindEffect(permissionsController) in the composable function," +
" check the documentation for more info: " +
"https://github.com/icerockdev/moko-permissions/blob/master/README.md"
)
}

override suspend fun isPermissionGranted(permission: Permission): Boolean {
Expand Down Expand Up @@ -142,14 +180,12 @@ class PermissionsControllerImpl(
else PermissionState.NotGranted
}

private fun shouldShowRequestPermissionRationale(permission: String): Boolean {
val activity: Activity = checkNotNull(this.activityHolder.value) {
"${this.activityHolder.value} activity is null, `bind` function was never called," +
" consider calling permissionsController.bind(activity)" +
" or BindEffect(permissionsController) in the composable function," +
" check the documentation for more info: " +
"https://github.com/icerockdev/moko-permissions/blob/master/README.md"
}
private suspend fun shouldShowRequestPermissionRationale(permission: String): Boolean {
val activity = awaitActivity()
return shouldShowRequestPermissionRationale(activity, permission)
}

private fun shouldShowRequestPermissionRationale(activity: Activity, permission: String): Boolean {
return ActivityCompat.shouldShowRequestPermissionRationale(
activity,
permission
Expand Down Expand Up @@ -303,6 +339,7 @@ class PermissionsControllerImpl(
private companion object {
val VERSIONS_WITHOUT_NOTIFICATION_PERMISSION =
Build.VERSION_CODES.KITKAT until Build.VERSION_CODES.TIRAMISU
private const val AWAIT_ACTIVITY_TIMEOUT_DURATION_MS = 2000L
}
}

Expand Down

0 comments on commit 5f7f6c4

Please sign in to comment.