From 89629fb0643cc6db822f91d6058b984efc27b366 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Fri, 18 Aug 2023 15:00:22 -0700 Subject: [PATCH 1/2] Seems to work whether Flutter is the first acitivty or not --- .../embedding/android/FlutterFragment.java | 69 +++++++++++++++++++ .../android/FlutterFragmentActivity.java | 2 + 2 files changed, 71 insertions(+) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 586d60c9c6980..95a8d1d0c6e4c 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -14,6 +14,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnWindowFocusChangeListener; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -170,6 +172,8 @@ public class FlutterFragment extends Fragment protected static final String ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED = "should_automatically_handle_on_back_pressed"; + private boolean hasRegisteredBackCallback = false; + @RequiresApi(18) private final OnWindowFocusChangeListener onWindowFocusChangeListener = Build.VERSION.SDK_INT >= 18 @@ -1023,6 +1027,20 @@ public void handleOnBackPressed() { } }; + private final OnBackInvokedCallback onBackInvokedCallback = + Build.VERSION.SDK_INT >= 33 + ? new OnBackInvokedCallback() { + // TODO(garyq): Remove SuppressWarnings annotation. This was added to workaround + // a google3 bug where the linter is not properly running against API 33, causing + // a failure here. See b/243609613 and https://github.com/flutter/flutter/issues/111295 + @SuppressWarnings("Override") + @Override + public void onBackInvoked() { + onBackPressed(); + } + } + : null; + public FlutterFragment() { // Ensure that we at least have an empty Bundle of arguments so that we don't // need to continually check for null arguments before grabbing one. @@ -1060,9 +1078,11 @@ public void onAttach(@NonNull Context context) { super.onAttach(context); delegate = delegateFactory.createDelegate(this); delegate.onAttach(context); + /* if (getArguments().getBoolean(ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, false)) { requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); } + */ context.registerComponentCallbacks(this); } @@ -1684,6 +1704,55 @@ public boolean popSystemNavigator() { return false; } + @Override + public void setFrameworkHandlesBack(boolean frameworkHandlesBack) { + Log.e( + "justin", + "setFrameworkHandlesBack in FlutterFragment! setEnabled: " + frameworkHandlesBack); + if (frameworkHandlesBack && !hasRegisteredBackCallback) { + registerOnBackInvokedCallback(); + } else if (!frameworkHandlesBack && hasRegisteredBackCallback) { + unregisterOnBackInvokedCallback(); + } + } + + /** + * Registers the callback with OnBackPressedDispatcher to capture back navigation gestures and + * pass them to the framework. + * + *

This replaces the deprecated onBackPressed method override in order to support API 33's + * predictive back navigation feature. + * + *

The callback must be unregistered in order to prevent unpredictable behavior once outside + * the Flutter app. + */ + @VisibleForTesting + public void registerOnBackInvokedCallback() { + if (Build.VERSION.SDK_INT >= 33) { + getActivity() + .getOnBackInvokedDispatcher() + .registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback); + hasRegisteredBackCallback = true; + } + } + + /** + * Unregisters the callback from OnBackPressedDispatcher. + * + *

This should be called when the activity is no longer in use to prevent unpredictable + * behavior such as being stuck and unable to press back. + */ + @VisibleForTesting + public void unregisterOnBackInvokedCallback() { + if (Build.VERSION.SDK_INT >= 33) { + getActivity() + .getOnBackInvokedDispatcher() + .unregisterOnBackInvokedCallback(onBackInvokedCallback); + hasRegisteredBackCallback = false; + } + } + @VisibleForTesting @NonNull boolean shouldDelayFirstAndroidViewDraw() { diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index 4f3ab481b9ee2..6463959491e1b 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -545,6 +545,8 @@ protected FlutterFragment createFlutterFragment() { .shouldAttachEngineToActivity(shouldAttachEngineToActivity()) .destroyEngineWithFragment(shouldDestroyEngineWithHost()) .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw) + // TODO(justinmc): Unsure whether this is relevant to predictive back. + // .shouldAutomaticallyHandleOnBackPressed(Build.VERSION.SDK_INT >= 33) .build(); } else { Log.v( From bf4e1ecb5f0c7c3847ccbc4d0a9309e0fe560d3a Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Fri, 18 Aug 2023 16:00:05 -0700 Subject: [PATCH 2/2] Work with original onBackPressed mechanics --- .../embedding/android/FlutterFragment.java | 72 +++---------------- .../android/FlutterFragmentActivity.java | 6 +- 2 files changed, 12 insertions(+), 66 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 95a8d1d0c6e4c..bf766dac15e24 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -14,8 +14,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnWindowFocusChangeListener; -import android.window.OnBackInvokedCallback; -import android.window.OnBackInvokedDispatcher; import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -172,8 +170,6 @@ public class FlutterFragment extends Fragment protected static final String ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED = "should_automatically_handle_on_back_pressed"; - private boolean hasRegisteredBackCallback = false; - @RequiresApi(18) private final OnWindowFocusChangeListener onWindowFocusChangeListener = Build.VERSION.SDK_INT >= 18 @@ -1027,20 +1023,6 @@ public void handleOnBackPressed() { } }; - private final OnBackInvokedCallback onBackInvokedCallback = - Build.VERSION.SDK_INT >= 33 - ? new OnBackInvokedCallback() { - // TODO(garyq): Remove SuppressWarnings annotation. This was added to workaround - // a google3 bug where the linter is not properly running against API 33, causing - // a failure here. See b/243609613 and https://github.com/flutter/flutter/issues/111295 - @SuppressWarnings("Override") - @Override - public void onBackInvoked() { - onBackPressed(); - } - } - : null; - public FlutterFragment() { // Ensure that we at least have an empty Bundle of arguments so that we don't // need to continually check for null arguments before grabbing one. @@ -1078,11 +1060,14 @@ public void onAttach(@NonNull Context context) { super.onAttach(context); delegate = delegateFactory.createDelegate(this); delegate.onAttach(context); - /* if (getArguments().getBoolean(ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, false)) { requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); + // By default, Android handles backs, and predictive back is enabled. This + // can be changed by calling setFrameworkHandlesBack. For example, the + // framework will call this automatically in a typical app when it has + // routes to pop. + onBackPressedCallback.setEnabled(false); } - */ context.registerComponentCallbacks(this); } @@ -1706,51 +1691,10 @@ public boolean popSystemNavigator() { @Override public void setFrameworkHandlesBack(boolean frameworkHandlesBack) { - Log.e( - "justin", - "setFrameworkHandlesBack in FlutterFragment! setEnabled: " + frameworkHandlesBack); - if (frameworkHandlesBack && !hasRegisteredBackCallback) { - registerOnBackInvokedCallback(); - } else if (!frameworkHandlesBack && hasRegisteredBackCallback) { - unregisterOnBackInvokedCallback(); - } - } - - /** - * Registers the callback with OnBackPressedDispatcher to capture back navigation gestures and - * pass them to the framework. - * - *

This replaces the deprecated onBackPressed method override in order to support API 33's - * predictive back navigation feature. - * - *

The callback must be unregistered in order to prevent unpredictable behavior once outside - * the Flutter app. - */ - @VisibleForTesting - public void registerOnBackInvokedCallback() { - if (Build.VERSION.SDK_INT >= 33) { - getActivity() - .getOnBackInvokedDispatcher() - .registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback); - hasRegisteredBackCallback = true; - } - } - - /** - * Unregisters the callback from OnBackPressedDispatcher. - * - *

This should be called when the activity is no longer in use to prevent unpredictable - * behavior such as being stuck and unable to press back. - */ - @VisibleForTesting - public void unregisterOnBackInvokedCallback() { - if (Build.VERSION.SDK_INT >= 33) { - getActivity() - .getOnBackInvokedDispatcher() - .unregisterOnBackInvokedCallback(onBackInvokedCallback); - hasRegisteredBackCallback = false; + if (getArguments().getBoolean(ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, false)) { + return; } + onBackPressedCallback.setEnabled(frameworkHandlesBack); } @VisibleForTesting diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index 6463959491e1b..5cb028d9ed7f9 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -521,6 +521,7 @@ protected FlutterFragment createFlutterFragment() { ? TransparencyMode.opaque : TransparencyMode.transparent; final boolean shouldDelayFirstAndroidViewDraw = renderMode == RenderMode.surface; + final boolean shouldAutomaticallyHandleOnBackPressed = Build.VERSION.SDK_INT >= 33; if (getCachedEngineId() != null) { Log.v( @@ -545,8 +546,7 @@ protected FlutterFragment createFlutterFragment() { .shouldAttachEngineToActivity(shouldAttachEngineToActivity()) .destroyEngineWithFragment(shouldDestroyEngineWithHost()) .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw) - // TODO(justinmc): Unsure whether this is relevant to predictive back. - // .shouldAutomaticallyHandleOnBackPressed(Build.VERSION.SDK_INT >= 33) + .shouldAutomaticallyHandleOnBackPressed(shouldAutomaticallyHandleOnBackPressed) .build(); } else { Log.v( @@ -583,6 +583,7 @@ protected FlutterFragment createFlutterFragment() { .transparencyMode(transparencyMode) .shouldAttachEngineToActivity(shouldAttachEngineToActivity()) .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw) + .shouldAutomaticallyHandleOnBackPressed(shouldAutomaticallyHandleOnBackPressed) .build(); } @@ -598,6 +599,7 @@ protected FlutterFragment createFlutterFragment() { .transparencyMode(transparencyMode) .shouldAttachEngineToActivity(shouldAttachEngineToActivity()) .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw) + .shouldAutomaticallyHandleOnBackPressed(shouldAutomaticallyHandleOnBackPressed) .build(); } }