So with the latest Update from
to
I get the error:
FragmentXY is attempting to registerForActivityResult after being created. Fragments must call registerForActivityResult() before they are created (i.e. initialization, onAttach(), or onCreate()).
I used to check permissions in my StartFragment (Single Activity App, in onViewCreated) after showing to the User information about the use of those permissions and why they are needed. Everything worked perfectly for the last 3(?) months.
I see in the changelog:
Behavior Changes
[...]
Calling registerForActivityResult() after onCreate() now throws an exception indicating that this is not allowed rather than silently failing to deliver results after a configuration change. (b/162255449) "
I downgraded back to version 1.3.0-alpha07 for the moment.
But if I need registerForActivityResult in my Fragments AFTER the view is created (e.g. for permissions), how can I do it when upgrading to version 1.3.0-alpha08?
The docs state that I should use launch() in onCreate of my Fragment (see below) but that would mean I have to do it before the view is created, and that would be contradictory to my app flow.
Behavior Changes
[...]
You can now call launch() on an ActivityResultLauncher in the onCreate() lifecycle method of a fragment. (b/161464278) "
As this behaviour seems to be intended by the developers, it is not a bug or anything but how can I continue using ActivityResults after onCreate? Any ideas?
Thanks to @A.Andriyishyna I understand that registration (in onCreate) and execution (when needed, e.g. in onViewCreated) have to be handled separately.
Problem is that I have handy inline functions (with courtesy to Flywith24) in other files, which help me to separate the permission BL from the View (Fragment).
Is there a way to keep those inline functions without having to change them drastically?
class GalleryFragment: ScopedFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializePermissions(requiredContext)
}
private fun initializePermissions(context: Context) {
storagePermissions(
context = context,
actionOnGranted = { showImages() },
actionOnDeclined = { showNoAccess() },
actionRepeat = { initializePermissions(context) }
)
}
}
inline fun Fragment.storagePermissions(
context: Context,
crossinline actionOnGranted: () -> Unit,
crossinline actionOnDeclined: () -> Unit,
crossinline actionRepeat: () -> Unit
) {
when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q -> {
if (
ContextCompat.checkSelfPermission(
context, Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
actionOnGranted()
} else {
permission(
Manifest.permission.READ_EXTERNAL_STORAGE
) {
granted = {
actionOnGranted()
}
denied = {
actionRepeat()
}
explained = {
actionOnDeclined()
}
}
}
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
if (
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_MEDIA_LOCATION
) == PackageManager.PERMISSION_GRANTED) {
Log.d("Storage Permission", "Permission already granted.")
actionOnGranted()
} else {
Log.d("Storage Permission", "No Permission Yet -> Ask for it!")
permissions(
Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION
) {
allGranted = {
actionOnGranted()
}
denied = {
Log.d("Storage Permission", "Denied")
actionRepeat()
}
explained = {
Log.d("Storage Permission", "Permanently Denied")
actionOnDeclined()
}
}
}
}
}
}
inline fun Fragment.requestPermission(
permission: String,
crossinline granted: (permission: String) -> Unit = {},
crossinline denied: (permission: String) -> Unit = {},
crossinline explained: (permission: String) -> Unit = {}
) {
registerForActivityResult(ActivityResultContracts.RequestPermission()) { result ->
when {
result -> granted.invoke(permission)
shouldShowRequestPermissionRationale(permission) -> denied.invoke(permission)
else -> explained.invoke(permission)
}
}.launch(permission)
}
inline fun Fragment.requestMultiplePermissions(
vararg permissions: String,
crossinline allGranted: () -> Unit = {},
crossinline denied: (List<String>) -> Unit = {},
crossinline explained: (List<String>) -> Unit = {}
) {
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result: MutableMap<String, Boolean> ->
val deniedList = result.filter { !it.value }.map { it.key }
when {
deniedList.isNotEmpty() -> {
val map = deniedList.groupBy { permission ->
if (shouldShowRequestPermissionRationale(permission)) DENIED else EXPLAINED
}
map[DENIED]?.let { denied.invoke(it) }
map[EXPLAINED]?.let { explained.invoke(it) }
}
else -> allGranted.invoke()
}
}.launch(permissions)
}
It just means that you shouldn't register the callback after onCreate().
So you can do this
private val checkPermission = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
...
}
and then launch the check anytime you need it
checkPermission.launch(array-of-permissions)
The reason:
When starting an activity for a result, it is possible (and, in cases of memory-intensive operations such as camera usage, almost certain) that your process and your activity will be destroyed due to low memory.
For this reason, the Activity Result APIs decouple the result callback from the place in your code where you launch the other activity. As the result callback needs to be available when your process and activity are recreated, the callback must be unconditionally registered every time your activity is created, even if the logic of launching the other activity only happens based on user input or other business logic.