GithubHelp home page GithubHelp logo

deva666 / peko Goto Github PK

View Code? Open in Web Editor NEW
152.0 2.0 10.0 560 KB

Android Library for requesting Permissions with Kotlin Flow

License: Apache License 2.0

Kotlin 100.00%
android android-library kotlin kotlin-coroutines android-permissions coroutines flow streams

peko's Introduction

PEKO

PErmissions with KOtlin

Quality Gate Status Android Arsenal License

Android Permissions with Kotlin Coroutines and Flow API

No more callbacks, listeners or verbose code for requesting Android permissions.
Get Permission Request Result asynchronously with one function call.
Context or Activity not needed for requests, request permissions from your View Model or Presenter.
Built with Kotlin Coroutines and Flow .


Thanks to the supporters

Supported by JetBrains Open Source

Sponsored by CloudBit

Installation

Hosted on Maven Central

implementation 'com.markodevcic:peko:3.0.5'

Example

First initialize the PermissionRequester with Application Context. This enables all requests to be made without a Context or Activity.
If you pass an Activity as Context, IllegalStateException is raised.

PermissionRequester.initialize(applicationContext)

Get the instance of PermissionRequester interface.

val requester = PermissionRequester.instance()

Request one or more permissions. For each permission receive PermissionResult as async Flow stream of data.

launch {
    requester.request(
            Manifest.permission.CAMERA,
            Manifest.permission.READ_CONTACTS
    ).collect { p ->
        when (p) {
            is PermissionResult.Granted -> print("${p.permission} granted") // nice, proceed 
            is PermissionResult.Denied -> print("${p.permission} denied") // denied, not interested in reason
            is PermissionResult.Denied.NeedsRationale -> print("${p.permission} needs rationale") // show rationale
            is PermissionResult.Denied.DeniedPermanently -> print("${p.permission} denied for good") // no go
            is PermissionResult.Cancelled -> print("request cancelled") // op canceled, repeat the request
        }
    }
}

Need to check only if permissions are granted? Let's skip the horrible Android API. No coroutine required.

val granted: Boolean = requester.areGranted(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS)

Or are any of the requested granted?

val anyGranted: Boolean = requester.anyGranted(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS)

Why Flows?

Requesting multiple permissions in a single request represents a data stream of PermissionsResult objects. Flow fits here perfectly. Each permission requested is either granted or denied, with Flow we can operate on each emitted result item and inspect it individually, that is check if it is Granted, Denied or Needs Rationale. Flows are async and require a coroutine to collect so this is not a huge update from Peko version 2. Also, they are now part of Kotlin Coroutines library, so no new dependencies are added.

Don't want to use Flow? No problem, suspendable extension functions that collect for you are there.

// just check all granted
launch {
    val allGranted: Boolean = requester.request(Manifest.permission.CAMERA)
            .allGranted()
}

// give me just granted permissions
launch {
    val granted: Collection<PermissionResult> =
            requester.request(Manifest.permission.CAMERA)
                    .grantedPermissions()
}


// give me all denied permissions, whatever the reason
launch {
    val denied: Collection<PermissionResult> =
            requester.request(Manifest.permission.CAMERA)
                    .deniedPermissions()

// these can be then separated to see needs rationale or denied permanently permissions
    val needsRationale = denied.filterIsInstance<PermissionResult.Denied.NeedsRationale>()
    val deniedPermanently = denied.filterIsInstance<PermissionResult.Denied.DeniedPermanently>()
}

// give me needs rationale permissions
launch {
    val needsRationale: Collection<PermissionResult> =
            requester.request(Manifest.permission.CAMERA)
                    .needsRationalePermissions()
}

// give me needs denied permanently permissions
launch {
    val deniedPermanently: Collection<PermissionResult> =
            requester.request(Manifest.permission.CAMERA)
                    .deniedPermanently()
}

Testing

Using permission requests as part of your business logic and want to run your unit tests on JVM? Perfect, PermissionRequester is an interface which can be easily mocked in your unit tests. It does not require a Context or Activity for any methods. Only a one time registration of Application Context needs to be done during app startup with PermissionRequester.initialize method.

Screen rotations

Library supports screen rotations. The only requirement is to preserve the instance of PermissionRequester during device orientation change. How to do this is entirely up to a developer. Easiest way is to use PermissionRequester with lifecycle aware Jetpack ViewModel which does this automatically.

What is new

Peko Version 3 is now released. Peko now uses coroutine Flow instead of suspend function for returning PermissionResult. Support for LiveData is removed. Flow can easily be adapted to work with LiveData.

Breaking changes from Peko Version 2

  • PermissionResult now has a single String permission as property.
  • Peko singleton is removed. PermissionRequester interface is now its replacement.
  • Extension functions for Fragment and Activity are removed.
  • PermissionLiveData class removed

Peko Version 2.0 uses vanilla Kotlin coroutines, and is here.

License

Copyright 2024 Marko Devcic

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.

peko's People

Contributors

deva666 avatar dmitry-borodin avatar koral-- avatar lucasvsme avatar sebastinto 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

peko's Issues

viewLifeCycleOwner of a fragment is throwing: Unsupported lifecycle owner

According to https://developer.android.com/topic/libraries/architecture/livedata and some articles https://proandroiddev.com/5-common-mistakes-when-using-architecture-components-403e9899f4cb, viewLifeCycleOwner is the right one to use instead of the fragment itself but when we try:

private fun observePermissions() {
        viewModel.permissionLiveData.observe(viewLifecycleOwner) {
            when(it) {

                is PermissionResult.Granted -> {
                    onPermissionGranted(it.grantedPermissions)
                }

                is PermissionResult.Denied.JustDenied -> {
                    onPermissionDenied(it.deniedPermissions)
                }

                is PermissionResult.Denied.NeedsRationale -> {
                    showPermissionRationale(it.deniedPermissions)
                }

                is PermissionResult.Denied.DeniedPermanently -> {
                    showPermissionRationale(it.deniedPermissions)
                }

                is PermissionResult.Cancelled -> {
                    onCancelPermissionDialog()
                }
            }
        }
    }

It gives an exception: "Unsupported lifecycle owner". It is working fine if we pass "this" as a lifecycle owner for the fragment.

ClosedSendChannelException in PekoActivity

Hi,
I have a few crashes in PekoActivity.onRequestPermissionsResult when processing the result to the corountine channel

viewModel.channel.offer(
    when {
        permissions.isEmpty() -> PermissionResult.Cancelled
        deniedPermissions.isEmpty() -> PermissionResult.Granted(grantedPermissions)
        needsRationale -> PermissionResult.Denied.NeedsRationale(deniedPermissions)
        doNotAskAgain -> PermissionResult.Denied.DeniedPermanently(deniedPermissions)
        else -> PermissionResult.Denied.JustDenied(deniedPermissions)
    })

I happen to have a closed channel. with the following stack

kotlinx.coroutines.channels.Closed.getSendException (Closed.java:1139)
kotlinx.coroutines.channels.AbstractSendChannel.helpCloseAndGetSendException (AbstractSendChannel.java:173)
kotlinx.coroutines.channels.AbstractSendChannel.trySend-JP2dKIU (AbstractSendChannel.java:165)
kotlinx.coroutines.channels.SendChannel$DefaultImpls.offer (SendChannel.java:166)
kotlinx.coroutines.channels.AbstractSendChannel.offer (AbstractSendChannel.java:142)
com.markodevcic.peko.PekoActivity.onRequestPermissionsResult (PekoActivity.java:52)

I didn't manage to reproduce it.
I can only guess that, somehow, the coroutine is started from a lifecycle dependent object, which is dead by the time the PekoActivity try to send the result ?
Maybe is wise to add a basic check like this at PekoActivity:52:

if(!channel.isClosedForSend) {
    viewModel.channel.offer(
        when {
            permissions.isEmpty() -> PermissionResult.Cancelled
            deniedPermissions.isEmpty() -> PermissionResult.Granted(grantedPermissions)
            needsRationale -> PermissionResult.Denied.NeedsRationale(deniedPermissions)
            doNotAskAgain -> PermissionResult.Denied.DeniedPermanently(deniedPermissions)
            else -> PermissionResult.Denied.JustDenied(deniedPermissions)
        })
}

What do you think ?
Thank you,
Regards.

Memory leak due to permissionsToRequesterMap

1 APPLICATION LEAKS

                                                                                                References underlined with "~~~" are likely causes.
                                                                                                Learn more at https://squ.re/leaks.
                                                                                                
                                                                                                27195 bytes retained by leaking objects
                                                                                                Signature: 66b567888a8bb4f1c35748db60004e7939645c28
                                                                                                ┬───
                                                                                                │ GC Root: System class
                                                                                                │
                                                                                                ├─ com.markodevcic.peko.PekoActivity class
                                                                                                │    Leaking: NO (a class is never leaking)
                                                                                                │    ↓ static PekoActivity.permissionsToRequesterMap
                                                                                                │                          ~~~~~~~~~~~~~~~~~~~~~~~~~
                                                                                                ├─ java.util.concurrent.ConcurrentHashMap instance
                                                                                                │    Leaking: UNKNOWN
                                                                                                │    Retaining 27.4 kB in 462 objects
                                                                                                │    ↓ ConcurrentHashMap[""]
                                                                                                │                       ~~~~
                                                                                                ├─ kotlinx.coroutines.CompletableDeferredImpl instance
                                                                                                │    Leaking: UNKNOWN
                                                                                                │    Retaining 27.2 kB in 459 objects
                                                                                                │    _state instance of com.markodevcic.peko.PekoActivity with mDestroyed = true
                                                                                                │    ↓ JobSupport._state
                                                                                                │                 ~~~~~~
                                                                                                ╰→ com.markodevcic.peko.PekoActivity instance
                                                                                                ​     Leaking: YES (ObjectWatcher was watching this because com.markodevcic.peko.PekoActivity received
                                                                                                ​     Activity#onDestroy() callback and Activity#mDestroyed is true)
                                                                                                ​     Retaining 27.2 kB in 458 objects
                                                                                                ​     key = acd6b2f5-6570-44f8-9eee-86be9a99681f
                                                                                                ​     watchDurationMillis = 5862
                                                                                                ​     retainedDurationMillis = 862
                                                                                                ​     mApplication instance of com.nadi.demo.application.ChatApplication
                                                                                                ​     mBase instance of android.app.ContextImpl

False PermissionResult.Granted when flow is interrupted

Steps to reproduce:

  1. Cause PekoActivity#onRequestPermissionsResult() to be called (I used PermissionsLiveData but I guess more ways are affected) with empty arrays. Eg. You may kill PackageInstaller app when it is showing permission approval dialog.

Expected result:
Either

  • nothing happens (no value is posted to LiveData etc.)
    or
  • new outcome like PermissionResult.Cancelled is sent

As documentation says:

Note: It is possible that the permissions request interaction with the user is interrupted. In this case you will receive empty permissions and results arrays which should be treated as a cancellation.

https://developer.android.com/reference/androidx/core/app/ActivityCompat.OnRequestPermissionsResultCallback.html?hl=en

Code here: https://github.com/deva666/Peko/blob/master/peko/src/main/java/com/markodevcic/peko/PekoActivity.kt#L54
checks whether denied permission is empty and assumes Granted result if so. However, list is also empty if interaction is cancelled.

DeniedPermanently triggered when selecting Approximate instead of Precise location

As stated in title.

Compile SDK: 33
Android version: 13
Image: https://i.imgur.com/FwdLvgh.png

CoroutineScope(Default).launch {
            val result = requestPermissionsAsync(
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION
            )

            when (result) {
                is PermissionResult.Granted -> {Log.e("Activity", "GRANTED") } // woohoo, all requested permissions granted
                is PermissionResult.Denied.JustDenied -> {Log.e("Activity", "JUST DENIED") } // at least one permission was denied, maybe we forgot to register it in the AndroidManifest?
                is PermissionResult.Denied.NeedsRationale -> {Log.e("Activity", "NEEDS") } // user clicked Deny, let's show a rationale
                is PermissionResult.Denied.DeniedPermanently -> { Log.e("Activity", "DENIED PERM")} // Android System won't show Permission dialog anymore, let's tell the user we can't proceed
                is PermissionResult.Cancelled -> {Log.e("Activity", "CANCELLED") } // interaction was interrupted
            }
        }

Suggestion: Add Peko.isGranted(activity, permission)

I would be nice to have an isGranted (because Androids' API suck).

Such as extension-function on activity, and/or Peko function.

I also suggest creating an Peko instance-class which could wrap the activity object, because that gives nicer interface for all peko methods. Like 'rxPermissions' does:

https://github.com/tbruyelle/RxPermissions/blob/master/lib/src/main/java/com/tbruyelle/rxpermissions3/RxPermissions.java

It would be nice to have a
peko.shouldShowRequestPermissionRationale
peko.isGranted

PermissionResult.NeedsRationale and PermissionResult.DoNotAskAgain is better not a subclass of PermissionResult.Denied

The sample code of README is as follows,

launch {
    val result = requestPermissionsAsync(Manifest.permission.BLUETOOTH, Manifest.permission.CAMERA) 
    
    when (result) {
        is PermissionResult.Granted -> { } // woohoo, all requested permissions granted
        is PermissionResult.Denied -> { } // at least one permission was denied
        is PermissionResult.NeedsRationale -> { } // user clicked Deny, let's show a rationale
        is PermissionResult.DoNotAskAgain -> { } // Android System won't show Permission dialog anymore, let's tell the user we can't proceed 
    }
}

In this case, if result variable is PermissionResult.NeedsRationale,
First match PermissionResult.Denied,
PermissionResult.NeedsRationale block is not executed.

This can be handled by changing the evaluation order of result variable in the order of NeedsRationale, DoNotAskAgain, Denied,
But it's not good because the behavior changes depending on how it line up.

Feature request: Add PermissionRequester.isAnyGranted function

It will be helpful to add PermissionRequester.isAnyGranted in case of we need any of requested permissions.
In case of requesting permissionRequester.areGranted(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION) we can't get permission for both of them - there is no actually need in both - any of them will be fine). But method PermissionRequester.areGranted checks for empty denied requests.
In this case would be helpful to have function such as PermissionRequester.isAnyGranted which will return true if any of permissions was granted

If less than API-23 then permissions should be treated as granted

I noticed this:

suspend fun requestPermissionsAsync(context: Context, vararg permissions: String): PermissionResult {

        if (isTargetSdkUnderAndroidM(context)) {
            return PermissionResult.Denied.JustDenied(permissions.toList())
        }

Before Android-23 all permissions are granted without asking user, just by declaring in manifest. So this should return Granted. Like rxPermission does in isGranted here:

https://github.com/tbruyelle/RxPermissions/blob/master/lib/src/main/java/com/tbruyelle/rxpermissions3/RxPermissions.java

Requesting ACCESS_BACKGROUND_LOCATION never completes

Hello there! Trying to gain background access. I learned that Peko, while requesting ACCESS_BACKGROUND_LOCATION, transitions user to the app permission settings only if ACCESS_FINE_LOCATION was previously gained, otherwise the flow does not emit anything. Is it intended behavior?

Steps to reproduce:

AndroidManifest:

  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

Activity's onCreate():

PermissionRequester.apply {
    initialize(applicationContext)
    lifecycleScope.launch {
        repeatOnLifecycle(STARTED) {
            try {
                instance().request(
                    permission.ACCESS_BACKGROUND_LOCATION,
                )
                    .catch {
                        Log.d("DEBUG_PERMISSIONS", "catch: $it")
                    }
                    .collect {
                        Log.d("DEBUG_PERMISSIONS", "$it")
                    }
            } catch (e: Throwable) {
                Log.d("DEBUG_PERMISSIONS", "error: $e")
            }
        }
    }
}

Sometimes PEKO fails with exception: java.lang.IllegalStateException

Used version: implementation 'com.markodevcic:peko:3.0.3'

Some Google play report:
Device: samsung dm1q (Galaxy S23)
Android: Android 13 (SDK 33)
compileSdkVersion: 33
targetSdkVersion: 33
minSdkVersion: 24

Sometimes PEKO fails with exception:

Exception java.lang.IllegalStateException:
  at com.markodevcic.peko.PekoActivity.onPostCreate (PekoActivity.kt:35)
  at android.app.Instrumentation.callActivityOnPostCreate (Instrumentation.java:1458)
  at android.app.ActivityThread.handleStartActivity (ActivityThread.java:4231)
  at android.app.servertransaction.TransactionExecutor.performLifecycleSequence (TransactionExecutor.java:221)
  at android.app.servertransaction.TransactionExecutor.cycleToPath (TransactionExecutor.java:201)
  at android.app.servertransaction.TransactionExecutor.executeLifecycleState (TransactionExecutor.java:173)
  at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:97)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2574)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:226)
  at android.os.Looper.loop (Looper.java:313)
  at android.app.ActivityThread.main (ActivityThread.java:8757)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:604)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)

Is it possible to find out if user clicked do not ask again?

I haven't found possibility to check whether it make sense to ask for that permission again.
Ideally I would like to have something like

val result = Peko.requestPermission(Bluetooth, context)
when (result)  {
  GRANTED -> proceed()
  DENIED -> postExplanation() //and request again
  BLOCKED -> showDifferentExplanation()
}

Crash when using app-context for permission request

Nice permission framework, I was just looking for something with coroutines.

PEKO 2.1.4.

I get this error, when doing
Peko.requestPermissionsAsync(application, locationPermission)

but not when doing

Peko.requestPermissionsAsync(activity, locationPermission)

So I suspect the method should be changed to require activity as input... (?).


    Process: dk.bnr.bnr.dev, PID: 3849
    android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
        at android.app.ContextImpl.startActivity(ContextImpl.java:1029)
        at android.app.ContextImpl.startActivity(ContextImpl.java:1005)
        at android.content.ContextWrapper.startActivity(ContextWrapper.java:403)
        at com.markodevcic.peko.PermissionRequesterFactoryImpl.getRequesterAsync(PermissionRequesterFactory.kt:21)
        at com.markodevcic.peko.PekoService$requestPermissions$3.invokeSuspend(PekoService.kt:59)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:233)
        at android.app.ActivityThread.main(ActivityThread.java:8010)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:978)
    	Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@fe01cb9, Dispatchers.Main]

DeniedPermanently triggered when clicking outside of permissions dialog

Hey,

When the user does not click on any button of the Android native request dialog but outside, the dialog gets dismissed and Peko triggers the PermissionResult.Denied.DeniedPermanently result. I think in this case PermissionResult.Cancelled should be called. The native dialog is triggered on next Activity execution despite Peko's PermissionResult.Denied.DeniedPermanently result.

Compile SDK: 33
Android version: 13

CoroutineScope(Default).launch {
            val result = requestPermissionsAsync(
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION
            )

            when (result) {
                is PermissionResult.Granted -> {Log.e("Activity", "GRANTED") } // woohoo, all requested permissions granted
                is PermissionResult.Denied.JustDenied -> {Log.e("Activity", "JUST DENIED") } // at least one permission was denied, maybe we forgot to register it in the AndroidManifest?
                is PermissionResult.Denied.NeedsRationale -> {Log.e("Activity", "NEEDS") } // user clicked Deny, let's show a rationale
                is PermissionResult.Denied.DeniedPermanently -> { Log.e("Activity", "DENIED PERM")} // Android System won't show Permission dialog anymore, let's tell the user we can't proceed
                is PermissionResult.Cancelled -> {Log.e("Activity", "CANCELLED") } // interaction was interrupted
            }
        }

Deadlock when executing request permission

PEKO 3.0.1

If I request permission on a fresh app startup where permissions has not been requested previously, then the app is stuck in a deadlock style ANR. It never proceeds. The same code I run works when I execute it via button press later

private val permissionRequester = PermissionRequester.instance() // Creates new instance

    fun register() {
        CoroutineScope(Dispatchers.Main).launch {
            permissionRequester.request(android.Manifest.permission.ACCESS_FINE_LOCATION)
                .collect {
                    // MyStuff - It never arrives to collect when deadlocked
                }
        }
    }

Debugging indicates that it hangs after executing PermissionRequester:75 which is this line

val requester = requesterFactory.getRequesterAsync(requireContext()).await()

And the PekoActivity executes onPostCreate (with non-null requesterDeferred) but neither requestPermissions, onRequestPermissionsResult nor finish got executed on PekoActivity according to my breakpoints. Apparantly your maven upload included the source-code, so IntelliJ could debug it which was pretty cool.

If I change the code so it performs the request on another thread, then it works

    fun register() {
        CoroutineScope(Dispatchers.Main).launch {
            // do my stuff on main (if needed, otherwise we could just create the above coroutine on IO or elsewhere
            withContext(Dispatchers.IO) { // this works, no deadlock
                permissionRequester.request(android.Manifest.permission.ACCESS_FINE_LOCATION)
                    .collect {
                        withContext(Dispatchers.Main) {
                            // do my stuff back on mair
                        }
                    }
            }
        }
    }

After changing to the above code, the coroutine proceeded past the PermissionRequester:75 await line, performed the permission-requests and then things ran along fine from there.

For some reason the main-thread code only hung when I executed during my startup work (but long after onCreate), but did not hang when executed from user button click. But then again, that just indicates deadlocks can be tricky.

Its quite unclear to me how it can be a deadlock, as your coroutines seem fine. They suspend and should allow the main to do its work.

I have no idea if you can make any sense of that. I'll probably roll back to v2 though that had a resume rare bug, but for now I'll skip requesting permission on startup and let the user trigger it.

if you changed your library to the following I suspect it would work:

					val asyncRequester = requesterFactory.getRequesterAsync(requireContext())
					val requester = 
					withContext(Dispaters.IO) {
					    asyncRequester.await()
					}

Best Alex

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.