From b9fdaf7ad76d657bf7002c4dad20e394f91dfb66 Mon Sep 17 00:00:00 2001 From: Praneeth Date: Tue, 17 Feb 2026 22:49:15 +0530 Subject: [PATCH 01/10] NavEventSnippets as per the Doc --- .../predictiveback/NavEventSnippets.kt | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt new file mode 100644 index 000000000..012379679 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt @@ -0,0 +1,122 @@ +@file:Suppress("unused", "UNUSED_VARIABLE") + +package com.example.compose.snippets.predictiveback + +import androidx.annotation.MainThread +import androidx.navigationevent.NavigationEvent +import androidx.navigationevent.NavigationEventHandler +import androidx.navigationevent.NavigationEventInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.navigationevent.NavigationEventDispatcher +import androidx.navigationevent.NavigationEventDispatcherOwner +import androidx.navigationevent.NavigationEventInput +import androidx.navigationevent.compose.NavigationEventState + +@Composable +private fun HandlingBackEvents() { + // [START android_compose_predictiveback_navevent_handler] + val myHandler = object: NavigationEventHandler( + initialInfo = NavigationEventInfo.None, + isBackEnabled = true + ) { + override fun onBackStarted(event: NavigationEvent) { + // Prepare for the back event + } + + override fun onBackProgressed(event: NavigationEvent) { + // Use event.progress for predictive animations + } + + // This is the required method for final event handling + override fun onBackCompleted() { + // Complete the back event + } + + override fun onBackCancelled() { + // Cancel the back event + } + } + // [END android_compose_predictiveback_navevent_handler] +} + +// [START android_compose_predictiveback_navevent_register_handler] +@Composable +fun RegisterHandler( + navigationEventDispatcher: NavigationEventDispatcher, + myHandler: NavigationEventHandler<*> +) { + DisposableEffect(navigationEventDispatcher, myHandler) { + navigationEventDispatcher.addHandler(myHandler) + onDispose { + myHandler.remove() + } + } +} +// [END android_compose_predictiveback_navevent_register_handler] + + + +// [START android_compose_predictiveback_navevent_NavigationEventHandler] +@Composable +public fun NavigationBackHandler( + state: NavigationEventState, + isBackEnabled: Boolean = true, + onBackCancelled: () -> Unit = {}, + onBackCompleted: () -> Unit, +){ + +} +// [END android_compose_predictiveback_navevent_NavigationEventHandler] + + +// [START android_compose_predictiveback_navevent_NavigationEvent_dispatcher_owner] +class MyComponent: NavigationEventDispatcherOwner { + override val navigationEventDispatcher: NavigationEventDispatcher = + NavigationEventDispatcher() +} +// [END android_compose_predictiveback_navevent_NavigationEvent_dispatcher_owner] + + +// [START android_compose_predictiveback_navevent_navigation_event_input] +public class MyInput : NavigationEventInput() { + @MainThread + public fun backStarted(event: NavigationEvent) { + dispatchOnBackStarted(event) + } + + @MainThread + public fun backProgressed(event: NavigationEvent) { + dispatchOnBackProgressed(event) + } + + public fun backCancelled() { + dispatchOnBackCancelled() + } + + @MainThread + public fun backCompleted() { + dispatchOnBackCompleted() + } +} +// [END android_compose_predictiveback_navevent_navigation_event_input] + +// TODO: We then need to provide that input to our dispatcher: +// [START android_compose_predictiveback_navevent_register_input] +fun setupDispatcher() { + val myComponent = MyComponent() + val myInput = MyInput() + + // Register the custom input with the dispatcher + myComponent.navigationEventDispatcher.addInput(myInput) +} +// [END android_compose_predictiveback_navevent_register_input] + +// [START android_compose_predictiveback_navevent_dispose] +fun cleanupDispatcher(myComponent: MyComponent) { + // Explicitly remove the dispatcher from the hierarchy when the component is destroyed + myComponent.navigationEventDispatcher.dispose() +} +// [END android_compose_predictiveback_navevent_dispose] + + From 347588233ec07fe2850737d6aeac31dee03eb051 Mon Sep 17 00:00:00 2001 From: praneethatchana <247144440+praneethatchana@users.noreply.github.com> Date: Tue, 17 Feb 2026 17:26:30 +0000 Subject: [PATCH 02/10] Apply Spotless --- .../snippets/predictiveback/NavEventSnippets.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt index 012379679..955874ec3 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + @file:Suppress("unused", "UNUSED_VARIABLE") package com.example.compose.snippets.predictiveback From 393a1ee81ad64915408b5ab8bd9870a0e95e1bae Mon Sep 17 00:00:00 2001 From: Praneeth Date: Wed, 4 Mar 2026 16:22:19 +0530 Subject: [PATCH 03/10] NavEventSnippets Added the New class for the Animation. --- .../snippets/predictiveback/Nav3Snippets.kt | 112 ++++++++++++++++++ .../predictiveback/NavEventSnippets.kt | 63 +++++++--- 2 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt new file mode 100644 index 000000000..4d7e54d65 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt @@ -0,0 +1,112 @@ +@file:Suppress("unused", "UNUSED_VARIABLE") + +package com.example.compose.snippets.predictiveback + +import android.os.Bundle +import android.util.Log +import android.view.MotionEvent.EDGE_LEFT +import android.view.MotionEvent.EDGE_RIGHT +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp +import androidx.navigationevent.NavigationEventInfo +import androidx.navigationevent.NavigationEventTransitionState +import androidx.navigationevent.compose.NavigationEventState +import androidx.navigationevent.compose.rememberNavigationEventState + + +// [START android_compose_predictiveback_navevent_animation] +object Routes { + const val SCREEN_A = "Screen A" + const val SCREEN_B = "Screen B" +} + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + var state by remember { mutableStateOf(Routes.SCREEN_A) } + val backEventState = rememberNavigationEventState(currentInfo = NavigationEventInfo.None) + + when (state) { + Routes.SCREEN_A -> { + ScreenA { state = Routes.SCREEN_B } + } + else -> { + if (backEventState.transitionState is NavigationEventTransitionState.InProgress) { + ScreenA { } + } + ScreenB( + backEventState = backEventState, + onBackCompleted = { state = Routes.SCREEN_A } + ) + } + } + } + } +} + +@Composable +fun ScreenB( + backEventState: NavigationEventState, + onBackCompleted: () -> Unit = {}, +) { + + var backProgress by remember { mutableFloatStateOf(0f) } + var swipeEdge by remember { mutableIntStateOf(0) } + + when (val transitionState = backEventState.transitionState) { + is NavigationEventTransitionState.InProgress -> { + backProgress = transitionState.latestEvent.progress + swipeEdge = transitionState.latestEvent.swipeEdge + Log.d("BackGesture", "Progress: ${transitionState.latestEvent.progress}") + } + is NavigationEventTransitionState.Idle -> { + Log.d("BackGesture", "Idle") + } + } + + val animatedScale by animateFloatAsState( + targetValue = 1f - (backProgress * 0.1f), + label = "ScaleAnimation" + ) + + // TODO: this should be hoisted up + val maxShift = ( LocalConfiguration.current.screenWidthDp / 20f) - 8 + + val animatedOffsetX by animateDpAsState( + targetValue = when (swipeEdge) { + EDGE_LEFT -> (backProgress * maxShift).dp + EDGE_RIGHT -> (-backProgress * maxShift).dp + else -> 0.dp + }, + label = "OffsetXAnimation" + ) + + NavigationBackHandler( + state = backEventState, + onBackCompleted = onBackCompleted, + isBackEnabled = true + ) + + // Rest of UI +} +// [END android_compose_predictiveback_navevent_animation] + +@Composable +fun ScreenA(onNavigate: () -> Unit) { + // Basic ScreenA implementation for snippet +} + diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt index 955874ec3..67d2582f7 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt @@ -19,18 +19,18 @@ package com.example.compose.snippets.predictiveback import androidx.annotation.MainThread -import androidx.navigationevent.NavigationEvent -import androidx.navigationevent.NavigationEventHandler -import androidx.navigationevent.NavigationEventInfo import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect +import androidx.navigationevent.NavigationEvent import androidx.navigationevent.NavigationEventDispatcher import androidx.navigationevent.NavigationEventDispatcherOwner +import androidx.navigationevent.NavigationEventHandler +import androidx.navigationevent.NavigationEventInfo import androidx.navigationevent.NavigationEventInput +import androidx.navigationevent.NavigationEventTransitionState import androidx.navigationevent.compose.NavigationEventState +import androidx.navigationevent.compose.rememberNavigationEventState -@Composable -private fun HandlingBackEvents() { +private fun handlingBackEvents() { // [START android_compose_predictiveback_navevent_handler] val myHandler = object: NavigationEventHandler( initialInfo = NavigationEventInfo.None, @@ -57,17 +57,12 @@ private fun HandlingBackEvents() { } // [START android_compose_predictiveback_navevent_register_handler] -@Composable -fun RegisterHandler( +fun registerHandler( navigationEventDispatcher: NavigationEventDispatcher, myHandler: NavigationEventHandler<*> ) { - DisposableEffect(navigationEventDispatcher, myHandler) { - navigationEventDispatcher.addHandler(myHandler) - onDispose { - myHandler.remove() - } - } + navigationEventDispatcher.addHandler(myHandler) + myHandler.remove() } // [END android_compose_predictiveback_navevent_register_handler] @@ -85,6 +80,41 @@ public fun NavigationBackHandler( } // [END android_compose_predictiveback_navevent_NavigationEventHandler] +// [START android_compose_predictiveback_navevent_transitionstate_with_backhandler] +@Composable +fun HandlingBackWithTransitionState( + onNavigateUp: () -> Unit +) { + val navigationState = rememberNavigationEventState( + currentInfo = NavigationEventInfo.None + ) + + val transitionState = navigationState.transitionState + + // React to predictive back transition updates + when (transitionState) { + is NavigationEventTransitionState.InProgress -> { + val progress = transitionState.latestEvent.progress + // Use progress (0f..1f) to update UI during the gesture + } + + is NavigationEventTransitionState.Idle -> { + // Reset any temporary UI state if the gesture is cancelled + } + } + + NavigationBackHandler( + state = navigationState, + onBackCancelled = { + // Called if the back gesture is cancelled + }, + onBackCompleted = { + // Called when the back gesture fully completes + onNavigateUp() + } + ) +} +// [END android_compose_predictiveback_navevent_transitionstate_with_backhandler] // [START android_compose_predictiveback_navevent_NavigationEvent_dispatcher_owner] class MyComponent: NavigationEventDispatcherOwner { @@ -117,7 +147,6 @@ public class MyInput : NavigationEventInput() { } // [END android_compose_predictiveback_navevent_navigation_event_input] -// TODO: We then need to provide that input to our dispatcher: // [START android_compose_predictiveback_navevent_register_input] fun setupDispatcher() { val myComponent = MyComponent() @@ -133,6 +162,4 @@ fun cleanupDispatcher(myComponent: MyComponent) { // Explicitly remove the dispatcher from the hierarchy when the component is destroyed myComponent.navigationEventDispatcher.dispose() } -// [END android_compose_predictiveback_navevent_dispose] - - +// [END android_compose_predictiveback_navevent_dispose] \ No newline at end of file From 4d988df257d3e9cfc1dfe16483bb27990c4a6471 Mon Sep 17 00:00:00 2001 From: praneethatchana <247144440+praneethatchana@users.noreply.github.com> Date: Wed, 4 Mar 2026 10:56:09 +0000 Subject: [PATCH 04/10] Apply Spotless --- .../snippets/predictiveback/Nav3Snippets.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt index 4d7e54d65..87c34aef2 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + @file:Suppress("unused", "UNUSED_VARIABLE") package com.example.compose.snippets.predictiveback From e3cdedb99c1489063beae08c4d7a1a1df11af22e Mon Sep 17 00:00:00 2001 From: Praneeth Date: Thu, 5 Mar 2026 11:29:32 +0530 Subject: [PATCH 05/10] Resolved the GIT comments --- .../predictiveback/NavEventSnippets.kt | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt index 67d2582f7..b14758641 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt @@ -56,18 +56,6 @@ private fun handlingBackEvents() { // [END android_compose_predictiveback_navevent_handler] } -// [START android_compose_predictiveback_navevent_register_handler] -fun registerHandler( - navigationEventDispatcher: NavigationEventDispatcher, - myHandler: NavigationEventHandler<*> -) { - navigationEventDispatcher.addHandler(myHandler) - myHandler.remove() -} -// [END android_compose_predictiveback_navevent_register_handler] - - - // [START android_compose_predictiveback_navevent_NavigationEventHandler] @Composable public fun NavigationBackHandler( @@ -136,6 +124,7 @@ public class MyInput : NavigationEventInput() { dispatchOnBackProgressed(event) } + @MainThread public fun backCancelled() { dispatchOnBackCancelled() } @@ -146,20 +135,3 @@ public class MyInput : NavigationEventInput() { } } // [END android_compose_predictiveback_navevent_navigation_event_input] - -// [START android_compose_predictiveback_navevent_register_input] -fun setupDispatcher() { - val myComponent = MyComponent() - val myInput = MyInput() - - // Register the custom input with the dispatcher - myComponent.navigationEventDispatcher.addInput(myInput) -} -// [END android_compose_predictiveback_navevent_register_input] - -// [START android_compose_predictiveback_navevent_dispose] -fun cleanupDispatcher(myComponent: MyComponent) { - // Explicitly remove the dispatcher from the hierarchy when the component is destroyed - myComponent.navigationEventDispatcher.dispose() -} -// [END android_compose_predictiveback_navevent_dispose] \ No newline at end of file From dfb6511bfc3877e292581ba0a8e4659bc3e3c0cf Mon Sep 17 00:00:00 2001 From: Praneeth Date: Thu, 5 Mar 2026 23:17:19 +0530 Subject: [PATCH 06/10] Resolved the TODO comment in the Nav3Snippets --- .../example/compose/snippets/predictiveback/Nav3Snippets.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt index 87c34aef2..f76ac5ee9 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt @@ -99,8 +99,7 @@ fun ScreenB( label = "ScaleAnimation" ) - // TODO: this should be hoisted up - val maxShift = ( LocalConfiguration.current.screenWidthDp / 20f) - 8 + val maxShift = (LocalConfiguration.current.screenWidthDp / 20f) - 8 val animatedOffsetX by animateDpAsState( targetValue = when (swipeEdge) { From 5dbef5ad64d16a851ffb0d47c8fcf63e6411d3c1 Mon Sep 17 00:00:00 2001 From: Praneeth Date: Fri, 6 Mar 2026 12:39:55 +0530 Subject: [PATCH 07/10] Resolved the GIT comment in the Nav3Snippets --- .../snippets/predictiveback/Nav3Snippets.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt index f76ac5ee9..4023a2518 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt @@ -18,13 +18,13 @@ package com.example.compose.snippets.predictiveback +import android.annotation.SuppressLint import android.os.Bundle import android.util.Log import android.view.MotionEvent.EDGE_LEFT import android.view.MotionEvent.EDGE_RIGHT import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -74,6 +74,7 @@ class MainActivity : ComponentActivity() { } } +@SuppressLint("ConfigurationScreenWidthHeight") @Composable fun ScreenB( backEventState: NavigationEventState, @@ -99,16 +100,16 @@ fun ScreenB( label = "ScaleAnimation" ) - val maxShift = (LocalConfiguration.current.screenWidthDp / 20f) - 8 + val configuration = LocalConfiguration.current + val maxShift = remember(configuration) { + (configuration.screenWidthDp / 20f) - 8 + } - val animatedOffsetX by animateDpAsState( - targetValue = when (swipeEdge) { - EDGE_LEFT -> (backProgress * maxShift).dp - EDGE_RIGHT -> (-backProgress * maxShift).dp - else -> 0.dp - }, - label = "OffsetXAnimation" - ) + val offsetX = when (swipeEdge) { + EDGE_LEFT -> (backProgress * maxShift).dp + EDGE_RIGHT -> (-backProgress * maxShift).dp + else -> 0.dp + } NavigationBackHandler( state = backEventState, From 73e9bcf3b6f5353e88fd3cfabb3500f9c2764cef Mon Sep 17 00:00:00 2001 From: Praneeth Date: Tue, 10 Mar 2026 00:33:34 +0530 Subject: [PATCH 08/10] There are two additional improvements made to the snippet. First, the gesture animation is now directly driven by `backProgress`, which simplifies the animation logic and makes the relationship between the gesture progress and the UI transformation clearer. Second, `backProgress` and `swipeEdge` are now derived directly from `backEventState` instead of being stored in mutable Compose state. This avoids mutating state during composition, reduces unnecessary state management, and keeps the snippet simpler and easier to follow. Overall, these changes make the example more straightforward, improve readability, and align the snippet with recommended Compose patterns. --- .../snippets/predictiveback/Nav3Snippets.kt | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt index 4023a2518..ff700cb1c 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt @@ -26,6 +26,8 @@ import android.view.MotionEvent.EDGE_RIGHT import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.offset import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf @@ -33,6 +35,8 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.dp import androidx.navigationevent.NavigationEventInfo @@ -80,19 +84,17 @@ fun ScreenB( backEventState: NavigationEventState, onBackCompleted: () -> Unit = {}, ) { - - var backProgress by remember { mutableFloatStateOf(0f) } - var swipeEdge by remember { mutableIntStateOf(0) } - - when (val transitionState = backEventState.transitionState) { - is NavigationEventTransitionState.InProgress -> { - backProgress = transitionState.latestEvent.progress - swipeEdge = transitionState.latestEvent.swipeEdge - Log.d("BackGesture", "Progress: ${transitionState.latestEvent.progress}") - } - is NavigationEventTransitionState.Idle -> { - Log.d("BackGesture", "Idle") - } + val transitionState = backEventState.transitionState + val latestEvent = + (transitionState as? NavigationEventTransitionState.InProgress) + ?.latestEvent + val backProgress = latestEvent?.progress ?: 0f + val swipeEdge = latestEvent?.swipeEdge ?: 0 + + if (transitionState is NavigationEventTransitionState.InProgress) { + Log.d("BackGesture", "Progress: ${transitionState.latestEvent.progress}") + } else if (transitionState is NavigationEventTransitionState.Idle) { + Log.d("BackGesture", "Idle") } val animatedScale by animateFloatAsState( @@ -116,8 +118,13 @@ fun ScreenB( onBackCompleted = onBackCompleted, isBackEnabled = true ) - - // Rest of UI + Box( + modifier = Modifier + .offset(x = offsetX) + .scale(animatedScale) + ){ + // Rest of UI + } } // [END android_compose_predictiveback_navevent_animation] From e9b97c819be98056e2f42cc92e8516c9a1702eb1 Mon Sep 17 00:00:00 2001 From: Praneeth Date: Tue, 10 Mar 2026 00:34:28 +0530 Subject: [PATCH 09/10] There are two additional improvements made to the snippet. First, the gesture animation is now directly driven by `backProgress`, which simplifies the animation logic and makes the relationship between the gesture progress and the UI transformation clearer. Second, `backProgress` and `swipeEdge` are now derived directly from `backEventState` instead of being stored in mutable Compose state. This avoids mutating state during composition, reduces unnecessary state management, and keeps the snippet simpler and easier to follow. Overall, these changes make the example more straightforward, improve readability, and align the snippet with recommended Compose patterns. --- .../com/example/compose/snippets/predictiveback/Nav3Snippets.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt index ff700cb1c..b5b5b0f31 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/Nav3Snippets.kt @@ -30,8 +30,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.offset import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue From 2f7f315ea4ef2acef102084441e4f92b8779cfec Mon Sep 17 00:00:00 2001 From: Praneeth Date: Tue, 17 Mar 2026 17:09:36 +0530 Subject: [PATCH 10/10] There are the additional improvements made to the snippet as per the suggestions given. --- .../predictiveback/NavEventSnippets.kt | 136 +++++++++++++++++- 1 file changed, 132 insertions(+), 4 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt index b14758641..a15b1652d 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/NavEventSnippets.kt @@ -18,8 +18,27 @@ package com.example.compose.snippets.predictiveback +import android.annotation.SuppressLint +import android.os.Bundle +import android.util.Log +import android.view.MotionEvent.EDGE_LEFT +import android.view.MotionEvent.EDGE_RIGHT +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.annotation.MainThread +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.offset import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.unit.dp import androidx.navigationevent.NavigationEvent import androidx.navigationevent.NavigationEventDispatcher import androidx.navigationevent.NavigationEventDispatcherOwner @@ -31,6 +50,7 @@ import androidx.navigationevent.compose.NavigationEventState import androidx.navigationevent.compose.rememberNavigationEventState private fun handlingBackEvents() { + val navigationEventDispatcher = NavigationEventDispatcher() // [START android_compose_predictiveback_navevent_handler] val myHandler = object: NavigationEventHandler( initialInfo = NavigationEventInfo.None, @@ -54,6 +74,14 @@ private fun handlingBackEvents() { } } // [END android_compose_predictiveback_navevent_handler] + + // [START android_compose_predictiveback_navevent_handler_register] + navigationEventDispatcher.addHandler(myHandler) + // [END android_compose_predictiveback_navevent_handler_register] + + // [START android_compose_predictiveback_navevent_handler_remove] + myHandler.remove() + // [END android_compose_predictiveback_navevent_handler_remove] } // [START android_compose_predictiveback_navevent_NavigationEventHandler] @@ -76,21 +104,17 @@ fun HandlingBackWithTransitionState( val navigationState = rememberNavigationEventState( currentInfo = NavigationEventInfo.None ) - val transitionState = navigationState.transitionState - // React to predictive back transition updates when (transitionState) { is NavigationEventTransitionState.InProgress -> { val progress = transitionState.latestEvent.progress // Use progress (0f..1f) to update UI during the gesture } - is NavigationEventTransitionState.Idle -> { // Reset any temporary UI state if the gesture is cancelled } } - NavigationBackHandler( state = navigationState, onBackCancelled = { @@ -104,6 +128,88 @@ fun HandlingBackWithTransitionState( } // [END android_compose_predictiveback_navevent_transitionstate_with_backhandler] + +// [START android_compose_predictiveback_navevent_animation] + +object Routes { + const val SCREEN_A = "Screen A" + const val SCREEN_B = "Screen B" +} +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + var state by remember { mutableStateOf(Routes.SCREEN_A) } + val backEventState = rememberNavigationEventState(currentInfo = NavigationEventInfo.None) + when (state) { + Routes.SCREEN_A -> { + ScreenA(onNavigate = { state = Routes.SCREEN_B }) + } + else -> { + if (backEventState.transitionState is NavigationEventTransitionState.InProgress) { + ScreenA(onNavigate = { }) + } + ScreenB( + backEventState = backEventState, + onBackCompleted = { state = Routes.SCREEN_A } + ) + } + } + } + } +} +@SuppressLint("ConfigurationScreenWidthHeight") +@Composable +fun ScreenB( + backEventState: NavigationEventState, + onBackCompleted: () -> Unit = {}, +) { + val transitionState = backEventState.transitionState + val latestEvent = + (transitionState as? NavigationEventTransitionState.InProgress) + ?.latestEvent + val backProgress = latestEvent?.progress ?: 0f + val swipeEdge = latestEvent?.swipeEdge ?: NavigationEvent.EDGE_LEFT + if (transitionState is NavigationEventTransitionState.InProgress) { + Log.d("BackGesture", "Progress: ${transitionState.latestEvent.progress}") + } else if (transitionState is NavigationEventTransitionState.Idle) { + Log.d("BackGesture", "Idle") + } + val animatedScale by animateFloatAsState( + targetValue = 1f - (backProgress * 0.1f), + label = "ScaleAnimation" + ) + val windowInfo = LocalWindowInfo.current + val density = LocalDensity.current + val maxShift = remember(windowInfo, density) { + val widthDp = with(density) { windowInfo.containerSize.width.toDp() } + (widthDp.value / 20f) - 8 + } + val offsetX = when (swipeEdge) { + EDGE_LEFT -> (backProgress * maxShift).dp + EDGE_RIGHT -> (-backProgress * maxShift).dp + else -> 0.dp + } + NavigationBackHandler( + state = backEventState, + onBackCompleted = onBackCompleted, + isBackEnabled = true + ) + Box( + modifier = Modifier + .offset(x = offsetX) + .scale(animatedScale) + ){ + // Rest of UI + } +} +// [END android_compose_predictiveback_navevent_animation] + +@Composable +fun ScreenA(onNavigate: () -> Unit) { + // Basic ScreenA implementation for snippet +} + // [START android_compose_predictiveback_navevent_NavigationEvent_dispatcher_owner] class MyComponent: NavigationEventDispatcherOwner { override val navigationEventDispatcher: NavigationEventDispatcher = @@ -111,6 +217,14 @@ class MyComponent: NavigationEventDispatcherOwner { } // [END android_compose_predictiveback_navevent_NavigationEvent_dispatcher_owner] +// [START android_compose_predictiveback_navevent_activity_own_dispatcher] +class MyCustomActivity : ComponentActivity() { + fun addMyHandler() { + // navigationEventDispatcher provided from the ComponentActivity + navigationEventDispatcher.addHandler(TODO()) + } +} +// [END android_compose_predictiveback_navevent_activity_own_dispatcher] // [START android_compose_predictiveback_navevent_navigation_event_input] public class MyInput : NavigationEventInput() { @@ -135,3 +249,17 @@ public class MyInput : NavigationEventInput() { } } // [END android_compose_predictiveback_navevent_navigation_event_input] + +private fun provideInputToDispatcher() { + val navigationEventDispatcher = NavigationEventDispatcher() + // [START android_compose_predictiveback_navevent_add_input] + navigationEventDispatcher.addInput(MyInput()) + // [END android_compose_predictiveback_navevent_add_input] +} + +private fun disposeDispatcher() { + val navigationEventDispatcher = NavigationEventDispatcher() + // [START android_compose_predictiveback_navevent_dispose] + navigationEventDispatcher.dispose() + // [END android_compose_predictiveback_navevent_dispose] +} \ No newline at end of file