GithubHelp home page GithubHelp logo

icerockdev / moko-permissions Goto Github PK

View Code? Open in Web Editor NEW
281.0 7.0 30.0 346 KB

Runtime permissions controls for mobile (android & ios) Kotlin Multiplatform development

Home Page: https://moko.icerock.dev/

License: Apache License 2.0

Kotlin 100.00%
android ios kotlin-native kotlin-multiplatform moko coroutines kotlin-multiplatform-mobile

moko-permissions's Introduction

moko-permissions
GitHub license Download kotlin-version

Mobile Kotlin runtime permissions multiplatform controller

moko-permissions - Kotlin MultiPlatform library for providing runtime permissions on iOS & Android.

Table of Contents

Features

  • Permission - enumeration with primary types of device permissions
  • PermissionsController - handler for runtime permission requests can be used in the common code with lifecycle safety for Android
  • DeniedException and DeniedAlwaysException - exceptions to handle user denial of permissions
  • Compose Multiplatform support

Requirements

  • Gradle version 6.8+
  • Android API 16+
  • iOS version 12.0+

Installation

root build.gradle

allprojects {
    repositories {
      mavenCentral()
    }
}

project build.gradle

dependencies {
    commonMainApi("dev.icerock.moko:permissions:0.18.0")
    
    // compose multiplatform
    commonMainApi("dev.icerock.moko:permissions-compose:0.18.0") // permissions api + compose extensions
    
    commonTestImplementation("dev.icerock.moko:permissions-test:0.18.0")
}

List of supported permissions

The full list can be found in dev.icerock.moko.permissions.Permission enum.

  • Camera: Permission.CAMERA
  • Gallery: Permission.GALLERY
  • Storage read: Permission.STORAGE
  • Storage write: Permission.WRITE_STORAGE
  • Fine location: Permission.LOCATION
  • Coarse location: Permission.COARSE_LOCATION
  • Background location: Permission.BACKGROUND_LOCATION
  • Remote notifications: Permission.REMOTE_NOTIFICATION
  • Audio recording: Permission.RECORD_AUDIO
  • Bluetooth LE: Permission.BLUETOOTH_LE
  • Bluetooth Scan: Permission.BLUETOOTH_SCAN
  • Bluetooth Connect: Permission.BLUETOOTH_CONNECT
  • Bluetooth Advertise: Permission.BLUETOOTH_ADVERTISE
  • Motion: Permission.MOTION

Usage

Common code:

class ViewModel(val permissionsController: PermissionsController): ViewModel() {
    fun onPhotoPressed() {
        viewModelScope.launch {
            try {
                permissionsController.providePermission(Permission.GALLERY)
                // Permission has been granted successfully.
            } catch(deniedAlways: DeniedAlwaysException) {
                // Permission is always denied.
            } catch(denied: DeniedException) {
                // Permission was denied.
            }
        }
    }
}

Android:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
        
    val viewModel = getViewModel {
        // Pass the platform implementation of the permission controller to a common code.
        ViewModel(PermissionsController())
    }
    
    // Binds the permissions controller to the activity lifecycle.
    viewModel.permissionsController.bind(activity)
}

Compose:

@Composable
fun TestScreen() {
    val viewModel = getViewModel {
        // Pass the platform implementation of the permission controller to a common code.
        ViewModel(PermissionsController())
    }
    
    // Binds the permissions controller to the LocalLifecycleOwner lifecycle.
    BindEffect(viewModel.permissionsController)
}

iOS:

// Just pass the platform implementation of the permission controller to a common code.
let viewModel = ViewModel(permissionsController: PermissionsController())

Compose Multiplatform

@Composable
fun Sample() {
    val factory: PermissionsControllerFactory = rememberPermissionsControllerFactory()
    val controller: PermissionsController = remember(factory) { factory.createPermissionsController() }
    val coroutineScope: CoroutineScope = rememberCoroutineScope()
    
    Button(
        onClick = {
            coroutineScope.launch {
                controller.providePermission(Permission.REMOTE_NOTIFICATION)
            }
        }
    ) {
        Text(text = "give permissions")
    }
}

Or with moko-mvvm with correct configuration change handle on android:

@Composable
fun Sample() {
    val factory: PermissionsControllerFactory = rememberPermissionsControllerFactory()
    val viewModel: PermissionsViewModel = getViewModel(
        key = "permissions-screen",
        factory = viewModelFactory { PermissionsViewModel(factory.createPermissionsController()) }
    )
    
    BindEffect(viewModel.permissionsController)

    Button(onClick = viewModel::onButtonClick) {
        Text(text = "give permissions")
    }
}

class PermissionsViewModel(
    val permissionsController: PermissionsController
) : ViewModel() {
    fun onButtonClick() {
        viewModelScope.launch {
            permissionsController.providePermission(Permission.REMOTE_NOTIFICATION)
        }
    }
}

Samples

More examples can be found in the sample directory.

Set Up Locally

Contributing

All development (both new features and bug fixes) is performed in develop branch. This way master sources always contain sources of the most recently released version. Please send PRs with bug fixes to develop branch. Fixes to documentation in markdown files are an exception to this rule. They are updated directly in master.

The develop branch is pushed to master during release.

More detailed guide for contributers see in contributing guide.

License

Copyright 2019 IceRock MAG Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

moko-permissions's People

Contributors

alex009 avatar alexpogrebnyak avatar alibek228k avatar anton6tak avatar charlee-dev avatar charles-buildingcash avatar codlab avatar hellomr3 avatar jacobras avatar kovalandrew avatar nverinaud avatar qwert2603 avatar raedghazal avatar rezmike avatar tetraquark avatar timur-muminov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

moko-permissions's Issues

AppStore connect rejects due to undescribed permissions

We've just been denied publishing to App Store Connect due to missing descriptions of permissions we do not use. We only use the location and coarse location permissions in the application. However we are asked to add description for NSContactsUsageDescription, NSMotionUsageDescription and NSBluetoothAlwaysUsageDescription.

Apple says this: "While your app might not use these APIs a purpose string is still required."

One way to fix this is by adding the values in info.plist, but doing this the app description will include permissions it does not use in the AppStore.

Android - Tight coupling with fragments

Hello!

I'm just integrating your library into my app and essentially I'm having to write a custom implementation of the PermissionsController on Android, since I don't use fragments and don't want to use a FragmentActivity.

In order to do this I'll be decoupling the Fragment logic from the permissions logic to allow for the multiple approaches.

Would there be interest if I were to create a PR with these changes into this repository? I'm happy to contribute if so, but I don't want to do so if there's no appetite for it.

Best

Sam

Open settings does not work on iOS

The current implementation uses deprecated API.

Now it should looks like that:

UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)

PermissionsController and ViewModel not working in iOS

build.gradle.kts:

plugins {
alias(libs.plugins.multiplatform)
alias(libs.plugins.compose)
alias(libs.plugins.android.application)
alias(libs.plugins.libres)
alias(libs.plugins.buildConfig)
alias(libs.plugins.kotlinx.serialization)
alias(libs.plugins.com.google.ksp)
}

kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = BuildVersion.environment.jvmTarget
}
}
}

listOf(
    iosX64(),
    iosArm64(),
    iosSimulatorArm64(),
).forEach {
    it.binaries.framework {
        baseName = "presentationUi"
        isStatic = true

        export(libs.moko.mvvm)
        export(libs.kmp.notifier)
        export(libs.moko.permission.compose)
    }
}

sourceSets {
    commonMain.dependencies {
        implementation(project(mapOf("path" to ":core:ui")))
        implementation(project(mapOf("path" to ":core:common")))
        implementation(project(mapOf("path" to ":core:di")))
        implementation(project(mapOf("path" to ":presentation:viewmodels")))
        implementation(project(mapOf("path" to ":domain:models")))
        implementation(compose.runtime)
        implementation(compose.material3)
        implementation(compose.materialIconsExtended)
        implementation(libs.bundles.layer.core.ui)
        api(libs.webview.kmm.compose)
        api(libs.moko.mvvm)
        api(libs.kmp.notifier)
        api(libs.moko.permission)
        api(libs.moko.permission.compose)
    }

    commonTest.dependencies {
        implementation(kotlin("test"))
    }

    androidMain.dependencies {
        implementation(libs.bundles.android.core.ui)
    }

    iosMain.dependencies {
    }
}

}

android {
namespace = BuildVersion.environment.applicationId
compileSdk = BuildVersion.android.compileSdk

defaultConfig {
    minSdk = BuildVersion.android.minSdk
    targetSdk = BuildVersion.android.compileSdk

    applicationId = BuildVersion.environment.applicationId
    versionCode = BuildVersion.environment.appVersion
    versionName = BuildVersion.environment.appVersionCode
}
sourceSets["main"].apply {
    manifest.srcFile("src/androidMain/AndroidManifest.xml")
    res.srcDirs("src/androidMain/resources")
}
compileOptions {
    sourceCompatibility = BuildVersion.environment.javaVersion
    targetCompatibility = BuildVersion.environment.javaVersion
}
buildFeatures {
    compose = true
}
composeOptions {
    kotlinCompilerExtensionVersion = BuildVersion.environment.composeCompilerVersion
}

}

libres {
// https://github.com/Skeptick/libres#setup
generatedClassName = "MainResources"
generateNamedArguments = true
}

task("testClasses").doLast {
println("This is a dummy testClasses task")
}

buildConfig {
// BuildConfig configuration here.
// https://github.com/gmazzo/gradle-buildconfig-plugin#usage-in-kts
}

iosApp:

import UIKit
import SwiftUI
import presentationUi
import FirebaseCore
import FirebaseMessaging

class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure() //important
NotifierManager.shared.initialize(configuration: NotificationPlatformConfigurationIos.shared)

return true

}
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}

func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any]) async -> UIBackgroundFetchResult {
NotifierManager.shared.onApplicationDidReceiveRemoteNotification(userInfo: userInfo)
return UIBackgroundFetchResult.newData
}

}

@main
struct iosApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

var body: some Scene {
    WindowGroup {
        ContentView()
    }
}

}

struct ContentView: View {
var body: some View {
ComposeView().ignoresSafeArea(.all)
}
}

struct ComposeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
// Just pass the platform implementation of the permission controller to a common code.
let viewModel = ViewModel(permissionsController: PermissionsController())
MainKt.MainViewController(viewModel)
}

func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}

}

I receiving this:

error: java.lang.IllegalStateException: No file for dev.icerock.moko.permissions.compose/BindEffect|BindEffect(dev.icerock.moko.permissions.ios.PermissionsControllerProtocol){}[0] at org.jetbrains.kotlin.backend.konan.serialization.KonanIrLinker$KonanPartialModuleDeserializer.getFileNameOf(KonanIrlinker.kt:630) at org.jetbrains.kotlin.backend.konan.serialization.KonanIrLinker.getExternalDeclarationFileName(KonanIrlinker.kt:557) at org.jetbrains.kotlin.backend.konan.DependenciesTrackerImpl$add$2.invoke(DependenciesTracker.kt:110) at org.jetbrains.kotlin.backend.konan.DependenciesTrackerImpl$add$2.invoke(DependenciesTracker.kt:109) at org.jetbrains.kotlin.backend.konan.DependenciesTrackerImpl.computeFileOrigin(DependenciesTracker.kt:128) at org.jetbrains.kotlin.backend.konan.DependenciesTrackerImpl.add(DependenciesTracker.kt:109) at org.jetbrains.kotlin.backend.konan.DependenciesTrackerImpl.add(DependenciesTracker.kt:102) at org.jetbrains.kotlin.backend.konan.llvm.CodegenLlvmHelpers.externalFunction$backend_native_compiler(ContextUtils.kt:366) at org.jetbrains.kotlin.backend.konan.llvm.ContextUtils.getLlvmFunctionOrNull(ContextUtils.kt:195) at org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator.llvmFunctionOrNull(CodeGenerator.kt:35) at org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator.llvmFunction(CodeGenerator.kt:31) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGeneratorKt$generateObjCImp$1.invoke(ObjCExportCodeGenerator.kt:953) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGeneratorKt$generateObjCImp$1.invoke(ObjCExportCodeGenerator.kt:938) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGeneratorKt.generateObjCImp(ObjCExportCodeGenerator.kt:1068) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGeneratorKt.generateObjCImp(ObjCExportCodeGenerator.kt:938) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGeneratorKt.generateObjCImp$default(ObjCExportCodeGenerator.kt:929) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGeneratorKt.createMethodAdapter(ObjCExportCodeGenerator.kt:1504) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGeneratorKt.createMethodAdapter(ObjCExportCodeGenerator.kt:1487) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGeneratorKt.createFinalMethodAdapter(ObjCExportCodeGenerator.kt:1494) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGeneratorKt.createTypeAdapterForFileClass(ObjCExportCodeGenerator.kt:1551) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGeneratorKt.access$createTypeAdapterForFileClass(ObjCExportCodeGenerator.kt:1) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGenerator.generateTypeAdapters(ObjCExportCodeGenerator.kt:433) at org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGenerator.generate$backend_native_compiler(ObjCExportCodeGenerator.kt:441) at org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport.generate$backend_native_compiler(ObjCExport.kt:139) at org.jetbrains.kotlin.backend.konan.llvm.CodeGeneratorVisitor$visitModuleFragment$2.invoke(IrToBitcode.kt:481) at org.jetbrains.kotlin.backend.konan.llvm.CodeGeneratorVisitor$visitModuleFragment$2.invoke(IrToBitcode.kt:479) at org.jetbrains.kotlin.backend.konan.llvm.CodeGeneratorVisitor.runAndProcessInitializers(IrToBitcode.kt:455) at org.jetbrains.kotlin.backend.konan.llvm.CodeGeneratorVisitor.visitModuleFragment(IrToBitcode.kt:479) at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid$DefaultImpls.visitModuleFragment(IrElementVisitorVoid.kt:158) at org.jetbrains.kotlin.backend.konan.llvm.CodeGeneratorVisitor.visitModuleFragment(IrToBitcode.kt:244) at org.jetbrains.kotlin.backend.konan.llvm.CodeGeneratorVisitor.visitModuleFragment(IrToBitcode.kt:244) at org.jetbrains.kotlin.backend.konan.serialization.KonanIrModuleFragmentImpl.accept(KonanIrlinker.kt:1177) at org.jetbrains.kotlin.ir.visitors.IrVisitorsKt.acceptVoid(IrVisitors.kt:11) at org.jetbrains.kotlin.backend.konan.driver.phases.BitcodeGenerationKt$CodegenPhase$1.invoke(BitcodeGeneration.kt:75) at org.jetbrains.kotlin.backend.konan.driver.phases.BitcodeGenerationKt$CodegenPhase$1.invoke(BitcodeGeneration.kt:61) at org.jetbrains.kotlin.backend.konan.driver.phases.PhaseBuildersKt$createSimpleNamedCompilerPhase$3.phaseBody(PhaseBuilders.kt:54) at org.jetbrains.kotlin.backend.konan.driver.phases.PhaseBuildersKt$createSimpleNamedCompilerPhase$3.phaseBody(PhaseBuilders.kt:42) at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:207) at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:94) at org.jetbrains.kotlin.backend.konan.driver.PhaseEngine.runPhase(Machinery.kt:139) at org.jetbrains.kotlin.backend.konan.driver.PhaseEngine.runPhase$default(Machinery.kt:130) at org.jetbrains.kotlin.backend.konan.driver.phases.TopLevelPhasesKt.runCodegen(TopLevelPhases.kt:378) at org.jetbrains.kotlin.backend.konan.driver.phases.TopLevelPhasesKt.runBackendCodegen(TopLevelPhases.kt:321) at org.jetbrains.kotlin.backend.konan.driver.phases.TopLevelPhasesKt.compileModule(TopLevelPhases.kt:251) at org.jetbrains.kotlin.backend.konan.driver.phases.TopLevelPhasesKt.runBackend$lambda$9$runAfterLowerings(TopLevelPhases.kt:105) at org.jetbrains.kotlin.backend.konan.driver.phases.TopLevelPhasesKt.runBackend(TopLevelPhases.kt:124) at org.jetbrains.kotlin.backend.konan.driver.DynamicCompilerDriver.produceObjCFramework(DynamicCompilerDriver.kt:76) at org.jetbrains.kotlin.backend.konan.driver.DynamicCompilerDriver.access$produceObjCFramework(DynamicCompilerDriver.kt:31) at org.jetbrains.kotlin.backend.konan.driver.DynamicCompilerDriver$run$1$1$1.invoke(DynamicCompilerDriver.kt:42) at org.jetbrains.kotlin.backend.konan.driver.DynamicCompilerDriver$run$1$1$1.invoke(DynamicCompilerDriver.kt:36) at org.jetbrains.kotlin.backend.konan.driver.PhaseEngine$Companion$startTopLevel$topLevelPhase$1.phaseBody(Machinery.kt:98) at org.jetbrains.kotlin.backend.konan.driver.PhaseEngine$Companion$startTopLevel$topLevelPhase$1.phaseBody(Machinery.kt:92) at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:207) at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:94) at org.jetbrains.kotlin.backend.konan.driver.PhaseEngine$Companion.startTopLevel(Machinery.kt:105) at org.jetbrains.kotlin.backend.konan.driver.DynamicCompilerDriver.run(DynamicCompilerDriver.kt:36) at org.jetbrains.kotlin.backend.konan.KonanDriver.run(KonanDriver.kt:118) at org.jetbrains.kotlin.cli.bc.K2Native.runKonanDriver(K2Native.kt:151) at org.jetbrains.kotlin.cli.bc.K2Native.doExecute(K2Native.kt:69) at org.jetbrains.kotlin.cli.bc.K2Native.doExecute(K2Native.kt:36) at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:104) at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:48) at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101) at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:79) at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:43) at org.jetbrains.kotlin.cli.common.CLITool$Companion.doMainNoExit(CLITool.kt:180) at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithRenderer$1.invoke(K2Native.kt:188) at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithRenderer$1.invoke(K2Native.kt:187) at org.jetbrains.kotlin.util.UtilKt.profileIf(Util.kt:22) at org.jetbrains.kotlin.util.UtilKt.profile(Util.kt:16) at org.jetbrains.kotlin.cli.bc.K2Native$Companion.mainNoExitWithRenderer(K2Native.kt:187) at org.jetbrains.kotlin.cli.bc.K2NativeKt.mainNoExitWithXcodeRenderer(K2Native.kt:206) at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMainWithXcodeRenderer$1.invoke(main.kt:53) at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMainWithXcodeRenderer$1.invoke(main.kt:53) at org.jetbrains.kotlin.cli.utilities.MainKt.mainImpl(main.kt:20) at org.jetbrains.kotlin.cli.utilities.MainKt.inProcessMain(main.kt:58) at org.jetbrains.kotlin.cli.utilities.MainKt.daemonMainWithXcodeRenderer(main.kt:53) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.jetbrains.kotlin.compilerRunner.KotlinToolRunner.runInProcess(KotlinToolRunner.kt:198) at org.jetbrains.kotlin.compilerRunner.KotlinToolRunner.run(KotlinToolRunner.kt:135) at org.jetbrains.kotlin.compilerRunner.KotlinNativeToolRunner.run(nativeToolRunners.kt:146) at org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink$compile$1.invoke(KotlinNativeLink.kt:414) at org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink$compile$1.invoke(KotlinNativeLink.kt:364) at org.jetbrains.kotlin.compilerRunner.ReportUtilsKt.addBuildMetricsForTaskAction(reportUtils.kt:276) at org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink.compile(KotlinNativeLink.kt:364) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:125) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29) at org.gradle.api.internal.tasks.execution.TaskExecution$3.run(TaskExecution.java:248) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:73) at org.gradle.api.internal.tasks.execution.TaskExecution.executeAction(TaskExecution.java:233) at org.gradle.api.internal.tasks.execution.TaskExecution.executeActions(TaskExecution.java:216) at org.gradle.api.internal.tasks.execution.TaskExecution.executeWithPreviousOutputFiles(TaskExecution.java:199) at org.gradle.api.internal.tasks.execution.TaskExecution.execute(TaskExecution.java:166) at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:105) at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:44) at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:59) at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:56) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:78) at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:56) at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:44) at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:67) at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:37) at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:41) at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:74) at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55) at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:50) at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:28) at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.executeDelegateBroadcastingChanges(CaptureStateAfterExecutionStep.java:100) at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:72) at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:50) at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:40) at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:29) at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:179) at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$1(BuildCacheStep.java:70) at org.gradle.internal.Either$Right.fold(Either.java:175) at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:59) at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:68) at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:46) at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:36) at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:25) at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:36) at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:22) at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:91) at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$2(SkipUpToDateStep.java:55) at java.base/java.util.Optional.orElseGet(Optional.java:364) at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:55) at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:37) at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:65) at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:36) at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37) at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27) at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:77) at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:38) at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:108) at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:55) at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:71) at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:45) at org.gradle.internal.execution.steps.SkipEmptyWorkStep.executeWithNonEmptySources(SkipEmptyWorkStep.java:177) at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:86) at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:53) at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:32) at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:21) at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38) at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:36) at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:23) at org.gradle.internal.execution.steps.CleanupStaleOutputsStep.execute(CleanupStaleOutputsStep.java:75) at org.gradle.internal.execution.steps.CleanupStaleOutputsStep.execute(CleanupStaleOutputsStep.java:41) at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.lambda$execute$2(ExecuteWorkBuildOperationFiringStep.java:66) at java.base/java.util.Optional.orElseGet(Optional.java:364) at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:66) at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:38) at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:32) at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:293) at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30) at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:21) at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37) at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27) at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:47) at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:34) at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:64) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:145) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:134) at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46) at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51) at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57) at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74) at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:78) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52) at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314) at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:80) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303) at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:463) at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:380) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:833)

Android is working well.

Missing authorization status checks for iOS remote notifications

There's an issue in the getPermissionState function for iOS remote notifications where it throws exception in case of UNAuthorizationStatusProvisional and UNAuthorizationStatusEphemeral which both provide ability to receive notifications.

Here's info from Apple docs

UNAuthorizationStatusProvisional - The application is provisionally authorized to post noninterruptive user notifications.
UNAuthorizationStatusEphemeral - The app is authorized to schedule or receive notifications for a limited amount of time.

When checking status library should return PermissionState.Granted for these two statuses.

override suspend fun getPermissionState(): PermissionState {
    // removed code for clarity
    return when (status) {
        UNAuthorizationStatusAuthorized -> PermissionState.Granted
        UNAuthorizationStatusNotDetermined -> PermissionState.NotDetermined
        UNAuthorizationStatusDenied -> PermissionState.DeniedAlways
        else -> throw IllegalStateException("unknown push authorization status $status")
    }
}

Fatal Exception: java.util.NoSuchElementException Array is empty.

Some users of my app experience the crash with the latest moko permissions - 0.10.1

Fatal Exception: java.util.NoSuchElementException: Array is empty.
       at kotlin.collections.ArraysKt___ArraysKt.first(ArraysKt___ArraysKt.java:1013)
       at dev.icerock.moko.permissions.ResolverFragment.onRequestPermissionsResult(ResolverFragment.java:54)
       at androidx.fragment.app.FragmentManager$11.onActivityResult(FragmentManager.java:2967)
       at androidx.fragment.app.FragmentManager$11.onActivityResult(FragmentManager.java:2939)
       at androidx.activity.result.ActivityResultRegistry.doDispatch(ActivityResultRegistry.java:392)
       at androidx.activity.result.ActivityResultRegistry.dispatchResult(ActivityResultRegistry.java:351)
       at androidx.activity.ComponentActivity.onRequestPermissionsResult(ComponentActivity.java:658)
       at androidx.fragment.app.FragmentActivity.onRequestPermissionsResult(FragmentActivity.java:636)
       at android.app.Activity.requestPermissions(Activity.java:5278)
       at androidx.core.app.ActivityCompat.requestPermissions(ActivityCompat.java:516)
       at androidx.activity.ComponentActivity$2.onLaunch(ComponentActivity.java:190)
       at androidx.activity.result.ActivityResultRegistry$3.launch(ActivityResultRegistry.java:226)
       at dev.icerock.moko.permissions.PermissionsControllerImpl.providePermission(PermissionsControllerImpl.java:47)
       at androidx.activity.result.ActivityResultLauncher.launch(ActivityResultLauncher.java:47)
       at androidx.fragment.app.FragmentManager.launchRequestPermissions(FragmentManager.java:3044)
       at androidx.fragment.app.Fragment.requestPermissions(Fragment.java:1561)
       at dev.icerock.moko.permissions.ResolverFragment.requestPermission(ResolverFragment.java:36)
       at dev.icerock.moko.permissions.PermissionsControllerImpl.providePermission(PermissionsControllerImpl.java:60)
       at app.inspiry.edit.EditActivity$pickNewImage$1.invokeSuspend(EditActivity.java:1249)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(BaseContinuationImpl.java:33)
       at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuationKt.java:367)
       at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(CancellableKt.java:30)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.startCoroutineImpl(BuildersKt__Builders_commonKt.java:192)
       at kotlinx.coroutines.BuildersKt.startCoroutineImpl(BuildersKt.java:1)
       at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.java:134)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(BuildersKt__Builders_commonKt.java:56)
       at kotlinx.coroutines.BuildersKt.launch(BuildersKt.java:1)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(BuildersKt__Builders_commonKt.java:47)
       at kotlinx.coroutines.BuildersKt.launch$default(BuildersKt.java:1)
       at app.inspiry.edit.EditActivity.pickNewImage(EditActivity.java:1247)
       at app.inspiry.views.media.InnerMediaViewAndroid.setPickImage$lambda-2(InnerMediaViewAndroid.java:182)
       at app.inspiry.views.media.InnerMediaViewAndroid$$InternalSyntheticLambda$0$3c79bbfbf7f8a6c4d25cb6ad0b8c5c8e0a90bdf875c794783a7a740bccad53f6$0.onClick$bridge(InnerMediaViewAndroid.java:9)
       at android.view.View.performClick(View.java:8160)
       at android.view.View.performClickInternal(View.java:8137)
       at android.view.View.access$3700(View.java:888)
       at android.view.View$PerformClick.run(View.java:30236)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:246)
       at android.app.ActivityThread.main(ActivityThread.java:8512)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1139)

Permission for POST_NOTIFICATION

Hi, Android API level 33 requires POST_NOTIFICATION permission granted to be able to create push notification, wondering if you guys has any plan?

Write_External_Storage Android Api 33

Hi,
in Android Api Level 33 it is not possible to grant the "Write_External_Storage" permission.
It would be nice if it would just be skipped if you try to request it.

Implement go to settings for enable push on android

now if request remote notifications on android - we got no action. but user can disable notifications in settings, and after #20 we can check this case by isPermissionGranted. we should add logic for request user go to settings and enable notifications on android

iOS Permissions are granted but permission state is NotDetermined or False

Hello,

using multiplatform compose.

Requesting permission (via providePermission()) works โœ”๏ธ but getPermissionState always returns NotDetermined and isPermissionGranted returns false

I call this code in one of my UIs to ensure bluetooth permissions are set. iOS correctly asks the user to grant permissions. But the controller never seems to register that these have been granted.

`fun RequestBluetoothPermission(executeAfter: () -> Unit) {

val factory: PermissionsControllerFactory = rememberPermissionsControllerFactory()
val controller: PermissionsController = remember(factory) { factory.createPermissionsController() }
val coroutineScope: CoroutineScope = rememberCoroutineScope()

BindEffect(controller)

coroutineScope.launch {
    try {
        Logger.i("Permission state BLUETOOTH_SCAN: ${controller.getPermissionState(Permission.BLUETOOTH_SCAN)}")
        Logger.i("Permission state BLUETOOTH_CONNECT: ${controller.getPermissionState(Permission.BLUETOOTH_CONNECT)}")

        controller.providePermission(Permission.BLUETOOTH_SCAN)
        controller.providePermission(Permission.BLUETOOTH_CONNECT)

        Logger.i("After Permission state BLUETOOTH_SCAN: ${controller.getPermissionState(Permission.BLUETOOTH_SCAN)}")
        Logger.i("After Permission state BLUETOOTH_CONNECT: ${controller.getPermissionState(Permission.BLUETOOTH_CONNECT)}")

        Logger.i("After Permission granted BLUETOOTH_SCAN: ${controller.getPermissionState(Permission.BLUETOOTH_SCAN)}")
        Logger.i("After Permission granted BLUETOOTH_CONNECT: ${controller.getPermissionState(Permission.BLUETOOTH_CONNECT)}")
        
        if (controller.isPermissionGranted(Permission.BLUETOOTH_CONNECT) &&
            controller.isPermissionGranted(Permission.BLUETOOTH_SCAN) ) {
            executeAfter()
        }
    } catch (e: Exception) {
        e.printStackTrace()
        Logger.e(e.message.toString())
    }
}

}
`

btw this works on Android but not on iOS 17. Been testing with an iPhone 13.

Anything that I am missing?

Update readme

  • logo (?)
  • description with goals
  • gif with clear example in action
  • features - permission request, lifecycle aware (if called from out of lifecycle coroutine scope)
  • simple usage (just provide permission)
  • installation steps
  • detailed usage (handle exceptions)
  • how to contribute
  • license block

Support Contacts

Can you provide Contacts Permission in this project? I'm bad in English. You project help me so much.

How to preview Composable with BindEffect?

@Preview @Composable fun MainScreenPreview() { BindEffect(permissionsController) }

When run preview task for Android exception has been thrown:
java.lang.ClassCastException: androidx.compose.ui.tooling.PreviewActivity cannot be cast to androidx.fragment.app.FragmentActivity at dev.icerock.moko.permissions.compose.BindEffect_androidKt$BindEffect$1.invokeSuspend(BindEffect.android.kt:24)

Reason of exception is cast context to FragmentActivity in BindEffect.

I don't know is there the way to use FragmentActivity to preview Composable. Now my solution is to add parameter to skip binding. It's bad for code readability. May be someone propose better solution?

Request multiple permissions at once

Sometimes it is needed to request multiple permissions at once, e.g. Bluetooth + Location. It would be nice to have this case covered by a (separate) method.

RequestCanceledException when permissions requests in 2 coroutines

I added this case in my repository.
Link: https://github.com/timur-muminov/Moko-permissions-bug/tree/case2

FATALEXCEPTION:mainProcess:com.myapplication3,PID:14529 dev.icerock.moko.permissions.RequestCanceledException atdev.icerock.moko.permissions.ResolverFragment.onRequestPermissionsResult(ResolverFragment.kt:52) atandroidx.fragment.app.FragmentManager$11.onActivityResult(FragmentManager.java:2967) atandroidx.fragment.app.FragmentManager$11.onActivityResult(FragmentManager.java:2939) atandroidx.activity.result.ActivityResultRegistry.doDispatch(ActivityResultRegistry.java:418) atandroidx.activity.result.ActivityResultRegistry.dispatchResult(ActivityResultRegistry.java:375) atandroidx.activity.ComponentActivity.onRequestPermissionsResult(ComponentActivity.java:802) atandroidx.fragment.app.FragmentActivity.onRequestPermissionsResult(FragmentActivity.java:636) atandroid.app.Activity.requestPermissions(Activity.java:5361) atandroidx.core.app.ActivityCompat$Api23Impl.requestPermissions(ActivityCompat.java:893) atandroidx.core.app.ActivityCompat.requestPermissions(ActivityCompat.java:557) atandroidx.activity.ComponentActivity$2.onLaunch(ComponentActivity.java:216) atandroidx.activity.result.ActivityResultRegistry$3.launch(ActivityResultRegistry.java:246) atandroidx.activity.result.ActivityResultLauncher.launch(ActivityResultLauncher.java:47) atandroidx.fragment.app.FragmentManager.launchRequestPermissions(FragmentManager.java:3044)

Invalid permission for REMOTE_NOTIFICATION on iOS

The issue

When calling permissionsController.getPermissionState() with Permission.REMOTE_NOTIFICATION, I always get the same value : DeniedAlways.

I'm testing it by deleting completely my app for my test phone.
At the time the call is made no authorization has been requested by the user yet.
In fact, just to be sure I also checked into the settings of the app that the Notification setting row does not appear.

What I tested

I first tested to prompt the notification status by logging it in my iOS app that way :

    let currentCenter = UNUserNotificationCenter.current()
    currentCenter.getNotificationSettings { value in
      print(value.authorizationStatus)
    }

Which gives me this output :

<UNNotificationSettings: 0x28002ac70; authorizationStatus: NotDetermined, notificationCenterSetting: NotSupported, soundSetting: NotSupported, badgeSetting: NotSupported, lockScreenSetting: NotSupported, carPlaySetting: NotSupported, announcementSetting: NotSupported, criticalAlertSetting: NotSupported, timeSensitiveSetting: NotSupported, alertSetting: NotSupported, scheduledDeliverySetting: NotSupported, directMessagesSetting: NotSupported, alertStyle: None, groupingSetting: Default providesAppNotificationSettings: No>

Everything is fine here so I moved on with trying to get the value myself from my KMM/iosMain code :

    fun getPermissionState() {
        val currentCenter = UNUserNotificationCenter.currentNotificationCenter()
        currentCenter.getNotificationSettingsWithCompletionHandler { settings: UNNotificationSettings? ->
            Log.debug(settings?.authorizationStatus.toString()) // returns 0 for UNAuthorizationStatus.notDetermined
        }
    }

I get the right result even in the kmm side, while at the same time I still get .DeniedAlways from moko.permissions

Any idea where this issue could originate from ?

Add jvm permissions

Hello, please implement jvm permission

at least make an empty implementation (stub) so that we can use your library directly in shared /commonMain

Error providing permissions with kotlin 1.7.20

I managed to configure moko-permissions library in my project and is working fine in iOS and Android for Gallery Permission, but when I try to do the same with Remote notifications permissions I'm receiving the error below in iOS (in Android is working fine):
The same error is throw either in getPermissionState and providePermission()

Uncaught Kotlin exception: kotlin.IllegalArgumentException: Failed requirement.
    at 0   myApp                          0x1033257e3        kfun:kotlin.Throwable#<init>(kotlin.String?){} + 123 
    at 1   myApp                          0x10331da4b        kfun:kotlin.Exception#<init>(kotlin.String?){} + 119 
    at 2   myApp                          0x10331dd17        kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 119 
    at 3   myApp                          0x10331e237        kfun:kotlin.IllegalArgumentException#<init>(kotlin.String?){} + 119 
    at 4   myApp                          0x1033351b3        kfun:kotlin.native.concurrent.Continuation1#invoke(1:0){} + 903 
    at 5   myApp                          0x1033352c3        kfun:kotlin.native.concurrent.Continuation1#$<bridge-UNNN>invoke(1:0){}(1:0){}kotlin.Any + 123 
    at 6   myApp                          0x103608183        _6465762e696365726f636b2e6d6f6b6f3a7065726d697373696f6e73_knbridge97 + 251 
    at 7   libdispatch.dylib                   0x1059946d3        _dispatch_call_block_and_release + 31 
    at 8   libdispatch.dylib                   0x1059963b3        _dispatch_client_callout + 19 
    at 9   libdispatch.dylib                   0x10599e53f        _dispatch_lane_serial_drain + 987 
    at 10  libdispatch.dylib                   0x10599f2c3        _dispatch_lane_invoke + 479 
    at 11  libdispatch.dylib                   0x1059abe1f        _dispatch_workloop_worker_thread + 915 
    at 12  libsystem_pthread.dylib             0x1f37230f3        _pthread_wqthread + 287 
    at 13  libsystem_pthread.dylib             0x1f3722e93        start_wqthread + 7 
dyld4 config: DYLD_LIBRARY_PATH=/usr/lib/system/introspection DYLD_INSERT_LIBRARIES=/Developer/usr/lib/libBacktraceRecording.dylib:/Developer/usr/lib/libMainThreadChecker.dylib:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib

Cannot find 'PermissionsController' in scope

I added moko-permissions library to my shared KMM module and Android module and it's working fine.
But when I'm trying to add it to the iOS project I get this error whenever I want to create the PermissionsController():

Cannot find 'PermissionsController' in scope

I'm pretty sure I added the proper import. Also I reviewed the Sample and I'm not seeing any difference.
Any idea of what I'm leaving out?
Thanks

Implement sample

  • provide permission
  • handle exceptions
  • support autorotate of screen (show lifecycle aware)
  • show placeholder in case "user press always denied"

Incorrect check for iOS remote notifications permission

Library is using incorrect API to check iOS push notification permissions.

Following code uses registeredForRemoteNotifications to check current permission state, but this function in some cases always returns true, regardless if the app actually called registerForRemoteNotifications.

override fun isPermissionGranted(): Boolean {
    return UIApplication.sharedApplication().registeredForRemoteNotifications
}

Apple docs say that The value returned by this method takes into account the userโ€™s preferences for receiving remote notifications but I never seen iOS project use this call to check permission state. Also after a bit of testing in clean project this does not seem to be true.

Proper approach would be to always check authorization status from notifications settings.
I added following function as temp solution (it just calls getPermissionState and maps the output into Boolean)

suspend fun isPermissionGranted(permission: Permission): Boolean

Problem with this solution is that it does not work with current library API because isPermissionGranted is not a suspend function. I'm not really sure what would be a good solution to this problem, one idea that would work is to add semaphore (in case of remote notification check) and make a call to getNotificationSettingsWithCompletionHandler sync.

Permission requests synchronization

If couple of permission requests run in different scopes in parallel, at least one request will fail with an exception.

W/Activity: Can request only one set of permissions at a time
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: dev.icerock.moko.samples.permissions.debug, PID: 27897
    java.util.NoSuchElementException: Array is empty.
        at kotlin.collections.ArraysKt___ArraysKt.first(_Arrays.kt:1013)
        at dev.icerock.moko.permissions.ResolverFragment.onRequestPermissionsResult(ResolverFragment.kt:54)
        at androidx.fragment.app.FragmentActivity.onRequestPermissionsResult(FragmentActivity.java:769)
        ...

Simpliest example of such case may look like that:

SampleViewModel.kt

private fun requestPermission() {
    viewModelScope.launch {
        try {
            // Calls suspend function in a coroutine to request some permission.
            permissionsController.providePermission(Permission.RECORD_AUDIO)
            // If there are no exceptions, permission has been granted successfully.
            eventsDispatcher.dispatchEvent { onSuccess() }
        } catch (deniedAlwaysException: DeniedAlwaysException) {
            eventsDispatcher.dispatchEvent { onDeniedAlways(deniedAlwaysException) }
        } catch (deniedException: DeniedException) {
            eventsDispatcher.dispatchEvent { onDenied(deniedException) }
        }
    }
    viewModelScope.launch {
        try {
            // Calls suspend function in a coroutine to request some permission.
            permissionsController.providePermission(Permission.CAMERA)
            // If there are no exceptions, permission has been granted successfully.
            eventsDispatcher.dispatchEvent { onSuccess() }
        } catch (deniedAlwaysException: DeniedAlwaysException) {
            eventsDispatcher.dispatchEvent { onDeniedAlways(deniedAlwaysException) }
        } catch (deniedException: DeniedException) {
            eventsDispatcher.dispatchEvent { onDenied(deniedException) }
        }
    }
}

In real-world cases this may be caused, for example, by requesting couple of different permissions in separate application modules. Moreover, LocationTracker from moko-geo is affected too.

Although problem can be solved by providing synchronization manually, it wold be nice to implement this in library. I think the best possible option is synchronize request across all PermissionController instances, but it will be great to have such thing for at least one instance.

add support of Android BLUETOOTH_CONNECT

Does this library support Android OS12?

Getting security crash about android.permission.BLUETOOTH_CONNECT

I'm using the below 2 permissions and the app is crashing,

LOCATION,
BLUETOOTH_LE,

Add permission type for Remote Notifications

List of available permissions is

enum class Permission {
    CAMERA,
    GALLERY,
    STORAGE,
    LOCATION,
    COARSE_LOCATION,
    BLUETOOTH_LE
}

Need to add REMOTE_NOTIFICATIONS type and implementation.

Error handling suggestion: When `bind` function is not called, calling any permission function doesn't do anything

I noticed that when bind/BindEffect function is not called, calling any permission function providePermission / isPermissionGranted / getPermissionState doesn't do anything and it keeps waiting for bind function to be called which is really hard to spot and can be frustrating to know why it happened.

the issue happens here

    private suspend fun awaitFragmentManager(): FragmentManager {
        val fragmentManager: FragmentManager? = fragmentManagerHolder.value
        if (fragmentManager != null) return fragmentManager

        return fragmentManagerHolder.filterNotNull().first()
    }

if fragmentManager is null, the function is suspended on .first() and keeps waiting forever.

Solution

the solution here can be that we add a timeout to the flow

 return withTimeoutOrNull(A_REASONABLE_TIMEOUT_DURATION) {
            fragmentManagerHolder.filterNotNull().first()
        } ?: throw IllegalStateException("some useful error")

which informs the developer of what the issue is and allows them to fix it quickly, if this is an accepted solution I can prepare a PR right away.

thanks.

permission not working on android but works on ios compose multiplatform

I am using voyager library for compose multiplatform, here is the dependencies on shared

implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion")
api("dev.icerock.moko:permissions-compose:0.16.0")

gradle properties
kotlin.version=1.8.20
agp.version=7.4.2
compose.version=1.4.1

The issue is the Log inside coroutine scope is called and after that no code run, there is no exception everything is blank or prints nothing on logger on android studio.I beleive something is wrong with controller.isPermissionGranted but in IOS It works properly

Android Log :
2023-07-07 16:06:22.577 744-744 test com.myapplication.MyApplication V on login button clicked.
2023-07-07 16:06:22.578 744-744 test com.myapplication.MyApplication V inside coroutine scope

IOS Log:
07-07 16:16:44.251 ๐Ÿ’œ VERBOSE test - on login button clicked.
07-07 16:16:44.251 ๐Ÿ’œ VERBOSE test - inside coroutine scope
07-07 16:16:44.262 ๐Ÿ’œ VERBOSE test - isGranted true

Android Manifest:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />

import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import dev.icerock.moko.permissions.DeniedAlwaysException
import dev.icerock.moko.permissions.DeniedException
import dev.icerock.moko.permissions.Permission
import dev.icerock.moko.permissions.PermissionsController
import dev.icerock.moko.permissions.compose.PermissionsControllerFactory
import dev.icerock.moko.permissions.compose.rememberPermissionsControllerFactory

val factory: PermissionsControllerFactory = rememberPermissionsControllerFactory()
    val controller: PermissionsController =
        remember(factory) { factory.createPermissionsController() }
    val coroutineScope: CoroutineScope = rememberCoroutineScope()

  Button(
            onClick = {
                //  onValid()
                AppLogger.log("on login button clicked.")
                coroutineScope.launch {
                    AppLogger.log("inside coroutine scope")
                    try {
                        val isGranted = controller.isPermissionGranted(Permission.CAMERA)
                        AppLogger.log("isGranted $isGranted")
                        if (!isGranted) {
                            // need to add permission on android manifest and info.plist on ios xcode
                            // https://iosdevcenters.blogspot.com/2016/09/infoplist-privacy-settings-in-ios-10.html
                            val permissionState =
                                controller.getPermissionState(Permission.CAMERA)
                            AppLogger.log(permissionState.name)
                            controller.providePermission(Permission.CAMERA)
                        }
                    } catch (deniedAlways: DeniedAlwaysException) {
                        AppLogger.log(deniedAlways.message.toString())
                        // Permission is always denied.
                        controller.openAppSettings()
                    } catch (denied: DeniedException) {
                        // Permission was denied.
                        AppLogger.log(denied.message.toString())
                        controller.openAppSettings()
                    }

                    //   controller.openAppSettings()
                    //   controller.providePermission(Permission.CAMERA)
                }
            },
            modifier = Modifier
                .fillMaxWidth()
                .padding(top = 16.dp)
        ) {
            Text(
                stringResource(MR.strings.sign_in),
                fontWeight = FontWeight.Bold,
                fontSize = MaterialTheme.typography.h6.fontSize
            )
        }

Exception: RequestCanceledException is thrown on App launch (Andorid).

Trying to show notification permission as soon as app starts.
My device throw dev.icerock.moko.permissions.RequestCanceledException Activity says Can request only one set of permissions at a time

  • Device: Samsung Galaxy A33 (Android 13)
  • How to reproduce:
    Reproducible only when app installs and launches for first time. After that subsequent launches does not throw exception.

I have attached sample code and error logs. Please check this and tell me if anything from my end is not well.

code

@Composable
fun App() {

    val factory = rememberPermissionsControllerFactory()
    val controller = remember(factory) { factory.createPermissionsController() }
    val scope = rememberCoroutineScope()

    BindEffect(controller)
    LaunchedEffect(key1 = true) {
        try {
            controller.providePermission(Permission.REMOTE_NOTIFICATION)
        } catch (e: Exception) {
            println("Line 54, e: ${e.getScopeName()}")
            e.printStackTrace()
        }
    }

    Text("Hello Student $name")
}

error Logs:

2023-11-15 14:16:24.887 23794-23794 Activity                com.penpencil.parent.android         W  Can request only one set of permissions at a time
2023-11-15 14:16:24.889 23794-23794 System.out              com.penpencil.parent.android         I  Line 54, e: q:'dev.icerock.moko.permissions.RequestCanceledException'
2023-11-15 14:16:24.890 23794-23794 System.err              com.penpencil.parent.android         W  dev.icerock.moko.permissions.RequestCanceledException
2023-11-15 14:16:24.890 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment.requestPermissionLauncher$lambda$1(ResolverFragment.kt:32)
2023-11-15 14:16:24.891 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment.$r8$lambda$xVWh1OMY-jV8sGm90Z_Lnoc0rAA(Unknown Source:0)
2023-11-15 14:16:24.891 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment$$ExternalSyntheticLambda0.onActivityResult(Unknown Source:4)
2023-11-15 14:16:24.891 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.activity.result.ActivityResultRegistry.doDispatch(ActivityResultRegistry.java:418)
2023-11-15 14:16:24.892 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.activity.result.ActivityResultRegistry.dispatchResult(ActivityResultRegistry.java:375)
2023-11-15 14:16:24.892 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.activity.ComponentActivity.onRequestPermissionsResult(ComponentActivity.java:844)
2023-11-15 14:16:24.892 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.fragment.app.FragmentActivity.onRequestPermissionsResult(FragmentActivity.java:490)
2023-11-15 14:16:24.892 23794-23794 System.err              com.penpencil.parent.android         W  	at android.app.Activity.requestPermissions(Activity.java:5486)
2023-11-15 14:16:24.893 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.core.app.ActivityCompat$Api23Impl.requestPermissions(ActivityCompat.java:944)
2023-11-15 14:16:24.893 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.core.app.ActivityCompat.requestPermissions(ActivityCompat.java:560)
2023-11-15 14:16:24.893 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.activity.ComponentActivity$2.onLaunch(ComponentActivity.java:237)
2023-11-15 14:16:24.893 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.activity.result.ActivityResultRegistry$2.launch(ActivityResultRegistry.java:175)
2023-11-15 14:16:24.894 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.fragment.app.Fragment$10.launch(Fragment.java:3621)
2023-11-15 14:16:24.894 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.activity.result.ActivityResultLauncher.launch(ActivityResultLauncher.java:47)
2023-11-15 14:16:24.894 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment$requestPermission$1$1.invokeSuspend(ResolverFragment.kt:79)
2023-11-15 14:16:24.894 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment$requestPermission$1$1.invoke(Unknown Source:8)
2023-11-15 14:16:24.895 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment$requestPermission$1$1.invoke(Unknown Source:4)
2023-11-15 14:16:24.895 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1$1$1.invokeSuspend(RepeatOnLifecycle.kt:111)
2023-11-15 14:16:24.895 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1$1$1.invoke(Unknown Source:8)
2023-11-15 14:16:24.896 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1$1$1.invoke(Unknown Source:4)
2023-11-15 14:16:24.896 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:78)
2023-11-15 14:16:24.896 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:264)
2023-11-15 14:16:24.896 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1.invokeSuspend(RepeatOnLifecycle.kt:110)
2023-11-15 14:16:24.897 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
2023-11-15 14:16:24.897 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
2023-11-15 14:16:24.897 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:68)
2023-11-15 14:16:24.897 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:375)
2023-11-15 14:16:24.898 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
2023-11-15 14:16:24.898 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
2023-11-15 14:16:24.898 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
2023-11-15 14:16:24.899 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
2023-11-15 14:16:24.899 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
2023-11-15 14:16:24.899 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
2023-11-15 14:16:24.900 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
2023-11-15 14:16:24.900 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
2023-11-15 14:16:24.900 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment.requestPermission(ResolverFragment.kt:58)
2023-11-15 14:16:24.900 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.PermissionsControllerImpl.providePermission(PermissionsControllerImpl.kt:57)
2023-11-15 14:16:24.901 23794-23794 System.err              com.penpencil.parent.android         W  	at com.penpencil.parent.AppKt$App$2.invokeSuspend(App.kt:54)
2023-11-15 14:16:24.901 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
2023-11-15 14:16:24.902 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
2023-11-15 14:16:24.902 23794-23794 System.err              com.penpencil.parent.android         W  	at android.os.Handler.handleCallback(Handler.java:942)
2023-11-15 14:16:24.903 23794-23794 System.err              com.penpencil.parent.android         W  	at android.os.Handler.dispatchMessage(Handler.java:99)
2023-11-15 14:16:24.903 23794-23794 System.err              com.penpencil.parent.android         W  	at android.os.Looper.loopOnce(Looper.java:226)
2023-11-15 14:16:24.903 23794-23794 System.err              com.penpencil.parent.android         W  	at android.os.Looper.loop(Looper.java:313)
2023-11-15 14:16:24.903 23794-23794 System.err              com.penpencil.parent.android         W  	at android.app.ActivityThread.main(ActivityThread.java:8762)
2023-11-15 14:16:24.903 23794-23794 System.err              com.penpencil.parent.android         W  	at java.lang.reflect.Method.invoke(Native Method)
2023-11-15 14:16:24.904 23794-23794 System.err              com.penpencil.parent.android         W  	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604)
2023-11-15 14:16:24.904 23794-23794 System.err              com.penpencil.parent.android         W  	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)
2023-11-15 14:16:24.910 23794-23794 System.out              com.penpencil.parent.android         I  Line 54, e: q:'dev.icerock.moko.permissions.RequestCanceledException'
2023-11-15 14:16:24.910 23794-23794 System.err              com.penpencil.parent.android         W  dev.icerock.moko.permissions.RequestCanceledException
2023-11-15 14:16:24.911 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment$requestPermission$1$1.invokeSuspend(ResolverFragment.kt:73)
2023-11-15 14:16:24.911 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment$requestPermission$1$1.invoke(Unknown Source:8)
2023-11-15 14:16:24.911 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment$requestPermission$1$1.invoke(Unknown Source:4)
2023-11-15 14:16:24.912 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1$1$1.invokeSuspend(RepeatOnLifecycle.kt:111)
2023-11-15 14:16:24.912 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1$1$1.invoke(Unknown Source:8)
2023-11-15 14:16:24.912 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1$1$1.invoke(Unknown Source:4)
2023-11-15 14:16:24.912 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:78)
2023-11-15 14:16:24.913 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:264)
2023-11-15 14:16:24.913 23794-23794 System.err              com.penpencil.parent.android         W  	at androidx.lifecycle.RepeatOnLifecycleKt$repeatOnLifecycle$3$1$1$1$1.invokeSuspend(RepeatOnLifecycle.kt:110)
2023-11-15 14:16:24.913 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
2023-11-15 14:16:24.914 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
2023-11-15 14:16:24.914 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:68)
2023-11-15 14:16:24.914 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:375)
2023-11-15 14:16:24.914 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
2023-11-15 14:16:24.915 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
2023-11-15 14:16:24.915 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
2023-11-15 14:16:24.915 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
2023-11-15 14:16:24.915 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
2023-11-15 14:16:24.916 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
2023-11-15 14:16:24.916 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
2023-11-15 14:16:24.916 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
2023-11-15 14:16:24.917 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.ResolverFragment.requestPermission(ResolverFragment.kt:58)
2023-11-15 14:16:24.917 23794-23794 System.err              com.penpencil.parent.android         W  	at dev.icerock.moko.permissions.PermissionsControllerImpl.providePermission(PermissionsControllerImpl.kt:57)
2023-11-15 14:16:24.917 23794-23794 System.err              com.penpencil.parent.android         W  	at com.penpencil.parent.AppKt$App$2.invokeSuspend(App.kt:54)
2023-11-15 14:16:24.917 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
2023-11-15 14:16:24.918 23794-23794 System.err              com.penpencil.parent.android         W  	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
2023-11-15 14:16:24.918 23794-23794 System.err              com.penpencil.parent.android         W  	at android.os.Handler.handleCallback(Handler.java:942)
2023-11-15 14:16:24.918 23794-23794 System.err              com.penpencil.parent.android         W  	at android.os.Handler.dispatchMessage(Handler.java:99)
2023-11-15 14:16:24.918 23794-23794 System.err              com.penpencil.parent.android         W  	at android.os.Looper.loopOnce(Looper.java:226)
2023-11-15 14:16:24.919 23794-23794 System.err              com.penpencil.parent.android         W  	at android.os.Looper.loop(Looper.java:313)
2023-11-15 14:16:24.919 23794-23794 System.err              com.penpencil.parent.android         W  	at android.app.ActivityThread.main(ActivityThread.java:8762)
2023-11-15 14:16:24.919 23794-23794 System.err              com.penpencil.parent.android         W  	at java.lang.reflect.Method.invoke(Native Method)
2023-11-15 14:16:24.919 23794-23794 System.err              com.penpencil.parent.android         W  	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604)
2023-11-15 14:16:24.920 23794-23794 System.err              com.penpencil.parent.android         W  	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

Expose Denied exceptions from platform-specific part to common

Denied exceptions now in platform-specific code. Should be in common code, for checking like:

try {
    permissionsController.providePermission(Permission.CAMERA)
} catch(error: DeniedException) {
    // user denied permission request
} catch(error: DeniedAlwaysException) {
    showPlaceholder = true
}

Exception when using moko-permissions with navigation component

I added this case in my repository.
Link: https://github.com/timur-muminov/Moko-permissions-bug

java.lang.IllegalStateException: FragmentManager is already executing transactions atandroidx.fragment.app.FragmentManager.ensureExecReady(FragmentManager.java:1695) atandroidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1725) atandroidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:317) atdev.icerock.moko.permissions.PermissionsControllerImpl.getOrCreateResolverFragment(PermissionsControllerImpl.kt:127) atdev.icerock.moko.permissions.PermissionsControllerImpl.providePermission(PermissionsControllerImpl.kt:49) atcom.myapplication3.sample.TestFragment$onViewStateRestored$1.invokeSuspend(TestFragment.kt:15) atkotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) atkotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367) atkotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) atkotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)

permissionsController.openAppSettings() isn't working tried on android emulator

When permission is denied i am trying to open the setting page but it's working

val factory: PermissionsControllerFactory = rememberPermissionsControllerFactory()
val viewModel = PermissionViewModel(factory.createPermissionsController()) BindEffect(viewModel.permissionsController)

viewModel.askPermission(Permission.COARSE_LOCATION)

Viewmodel code

private val _permissionState = MutableStateFlow(Pair(Permission.COARSE_LOCATION, PermissionState.NotDetermined))
val permissionState = _permissionState.asStateFlow()

`fun askPermission(permission: Permission) {
    viewModelScope.launch {
        try {
            val grantState = permissionsController.getPermissionState(permission)
            when (grantState) {
                PermissionState.Granted -> {
                    println("askPermission Granted ")
                    _permissionState.emit(Pair(permission, PermissionState.Granted))
                }
                PermissionState.DeniedAlways -> {
                    println("askPermission DeniedAlways")
                    permissionsController.openAppSettings()
                }

                PermissionState.NotDetermined,
                PermissionState.Denied,
                -> {
                    println("askPermission Denied ")
                     permissionsController.providePermission(permission)
                }
            }
        } catch (deniedAlways: DeniedAlwaysException) {
            // Permission is always denied.
            println("deniedAlways")
            _permissionState.emit(Pair(permission, PermissionState.DeniedAlways))
        } catch (denied: DeniedException) {
            // Permission was denied.
            println("denied")
            _permissionState.emit(Pair(permission, PermissionState.Denied))
        } catch (exception: Exception) {
            println("exception")
        }
    }
}

Localization permission for BLE devices on Android >=12

Add missing activity recognition permissions for Android and iOS

Hi. Can you add missing Android and iOS permissions, please?

Android:

  • ACTIVITY_RECOGNITIONHi. Can you add permissions mission Android and iOS permissions, please?

Android:

ACTIVITY_RECOGNITION

iOS:

MotionActivityManagerAuthorized

iOS:

  • MotionActivityManagerAuthorized

Error using androidx.activity.ComponentActivity

My activity extend of androidx.activity.ComponentActivity.

is there any option to workaround?

java.lang.ClassCastException: com.company.component.ui.SActivity cannot be cast to androidx.fragment.app.FragmentActivity at dev.icerock.moko.permissions.compose.BindEffect_androidKt$BindEffect$1.invokeSuspend(BindEffect.android.kt:24) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108) at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81) at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41) at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:68) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:967) at android.view.Choreographer.doCallbacks(Choreographer.java:793) at android.view.Choreographer.doFrame(Choreographer.java:724) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:954) at android.os.Handler.handleCallback(Handler.java:914) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:224) at android.app.ActivityThread.main(ActivityThread.java:7551) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:995) Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@c4c4f7, androidx.compose.runtime.BroadcastFrameClock@5d54f64, StandaloneCoroutine{Cancelling}@55b75cd, AndroidUiDispatcher@f63ce82]

thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.