Repository: android/compose-samples
File: Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyFragment.kt (Kotlin)

Code before:
    @OptIn(ExperimentalAnimationApi::class)
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            // In order for savedState to work, the same ID needs to be used for all instances.
            id = R.id.sign_in_fragment

            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
            setContent {
                JetsurveyTheme {
                    val state = viewModel.uiState.observeAsState().value ?: return@JetsurveyTheme
                    AnimatedContent(
                        targetState = state,
                        transitionSpec = {
                            fadeIn() + slideIntoContainer(
                                towards = AnimatedContentScope
                                    .SlideDirection.Up,
                                animationSpec = tween(600)
                            ) with
                                fadeOut(animationSpec = tween(200))
                        }
                    ) { targetState ->
                        // It's important to use targetState and not state, as its critical to ensure
                        // a successful lookup of all the incoming and outgoing content during
                        // content transform.
                        when (targetState) {
                            is SurveyState.Questions -> SurveyQuestionsScreen(
                                questions = targetState,
                                shouldAskPermissions = viewModel.askForPermissions,
                                onAction = { id, action -> handleSurveyAction(id, action) },
                                onDoNotAskForPermissions = { viewModel.doNotAskForPermissions() },
                                onDonePressed = { viewModel.computeResult(targetState) },
                                onBackPressed = {
                                    activity?.onBackPressedDispatcher?.onBackPressed()
                                }
                            )
                            is SurveyState.Result -> SurveyResultScreen(
                                result = targetState,
                                onDonePressed = {
                                    activity?.onBackPressedDispatcher?.onBackPressed()
                                }
                            )
                        }
                    }
                }

Review comment (performance):
Minor nit that the 600 on line 74 and 200 on line 76 feel like magic numbers. It'd be cool to have them defined in the theme and grab them as composition locals, but maybe it's enough to just make them constants since they're only used here. I'm also wondering if these two numbers have a relationship, so it can be something like fastAnimationDuration = 200, slowAnimationDuration = fastAnimationDuration * 3? Or if they should be independent.

Code after:
    @OptIn(ExperimentalAnimationApi::class)
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            // In order for savedState to work, the same ID needs to be used for all instances.
            id = R.id.sign_in_fragment

            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
            setContent {
                JetsurveyTheme {
                    val state = viewModel.uiState.observeAsState().value ?: return@JetsurveyTheme
                    AnimatedContent(
                        targetState = state,
                        transitionSpec = {
                            fadeIn() + slideIntoContainer(
                                towards = AnimatedContentScope
                                    .SlideDirection.Up,
                                animationSpec = tween(ANIMATION_SLIDE_IN_DURATION)
                            ) with
                                fadeOut(animationSpec = tween(ANIMATION_FADE_OUT_DURATION))
                        }
                    ) { targetState ->
                        // It's important to use targetState and not state, as its critical to ensure
                        // a successful lookup of all the incoming and outgoing content during
                        // content transform.
                        when (targetState) {
                            is SurveyState.Questions -> SurveyQuestionsScreen(
                                questions = targetState,
                                shouldAskPermissions = viewModel.askForPermissions,
                                onAction = { id, action -> handleSurveyAction(id, action) },
                                onDoNotAskForPermissions = { viewModel.doNotAskForPermissions() },
                                onDonePressed = { viewModel.computeResult(targetState) },
                                onBackPressed = {
                                    activity?.onBackPressedDispatcher?.onBackPressed()
                                }
                            )
                            is SurveyState.Result -> SurveyResultScreen(
                                result = targetState,
                                onDonePressed = {
                                    activity?.onBackPressedDispatcher?.onBackPressed()
                                }
                            )
                        }
                    }
                }