Description
The PermissionsControllerImpl implementaiton in the Moko permissions library (v0.19.1 and v0.20.0) causes memory leaks by retaining references to old Activity instances during configuration changes (such as screen rotation).
This is due to the use of suspendCoroutine with callback closures that capture the ActivityResultLauncher, which in turn holds a reference to the activity context. As a result, the old activity cannot be garbage collected, leading to memory leaks (confirmed with LeakCanary).
Root Cause
suspendCoroutine captures the continuation in a closure.
- The closure references the
ActivityResultLauncher instance.
- The launcher holds a reference to the activity context.
- During configuration changes, the activity is recreated, but the old continuation still holds references to the old activity through the launcher.
Proposed Solution
A custom implementation (NoLeakPermissionsController) avoids this leak by:
- Using
kotlinx.coroutines.flow.MutableSharedFlow to emit permission request results instead of suspendCoroutine.
- Unbinding any existing activity references when binding a new one.
- Passing the activity as a
LifecycleOwner to the ActivityResultLauncher - making sure the launcher is cleared when the activity is destroyed.
Proposed Fix PR : Link
References
Steps to Reproduce
Super simple sample app to reproduce
- Use the default
PermissionsControllerImpl in an activity.
- Request permission.
- While the system permission dialog is visible - Rotate the device or trigger a configuration change.
- Observe with LeakCanary: the old activity instance is retained.
Expected Behavior
Old activity instances should be garbage collected after configuration changes.
Environment
- Moko permissions version: 0.19.1 and 0.20.0
Description
The
PermissionsControllerImplimplementaiton in the Moko permissions library (v0.19.1 and v0.20.0) causes memory leaks by retaining references to oldActivityinstances during configuration changes (such as screen rotation).This is due to the use of
suspendCoroutinewith callback closures that capture theActivityResultLauncher, which in turn holds a reference to the activity context. As a result, the old activity cannot be garbage collected, leading to memory leaks (confirmed with LeakCanary).Root Cause
suspendCoroutinecaptures the continuation in a closure.ActivityResultLauncherinstance.Proposed Solution
A custom implementation (
NoLeakPermissionsController) avoids this leak by:kotlinx.coroutines.flow.MutableSharedFlowto emit permission request results instead ofsuspendCoroutine.LifecycleOwnerto theActivityResultLauncher- making sure the launcher is cleared when the activity is destroyed.Proposed Fix PR : Link
References
Steps to Reproduce
Super simple sample app to reproduce
PermissionsControllerImplin an activity.Expected Behavior
Old activity instances should be garbage collected after configuration changes.
Environment