Last active
May 9, 2024 17:48
-
-
Save ElianFabian/fda661eb778c74538cec536f7231a621 to your computer and use it in GitHub Desktop.
Activity Result extension functions to directly get the result from a suspending function or a callback in the call site.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.activity.ComponentActivity | |
import androidx.activity.result.ActivityResultCallback | |
import androidx.activity.result.ActivityResultLauncher | |
import androidx.activity.result.contract.ActivityResultContract | |
import androidx.fragment.app.Fragment | |
import androidx.lifecycle.lifecycleScope | |
import kotlinx.coroutines.CoroutineScope | |
import kotlinx.coroutines.channels.Channel | |
import kotlinx.coroutines.launch | |
private fun <I, O, A> registerForActivityResultSuspendInternal( | |
transform: (input: A) -> I, | |
contract: ActivityResultContract<I, O>, | |
registerForActivityResult: ( | |
contract: ActivityResultContract<I, O>, | |
callback: ActivityResultCallback<O>, | |
) -> ActivityResultLauncher<I>, | |
): suspend ( | |
input: A, | |
) -> O { | |
val channel = Channel<O>() | |
val resultLauncher = registerForActivityResult(contract) { result -> | |
channel.trySend(result) | |
} | |
return { input: A -> | |
resultLauncher.launch(transform(input)) | |
channel.receive() | |
} | |
} | |
private fun <I, O, A> registerForActivityResultCallbackInternal( | |
transform: (input: A) -> I, | |
contract: ActivityResultContract<I, O>, | |
scope: CoroutineScope, | |
registerForActivityResult: ( | |
contract: ActivityResultContract<I, O>, | |
callback: ActivityResultCallback<O>, | |
) -> ActivityResultLauncher<I>, | |
): (input: A, callback: (result: O) -> Unit) -> Unit { | |
val channel = Channel<O>() | |
val resultLauncher = registerForActivityResult(contract) { result -> | |
channel.trySend(result) | |
} | |
return { input: A, callback: (result: O) -> Unit -> | |
resultLauncher.launch(transform(input)) | |
scope.launch { | |
val result = channel.receive() | |
callback(result) | |
} | |
} | |
} | |
fun <I, O, A> ComponentActivity.registerForActivityResultSuspend( | |
transform: (input: A) -> I, | |
contract: ActivityResultContract<I, O>, | |
): suspend (input: A) -> O { | |
return registerForActivityResultSuspendInternal( | |
contract = contract, | |
transform = transform, | |
registerForActivityResult = ::registerForActivityResult, | |
) | |
} | |
fun <I, O> ComponentActivity.registerForActivityResultSuspend( | |
contract: ActivityResultContract<I, O>, | |
): suspend (input: I) -> O { | |
return this.registerForActivityResultSuspend( | |
contract = contract, | |
transform = { it }, | |
) | |
} | |
fun <I, O> ComponentActivity.registerForActivityResultSuspend( | |
contract: ActivityResultContract<I, O>, | |
input: I, | |
): suspend () -> O { | |
val function = registerForActivityResultSuspend(contract) | |
return { function(input) } | |
} | |
fun <I, O, A> ComponentActivity.registerForActivityResultCallback( | |
transform: (input: A) -> I, | |
contract: ActivityResultContract<I, O>, | |
): (input: A, callback: (result: O) -> Unit) -> Unit { | |
return registerForActivityResultCallbackInternal( | |
contract = contract, | |
registerForActivityResult = ::registerForActivityResult, | |
transform = transform, | |
scope = lifecycleScope, | |
) | |
} | |
fun <I, O> ComponentActivity.registerForActivityResultCallback( | |
contract: ActivityResultContract<I, O>, | |
): (input: I, callback: (result: O) -> Unit) -> Unit { | |
return registerForActivityResultCallback( | |
contract = contract, | |
transform = { it }, | |
) | |
} | |
fun <I, O> ComponentActivity.registerForActivityResultCallback( | |
contract: ActivityResultContract<I, O>, | |
input: I, | |
): (callback: (result: O) -> Unit) -> Unit { | |
val function = registerForActivityResultCallback(contract) | |
return { callback -> | |
function(input, callback) | |
} | |
} | |
fun <I, O, A> Fragment.registerForActivityResultSuspend( | |
transform: (A) -> I, | |
contract: ActivityResultContract<I, O>, | |
): suspend (input: A) -> O { | |
return registerForActivityResultSuspendInternal( | |
contract = contract, | |
transform = transform, | |
registerForActivityResult = ::registerForActivityResult, | |
) | |
} | |
fun <I, O> Fragment.registerForActivityResultSuspend( | |
contract: ActivityResultContract<I, O>, | |
): suspend (input: I) -> O { | |
return registerForActivityResultSuspend( | |
contract = contract, | |
transform = { it }, | |
) | |
} | |
fun <I, O> Fragment.registerForActivityResultSuspend( | |
contract: ActivityResultContract<I, O>, | |
input: I, | |
): suspend () -> O { | |
val function = registerForActivityResultSuspend(contract) | |
return { function(input) } | |
} | |
fun <I, O, A> Fragment.registerForActivityResultCallback( | |
transform: (A) -> I, | |
contract: ActivityResultContract<I, O>, | |
): (input: A, callback: (result: O) -> Unit) -> Unit { | |
return registerForActivityResultCallbackInternal( | |
contract = contract, | |
registerForActivityResult = ::registerForActivityResult, | |
transform = transform, | |
scope = lifecycleScope, | |
) | |
} | |
fun <I, O> Fragment.registerForActivityResultCallback( | |
contract: ActivityResultContract<I, O>, | |
): (input: I, callback: (result: O) -> Unit) -> Unit { | |
return registerForActivityResultCallback( | |
contract = contract, | |
transform = { it }, | |
) | |
} | |
fun <I, O> Fragment.registerForActivityResultCallback( | |
contract: ActivityResultContract<I, O>, | |
input: I, | |
): (callback: (result: O) -> Unit) -> Unit { | |
val function = registerForActivityResultCallback(contract) | |
return { callback -> | |
function(input, callback) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// --- Normal usage --- | |
private val requestPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> | |
if (isGranted) { | |
getImageFromGallery.launch("image/*") | |
} | |
} | |
private val getImageFromGallery = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> | |
if (uri == null) { | |
return@registerForActivityResult | |
} | |
cropImage.launch( | |
CropImageContractOptions( | |
uri = uri, | |
cropImageOptions = CropImageOptions().apply { | |
fixAspectRatio = true | |
} | |
) | |
) | |
} | |
private val cropImage = registerForActivityResult(CropImageContract()) { result -> | |
if (!result.isSuccessful) { | |
return@registerForActivityResult | |
} | |
val imagePath = result.getUriFilePath(requireContext(), uniqueName = true).orEmpty() | |
val imageFile = File(imagePath) | |
// TODO: Perform some operation | |
} | |
btnSelectPicture.setOnClickListener { | |
requestPermission.launch( | |
when { | |
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> Manifest.permission.READ_MEDIA_IMAGES | |
else -> Manifest.permission.READ_EXTERNAL_STORAGE | |
} | |
) | |
} | |
// --- Suspend usage --- | |
private val requestReadImagesPermission = registerForActivityResultSuspend( | |
contract = ActivityResultContracts.RequestPermission(), | |
input = when { | |
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> Manifest.permission.READ_MEDIA_IMAGES | |
else -> Manifest.permission.READ_EXTERNAL_STORAGE | |
}, | |
) | |
private val selectImageFromGallery = registerForActivityResultSuspend( | |
contract = ActivityResultContracts.GetContent(), | |
input = "image/*", | |
) | |
private val cropImage = registerForActivityResultSuspend( | |
contract = CropImageContract(), | |
transform = { uri: Uri -> | |
CropImageContractOptions( | |
uri = uri, | |
cropImageOptions = CropImageOptions().apply { | |
fixAspectRatio = true | |
} | |
) | |
} | |
) | |
btnSelectPicture.setOnClickListener { | |
lifecycleScope.launch { | |
val isGranted = requestReadImagesPermission() | |
if (!isGranted) { | |
return@launch | |
} | |
val uri = selectImageFromGallery() ?: return@launch | |
val cropResult = cropImage(uri) | |
if (!cropResult.isSuccessful) { | |
return@launch | |
} | |
val imagePath = cropResult.getUriFilePath(requireContext(), uniqueName = true).orEmpty() | |
val imageFile = File(imagePath) | |
// TODO: Perform some operation | |
} | |
} | |
// --- Callback usage --- | |
private val requestReadImagesPermission = registerForActivityResultCallback( | |
contract = ActivityResultContracts.RequestPermission(), | |
input = when { | |
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> Manifest.permission.READ_MEDIA_IMAGES | |
else -> Manifest.permission.READ_EXTERNAL_STORAGE | |
}, | |
) | |
private val selectImageFromGallery = registerForActivityResultCallback( | |
contract = ActivityResultContracts.GetContent(), | |
input = "image/*", | |
) | |
private val cropImage = registerForActivityResultCallback( | |
contract = CropImageContract(), | |
transform = { uri: Uri -> | |
CropImageContractOptions( | |
uri = uri, | |
cropImageOptions = CropImageOptions().apply { | |
fixAspectRatio = true | |
} | |
) | |
} | |
btnSelectPicture.setOnClickListener { | |
requestReadImagesPermission { isGranted -> | |
if (!isGranted) { | |
return@requestReadImagesPermission | |
} | |
selectImageFromGallery { uri -> | |
if (uri == null) { | |
return@selectImageFromGallery | |
} | |
cropImage(uri) { result -> | |
if (!result.isSuccessful) { | |
return@cropImage | |
} | |
val imagePath = result.getUriFilePath(requireContext(), uniqueName = true).orEmpty() | |
val imageFile = File(imagePath) | |
// TODO: Perform some operation | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment