https://kotlinlang.org logo
#compose
Title
# compose
b

Berkeli Alashov

06/29/2022, 11:04 PM
Hello. I'm trying to implement accessibility requirements for a reusable component called
FullScreenLoading
. Requirements include to announce loading starting and finishing via TalkBack. I have two possible implementations using
DisposableEffect
in loading icon's composable scope and both are not ideal. Including code in comments. Any ideas to improve option 1 (from code) or maybe I should go with option 2 in cases like this?
https://pastebin.com/Euea93iq
Copy code
@Composable
fun FullScreenLoading(
    isLoading: Boolean,
    modifier: Modifier = Modifier,
    iconSize: Dp = 32.dp,
    content: @Composable BoxScope.() -> Unit
) {
    val loadingContentCd = "Loading"
    val finishedLoadingCd = "Finished Loading"

    var finishedLoading by remember { mutableStateOf(false) }

    // option 1: works for normal usages, but not when compose screen is closed quickly after loading is finished (isLoading=false state is not reached before navigating away)
    // possible fix for that is to delay navigating away shortly after loading is finished  (~350ms delay seems to be enough on my test devices, but not 325ms.. not ideal since it could require different delays for different devices)
    if (finishedLoading) {
        Text(text = " ", modifier = Modifier
            .focusable(false)
            .semantics {
                liveRegion = LiveRegionMode.Assertive
                contentDescription = finishedLoadingCd
            }
        )
    }

    Box(modifier.fillMaxSize()) {
        content()
        if (isLoading) {
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.White.copy(alpha = 0.8f))
                    //.simpleClickable { Timber.i("FullScreenLoading clicked") }
                    .semantics {
                        liveRegion = LiveRegionMode.Assertive
                        contentDescription = loadingContentCd
                    }
            ) {
                // Loading icon goes here
                Box(
                    modifier = Modifier
                        .size(iconSize)
                        .align(Alignment.Center)
                        .background(Color.Red)
                )
                val context = LocalContext.current
                DisposableEffect(context) {
                    onDispose {
                        // option 1:
                        finishedLoading = true

                        // option 2: use announceForAccessibility through host activity
                        // announceForAccessibility is not recommended even for View system anymore and Compose won't have support for it: <https://issuetracker.google.com/issues/172590945>
                        // context.findActivity().getRootView().announceForAccessibility(finishedLoadingCd)
                    }
                }
            }
        }
    }
}

@Preview
@Composable
private fun FullScreenLoadingPreview() {
    var isLoading by remember { mutableStateOf(true) }

    LaunchedEffect(Unit) {
        while (true) {
            delay(3000)
            isLoading = !isLoading
        }
    }
    FullScreenLoading(isLoading = isLoading) {
        Text(text = "Content")
    }
}
49 Views