Skip to content

Instantly share code, notes, and snippets.

@ElianFabian
Last active May 9, 2024 17:48
Show Gist options
  • Save ElianFabian/fda661eb778c74538cec536f7231a621 to your computer and use it in GitHub Desktop.
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.
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)
}
}
// --- 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