Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1e6864c

Browse files
authored
[Android] Save back handling state in Activity/Fragment bundle (#56715)
Fixes flutter/flutter#159158, and fixes b/355556397 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 74e9c6f commit 1e6864c

File tree

6 files changed

+146
-2
lines changed

6 files changed

+146
-2
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterActivity.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ public class FlutterActivity extends Activity
212212
implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner {
213213
private static final String TAG = "FlutterActivity";
214214

215-
private boolean hasRegisteredBackCallback = false;
215+
@VisibleForTesting boolean hasRegisteredBackCallback = false;
216216

217217
/**
218218
* The ID of the {@code FlutterView} created by this activity.
@@ -634,6 +634,13 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
634634

635635
super.onCreate(savedInstanceState);
636636

637+
if (savedInstanceState != null) {
638+
boolean frameworkHandlesBack =
639+
savedInstanceState.getBoolean(
640+
FlutterActivityAndFragmentDelegate.ON_BACK_CALLBACK_ENABLED_KEY);
641+
setFrameworkHandlesBack(frameworkHandlesBack);
642+
}
643+
637644
delegate = new FlutterActivityAndFragmentDelegate(this);
638645
delegate.onAttach(this);
639646
delegate.onRestoreInstanceState(savedInstanceState);
@@ -1477,6 +1484,11 @@ public boolean attachToEngineAutomatically() {
14771484
return true;
14781485
}
14791486

1487+
@Override
1488+
public boolean getBackCallbackState() {
1489+
return hasRegisteredBackCallback;
1490+
}
1491+
14801492
@Override
14811493
public boolean popSystemNavigator() {
14821494
// Hook for subclass. No-op if returns false.

shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
private static final String TAG = "FlutterActivityAndFragmentDelegate";
7777
private static final String FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework";
7878
private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins";
79+
static final String ON_BACK_CALLBACK_ENABLED_KEY = "enableOnBackInvokedCallbackState";
7980
private static final int FLUTTER_SPLASH_VIEW_FALLBACK_ID = 486947586;
8081

8182
/** Factory to obtain a FlutterActivityAndFragmentDelegate instance. */
@@ -691,6 +692,12 @@ void onSaveInstanceState(@Nullable Bundle bundle) {
691692
flutterEngine.getActivityControlSurface().onSaveInstanceState(plugins);
692693
bundle.putBundle(PLUGINS_RESTORATION_BUNDLE_KEY, plugins);
693694
}
695+
696+
// If using a cached engine, we need to save whether the framework or the system should handle
697+
// backs.
698+
if (host.getCachedEngineId() != null && !host.shouldDestroyEngineWithHost()) {
699+
bundle.putBoolean(ON_BACK_CALLBACK_ENABLED_KEY, host.getBackCallbackState());
700+
}
694701
}
695702

696703
@Override
@@ -1297,5 +1304,7 @@ PlatformPlugin providePlatformPlugin(
12971304
* <p>Defaults to {@code true}.
12981305
*/
12991306
boolean attachToEngineAutomatically();
1307+
1308+
boolean getBackCallbackState();
13001309
}
13011310
}

shell/platform/android/io/flutter/embedding/android/FlutterFragment.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,8 @@ public FlutterActivityAndFragmentDelegate createDelegate(
10091009
return new FlutterActivityAndFragmentDelegate(host);
10101010
}
10111011

1012-
private final OnBackPressedCallback onBackPressedCallback =
1012+
@VisibleForTesting
1013+
final OnBackPressedCallback onBackPressedCallback =
10131014
new OnBackPressedCallback(true) {
10141015
@Override
10151016
public void handleOnBackPressed() {
@@ -1071,6 +1072,12 @@ public void onAttach(@NonNull Context context) {
10711072
@Override
10721073
public void onCreate(@Nullable Bundle savedInstanceState) {
10731074
super.onCreate(savedInstanceState);
1075+
if (savedInstanceState != null) {
1076+
boolean frameworkHandlesBack =
1077+
savedInstanceState.getBoolean(
1078+
FlutterActivityAndFragmentDelegate.ON_BACK_CALLBACK_ENABLED_KEY);
1079+
onBackPressedCallback.setEnabled(frameworkHandlesBack);
1080+
}
10741081
delegate.onRestoreInstanceState(savedInstanceState);
10751082
}
10761083

@@ -1655,6 +1662,11 @@ public boolean attachToEngineAutomatically() {
16551662
return true;
16561663
}
16571664

1665+
@Override
1666+
public boolean getBackCallbackState() {
1667+
return onBackPressedCallback.isEnabled();
1668+
}
1669+
16581670
/**
16591671
* {@inheritDoc}
16601672
*

shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package io.flutter.embedding.android;
66

77
import static io.flutter.Build.API_LEVELS;
8+
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID;
89
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY;
910
import static org.junit.Assert.assertArrayEquals;
1011
import static org.junit.Assert.assertEquals;
@@ -34,6 +35,7 @@
3435
import androidx.annotation.RequiresApi;
3536
import androidx.lifecycle.DefaultLifecycleObserver;
3637
import androidx.lifecycle.LifecycleOwner;
38+
import androidx.test.core.app.ActivityScenario;
3739
import androidx.test.core.app.ApplicationProvider;
3840
import androidx.test.ext.junit.runners.AndroidJUnit4;
3941
import io.flutter.FlutterInjector;
@@ -94,6 +96,48 @@ public void flutterViewHasId() {
9496
assertTrue(activity.findViewById(FlutterActivity.FLUTTER_VIEW_ID) instanceof FlutterView);
9597
}
9698

99+
@Test
100+
@Config(minSdk = API_LEVELS.API_34)
101+
@TargetApi(API_LEVELS.API_34)
102+
public void whenUsingCachedEngine_predictiveBackStateIsSaved() {
103+
FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
104+
FlutterJNI mockFlutterJni = mock(FlutterJNI.class);
105+
when(mockFlutterJni.isAttached()).thenReturn(true);
106+
FlutterEngine cachedEngine = new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni);
107+
FlutterEngineCache.getInstance().put("my_cached_engine", cachedEngine);
108+
109+
ActivityScenario<FlutterActivity> flutterActivityScenario =
110+
ActivityScenario.launch(FlutterActivity.class);
111+
112+
// Set to framework handling and then recreate the activity and check the state is preserved.
113+
flutterActivityScenario.onActivity(activity -> activity.setFrameworkHandlesBack(true));
114+
flutterActivityScenario.onActivity(
115+
activity -> activity.getIntent().putExtra(EXTRA_CACHED_ENGINE_ID, "my_cached_engine"));
116+
117+
flutterActivityScenario.recreate();
118+
flutterActivityScenario.onActivity(activity -> assertTrue(activity.hasRegisteredBackCallback));
119+
120+
// Clean up.
121+
flutterActivityScenario.close();
122+
}
123+
124+
@Test
125+
@Config(minSdk = API_LEVELS.API_34)
126+
@TargetApi(API_LEVELS.API_34)
127+
public void whenNotUsingCachedEngine_predictiveBackStateIsNotSaved() {
128+
ActivityScenario<FlutterActivity> flutterActivityScenario =
129+
ActivityScenario.launch(FlutterActivity.class);
130+
131+
// Set to framework handling and then recreate the activity and check the state is preserved.
132+
flutterActivityScenario.onActivity(activity -> activity.setFrameworkHandlesBack(true));
133+
134+
flutterActivityScenario.recreate();
135+
flutterActivityScenario.onActivity(activity -> assertFalse(activity.hasRegisteredBackCallback));
136+
137+
// Clean up.
138+
flutterActivityScenario.close();
139+
}
140+
97141
// TODO(garyq): Robolectric does not yet support android api 33 yet. Switch to a robolectric
98142
// test that directly exercises the OnBackInvoked APIs when API 33 is supported.
99143
@Test

shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,11 @@ public boolean attachToEngineAutomatically() {
401401
return true;
402402
}
403403

404+
@Override
405+
public boolean getBackCallbackState() {
406+
return false;
407+
}
408+
404409
@Override
405410
public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {}
406411

shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package io.flutter.embedding.android;
66

77
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY;
8+
import static io.flutter.embedding.android.FlutterFragment.ARG_CACHED_ENGINE_ID;
9+
import static io.flutter.embedding.android.FlutterFragment.ARG_DESTROY_ENGINE_WITH_FRAGMENT;
810
import static org.junit.Assert.assertEquals;
911
import static org.junit.Assert.assertFalse;
1012
import static org.junit.Assert.assertNotNull;
@@ -13,6 +15,7 @@
1315
import static org.mockito.Mockito.spy;
1416
import static org.mockito.Mockito.when;
1517

18+
import android.annotation.TargetApi;
1619
import android.content.Context;
1720
import android.content.Intent;
1821
import android.content.pm.PackageManager;
@@ -25,9 +28,11 @@
2528
import androidx.test.core.app.ActivityScenario;
2629
import androidx.test.core.app.ApplicationProvider;
2730
import androidx.test.ext.junit.runners.AndroidJUnit4;
31+
import io.flutter.Build;
2832
import io.flutter.FlutterInjector;
2933
import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
3034
import io.flutter.embedding.engine.FlutterEngine;
35+
import io.flutter.embedding.engine.FlutterEngineCache;
3136
import io.flutter.embedding.engine.FlutterJNI;
3237
import io.flutter.embedding.engine.loader.FlutterLoader;
3338
import io.flutter.plugins.GeneratedPluginRegistrant;
@@ -254,6 +259,63 @@ public void itHandlesNewFragmentRecreationDuringRestoreWhenActivityIsRecreated()
254259
assertEquals(0, activity.numberOfEnginesCreated);
255260
}
256261

262+
@Test
263+
@Config(minSdk = Build.API_LEVELS.API_34)
264+
@TargetApi(Build.API_LEVELS.API_34)
265+
public void whenUsingCachedEngine_predictiveBackStateIsSaved() {
266+
FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
267+
FlutterJNI mockFlutterJni = mock(FlutterJNI.class);
268+
when(mockFlutterJni.isAttached()).thenReturn(true);
269+
FlutterEngine cachedEngine = new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni);
270+
FlutterEngineCache.getInstance().put("my_cached_engine", cachedEngine);
271+
272+
ActivityScenario<FlutterFragmentActivity> flutterFragmentActivityActivityScenario =
273+
ActivityScenario.launch(FlutterFragmentActivity.class);
274+
275+
// Set to framework handling and then recreate the activity and check the state is preserved.
276+
flutterFragmentActivityActivityScenario.onActivity(
277+
activity -> {
278+
FlutterFragment flutterFragment = activity.retrieveExistingFlutterFragmentIfPossible();
279+
flutterFragment.setFrameworkHandlesBack(true);
280+
Bundle bundle = flutterFragment.getArguments();
281+
bundle.putString(ARG_CACHED_ENGINE_ID, "my_cached_engine");
282+
bundle.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, false);
283+
FlutterEngineCache.getInstance().put("my_cached_engine", cachedEngine);
284+
flutterFragment.setArguments(bundle);
285+
});
286+
287+
flutterFragmentActivityActivityScenario.recreate();
288+
289+
flutterFragmentActivityActivityScenario.onActivity(
290+
activity -> {
291+
assertTrue(
292+
activity
293+
.retrieveExistingFlutterFragmentIfPossible()
294+
.onBackPressedCallback
295+
.isEnabled());
296+
});
297+
298+
// Clean up.
299+
flutterFragmentActivityActivityScenario.close();
300+
}
301+
302+
@Test
303+
@Config(minSdk = Build.API_LEVELS.API_34)
304+
@TargetApi(Build.API_LEVELS.API_34)
305+
public void whenNotUsingCachedEngine_predictiveBackStateIsNotSaved() {
306+
ActivityScenario<FlutterActivity> flutterActivityScenario =
307+
ActivityScenario.launch(FlutterActivity.class);
308+
309+
// Set to framework handling and then recreate the activity and check the state is preserved.
310+
flutterActivityScenario.onActivity(activity -> activity.setFrameworkHandlesBack(true));
311+
312+
flutterActivityScenario.recreate();
313+
flutterActivityScenario.onActivity(activity -> assertFalse(activity.hasRegisteredBackCallback));
314+
315+
// Clean up.
316+
flutterActivityScenario.close();
317+
}
318+
257319
static class FlutterFragmentActivityWithProvidedEngine extends FlutterFragmentActivity {
258320
int numberOfEnginesCreated = 0;
259321

0 commit comments

Comments
 (0)