agung Dwi
agung Dwi

Reputation: 73

Jetpack Compose Shared Element Transition Bug?

so i have this shared element transition bug or maybe i just implement it wrong in compose, you can see the bug on this video video.

as you can see at first the transition between list screen and detail screen (add edit screen) work properly and after open the (add edit screen) from FAB to add new note the transition broken and just showing the item gone and pop up again without resize mode transition like the first time, this issue happen too if i search notes or delete and undo delete of a note really fast.

and this my code

Note Content Code:


@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun NoteContent(
    state: NoteState,
    query: String,
    onOrderChange: (NoteOrder) -> Unit,
    onToggleOrderSectionClick: () -> Unit,
    onDeleteClick: (Note) -> Unit,
    onUndoClick: () -> Unit,
    navigateToAddEdit: (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 = {navigateToAddEdit(-1,-1)},
                    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)
            ) {
                LazyVerticalStaggeredGrid(
                    state = staggeredGridState,
                    columns = StaggeredGridCells.Fixed(2),
                    modifier = Modifier.fillMaxWidth(),
                    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 ->
                        AnimatedVisibility(
                            visible = state.notes.isNotEmpty(),
                            enter = fadeIn(),
                            exit = fadeOut(),
                        ) {
                            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 -> navigateToAddEdit(it1, note.color) }

                                })

                        }
                    }
                }

                AnimatedVisibility(
                    visible = state.notes.isEmpty(),
                    enter = fadeIn(),
                    exit = fadeOut(),
                ) {
                    EmptyNote(
                        isSearchActive = state.isSearchActive,

                        )
                }
            }

        }

    }
}

AddEditScreen Code :


@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun AddEditNoteScreen(
    navController: NavController,
    noteColor : Int,
    noteId:Int?,
    viewModel: AddEditViewModel = hiltViewModel(),
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope
){
    val titleState = viewModel.title.value
    val contentState = viewModel.content.value

    val snackbarHostState = remember { SnackbarHostState() }

    Log.d("test",  noteColor.toString())

    val backgroundAnimateableColor = remember {
        Animatable(
            Color(if (noteColor != -1) noteColor else viewModel.color.value)
        )
    }

    if(noteColor != -1){
        viewModel.onEvent(AddEditNoteEvent.ChangeColor(noteColor))
    }

    val scope = rememberCoroutineScope()

    LaunchedEffect(key1 = true) {
        viewModel.eventFlow.collectLatest { event ->
            when (event) {
                is AddEditViewModel.UiEvent.ShowSnackbar -> {
                    snackbarHostState.showSnackbar(
                        message = event.message
                    )
                }
                is AddEditViewModel.UiEvent.SaveNote -> {
                    navController.navigateUp()
                }
            }

        }

    }

    with(sharedTransitionScope){
        Scaffold(
            floatingActionButton = {
                FloatingActionButton(
                    onClick = {
                        viewModel.onEvent(AddEditNoteEvent.SaveNote)
                    },
                    containerColor = MaterialTheme.colorScheme.primary
                ){
                    Icon(imageVector = Icons.Default.Check , contentDescription = "Save" )
                }

            },
            modifier = Modifier
                .sharedBounds(
                    rememberSharedContentState(key = "note/$noteId"),
                    animatedVisibilityScope = animatedVisibilityScope,
                    enter = fadeIn(),
                    exit = fadeOut(),
                    resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds()
                ),

            snackbarHost = { SnackbarHost(hostState = snackbarHostState) },


            ) { paddingValues ->
            Column(
                modifier = Modifier
                    .background(backgroundAnimateableColor.value)
                    .fillMaxSize()
                    .padding(20.dp)
                    .padding(paddingValues)


            ){

                Row(modifier = Modifier
                    .fillMaxWidth()
                    .padding(8.dp),
                    horizontalArrangement = Arrangement.SpaceBetween
                ){
                    Utils.noteColors.forEach{ color ->
                        val colorInt = color.toArgb()
                        Box(
                            modifier = Modifier
                                .size(50.dp)
                                .shadow(15.dp, CircleShape)
                                .clip(CircleShape)
                                .background(color)
                                .border(
                                    width = 3.dp,
                                    color = if (backgroundAnimateableColor.value.toArgb() == colorInt) {
                                        Color.Black
                                    } else Color.Transparent,
                                    shape = CircleShape
                                )
                                .clickable {
                                    scope.launch {
                                        backgroundAnimateableColor.animateTo(
                                            targetValue = Color(colorInt),
                                            animationSpec = tween(
                                                delayMillis = 300
                                            )
                                        )
                                    }
                                    viewModel.onEvent(AddEditNoteEvent.ChangeColor(colorInt))
                                }
                        )

                    }
                }
                Spacer(modifier = Modifier.height(16.dp))
                TrasnparentHintTextField(
                    text = titleState.text,
                    hint = titleState.hint,
                    onValueChange = {
                        viewModel.onEvent(AddEditNoteEvent.EnteredTitle(it))
                    },
                    onFocusChange = {
                        viewModel.onEvent(AddEditNoteEvent.IsFocusTitle(it))
                    },
                    isHintVisible = titleState.isHintVisible,
                    singleLine = true,
                    textStyle = MaterialTheme.typography.headlineSmall
                )
                Spacer(modifier = Modifier.height(16.dp))
                TrasnparentHintTextField(
                    text = contentState.text,
                    hint = contentState.hint,
                    onValueChange = {
                        viewModel.onEvent(AddEditNoteEvent.EnteredContent(it))
                    },
                    onFocusChange = {
                        viewModel.onEvent(AddEditNoteEvent.IsFocusContent(it))
                    },
                    isHintVisible = contentState.isHintVisible,
                    singleLine = false,
                    textStyle = MaterialTheme.typography.bodyLarge
                )

            }

        }

    }


}

Anyone know how to fix it? P.S : i think this happen if a note recompose

I already try changing to shareElement modifier, and change the modifier location

Upvotes: 2

Views: 598

Answers (0)

Related Questions