androidandroid-jetpack-composeandroid-jetpack-navigationshared-element-transitionandroid-jetpack-compose-material3

Jetpack compose preview for screen that have SharedTransitionScope, AnimatedVisibilityScope as parameter


I have this implementation for my Screen that use shared element transition

}

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun NoteContent(
    state: NoteState,
    query: String,
    onOrderChange: (NoteOrder) -> Unit,
    onToggleOrderSectionClick: () -> Unit,
    onDeleteClick: (Note) -> Unit,
    onUndoClick: () -> Unit,
    navigateToAdd: () -> Unit,
    navigateToEdit: (Int, Int) -> Unit,
    onQueryChange: (String) -> Unit,
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope
) {
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()
    val staggeredGridState = rememberLazyStaggeredGridState()

    with(sharedTransitionScope){
        Scaffold(
            floatingActionButton = {
                FloatingActionButton(
                    onClick = navigateToAdd, containerColor = MaterialTheme.colorScheme.primary
                ) {
                    Icon(imageVector = Icons.Default.Add, contentDescription = "Add Note")
                }
            },
            snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
        ) { padding ->
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(horizontal = 8.dp)
            ) {

                Spacer(modifier = Modifier.height(8.dp))

                LazyVerticalStaggeredGrid(
                    state = staggeredGridState,
                    columns = StaggeredGridCells.Fixed(2),
                    modifier = Modifier.fillMaxSize(),
                    contentPadding = PaddingValues(bottom = 4.dp),
                    horizontalArrangement = Arrangement.spacedBy(4.dp),
                    verticalItemSpacing = 4.dp
                ) {
                    item(span = StaggeredGridItemSpan.FullLine) {
                        SearchBar(
                            query = query,
                            onQueryChange = onQueryChange,
                            onToggleOrderSectionClick = onToggleOrderSectionClick
                        )
                    }

                    item(span = StaggeredGridItemSpan.FullLine) {
                        AnimatedVisibility(
                            visible = state.isOrderSectionVisible,
                            enter = fadeIn() + slideInVertically(),
                            exit = fadeOut() + slideOutVertically()
                        ) {
                            OrderRadio(modifier = Modifier
                                .fillMaxWidth()
                                .padding(vertical = 8.dp, horizontal = 8.dp),
                                noteOrder = state.noteOrder,
                                onOrderChange = { order ->
                                    onOrderChange(order)
                                })
                        }
                    }



                    items(state.notes, key = { it.id ?: 0 }) { note ->
                        NoteItem(note = note,
                            modifier = Modifier
                                .padding(4.dp)
                                .fillMaxWidth()
                                .animateItem(
                                    placementSpec = tween(
                                        durationMillis = 300, delayMillis = 0
                                    )
                                )
                                .sharedBounds(
                                    rememberSharedContentState(key = "note/${note.id}"),
                                    animatedVisibilityScope = animatedVisibilityScope,
                                    enter = fadeIn(),
                                    exit = fadeOut(),
                                    resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds()
                                ), // This line adds the placement animation
                            onDeleteClick = {
                                onDeleteClick(note)
                                scope.launch {
                                    val result = snackbarHostState.showSnackbar(
                                        message = "Note deleted", actionLabel = "Undo"
                                    )
                                    if (result == SnackbarResult.ActionPerformed) {
                                        onUndoClick()
                                    }
                                }
                            },
                            onClick = {
                                note.id?.let { it1 -> navigateToEdit(it1, note.color) }

                            })
                    }
                }
            }
        }

    }
}

And i want to make preview of this screen or the content of the screen but idk what to pas for SharedTransitionScope, AnimatedVisibilityScope parameter in preview

fun NoteContentPreview() {
    NotedTheme {

        NoteContent(
            state = DummyNote.dummyNoteState,
            onOrderChange = {},
            query = "",
            onToggleOrderSectionClick = { /*TODO*/ },
            onDeleteClick = {},
            onUndoClick = { /*TODO*/ },
            navigateToAdd = { /*TODO*/ },
            navigateToEdit = { _, _ -> /*TODO*/ },
            onQueryChange = { },
            sharedTransitionScope = ,
            animatedVisibilityScope =
            )
    }
}

anyone know how to solve this without mocking any parameter or i must mocked them to make preview of the screen?, Thank you in advance

I can show Preview for the compose Screen


Solution

  • I am facing the same issue as you since I am implementing shared transition on an app that has previews for every screen. I couldn't find a less verbose solution, but what has worked is to wrap all screens inside the preview composables with SharedTransitionLayout{} and AnimatedVisibility(true){} and pass the scopes to the composable functions.

    To make it a bit less verbose you can make your composable screens be an extension function of SharedTransitionScope.

    This is how it looks like for me

    @Composable
    fun SharedTransitionScope.MyScreen(animatedVisibilityScope:AnimatedVisibilityScope){}
    
    @Composable
    @Preview
    private fun Preview_MyScreen() {
        SharedTransitionLayout {
            AnimatedVisibility(visible = true) {
                MyScreen(animatedVisibilityScope = this)
            }
        }
    }
    

    Interested in knowing if there are better ways to do this as well.