Skip to content

Commit 8e63563

Browse files
authored
Add registration calls for ComponentCallbacks2 (flutter#34206)
1 parent b372fb4 commit 8e63563

File tree

3 files changed

+92
-15
lines changed

3 files changed

+92
-15
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@
7171
private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins";
7272
private static final int FLUTTER_SPLASH_VIEW_FALLBACK_ID = 486947586;
7373

74+
/** Factory to obtain a FlutterActivityAndFragmentDelegate instance. */
75+
public interface DelegateFactory {
76+
FlutterActivityAndFragmentDelegate createDelegate(FlutterActivityAndFragmentDelegate.Host host);
77+
}
78+
7479
// The FlutterActivity or FlutterFragment that is delegating most of its calls
7580
// to this FlutterActivityAndFragmentDelegate.
7681
@NonNull private Host host;

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@
9494
* Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a {@code Fragment}.
9595
*/
9696
public class FlutterFragment extends Fragment
97-
implements FlutterActivityAndFragmentDelegate.Host, ComponentCallbacks2 {
97+
implements FlutterActivityAndFragmentDelegate.Host,
98+
ComponentCallbacks2,
99+
FlutterActivityAndFragmentDelegate.DelegateFactory {
98100
/**
99101
* The ID of the {@code FlutterView} created by this activity.
100102
*
@@ -732,6 +734,14 @@ public <T extends FlutterFragment> T build() {
732734
// implementation for details about why it exists.
733735
@VisibleForTesting @Nullable /* package */ FlutterActivityAndFragmentDelegate delegate;
734736

737+
@NonNull private FlutterActivityAndFragmentDelegate.DelegateFactory delegateFactory = this;
738+
739+
/** Default delegate factory that creates a simple FlutterActivityAndFragmentDelegate instance. */
740+
public FlutterActivityAndFragmentDelegate createDelegate(
741+
FlutterActivityAndFragmentDelegate.Host host) {
742+
return new FlutterActivityAndFragmentDelegate(host);
743+
}
744+
735745
private final OnBackPressedCallback onBackPressedCallback =
736746
new OnBackPressedCallback(true) {
737747
@Override
@@ -757,8 +767,10 @@ public FlutterFragment() {
757767
// TODO(mattcarroll): remove this when tests allow for it
758768
// (https://github.com/flutter/flutter/issues/43798)
759769
@VisibleForTesting
760-
/* package */ void setDelegate(@NonNull FlutterActivityAndFragmentDelegate delegate) {
761-
this.delegate = delegate;
770+
/* package */ void setDelegateFactory(
771+
@NonNull FlutterActivityAndFragmentDelegate.DelegateFactory delegateFactory) {
772+
this.delegateFactory = delegateFactory;
773+
delegate = delegateFactory.createDelegate(this);
762774
}
763775

764776
/**
@@ -773,11 +785,12 @@ public ExclusiveAppComponent<Activity> getExclusiveAppComponent() {
773785
@Override
774786
public void onAttach(@NonNull Context context) {
775787
super.onAttach(context);
776-
delegate = new FlutterActivityAndFragmentDelegate(this);
788+
delegate = delegateFactory.createDelegate(this);
777789
delegate.onAttach(context);
778790
if (getArguments().getBoolean(ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, false)) {
779791
requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
780792
}
793+
context.registerComponentCallbacks(this);
781794
}
782795

783796
@Override
@@ -873,6 +886,7 @@ public void detachFromFlutterEngine() {
873886

874887
@Override
875888
public void onDetach() {
889+
getContext().unregisterComponentCallbacks(this);
876890
super.onDetach();
877891
if (delegate != null) {
878892
delegate.onDetach();

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

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import static org.junit.Assert.assertNotNull;
77
import static org.junit.Assert.assertNull;
88
import static org.junit.Assert.assertTrue;
9+
import static org.mockito.ArgumentMatchers.any;
910
import static org.mockito.Mockito.doAnswer;
1011
import static org.mockito.Mockito.mock;
1112
import static org.mockito.Mockito.never;
13+
import static org.mockito.Mockito.spy;
1214
import static org.mockito.Mockito.times;
1315
import static org.mockito.Mockito.verify;
1416
import static org.mockito.Mockito.when;
@@ -36,10 +38,25 @@ public class FlutterFragmentTest {
3638
private final Context ctx = ApplicationProvider.getApplicationContext();
3739
boolean isDelegateAttached;
3840

41+
class TestDelegateFactory implements FlutterActivityAndFragmentDelegate.DelegateFactory {
42+
FlutterActivityAndFragmentDelegate delegate;
43+
44+
TestDelegateFactory(FlutterActivityAndFragmentDelegate delegate) {
45+
this.delegate = delegate;
46+
}
47+
48+
public FlutterActivityAndFragmentDelegate createDelegate(
49+
FlutterActivityAndFragmentDelegate.Host host) {
50+
return delegate;
51+
}
52+
}
53+
3954
@Test
4055
public void itCreatesDefaultFragmentWithExpectedDefaults() {
4156
FlutterFragment fragment = FlutterFragment.createDefault();
42-
fragment.setDelegate(new FlutterActivityAndFragmentDelegate(fragment));
57+
TestDelegateFactory delegateFactory =
58+
new TestDelegateFactory(new FlutterActivityAndFragmentDelegate(fragment));
59+
fragment.setDelegateFactory(delegateFactory);
4360

4461
assertEquals("main", fragment.getDartEntrypointFunctionName());
4562
assertNull(fragment.getDartEntrypointLibraryUri());
@@ -68,7 +85,9 @@ public void itCreatesNewEngineFragmentWithRequestedSettings() {
6885
.renderMode(RenderMode.texture)
6986
.transparencyMode(TransparencyMode.opaque)
7087
.build();
71-
fragment.setDelegate(new FlutterActivityAndFragmentDelegate(fragment));
88+
TestDelegateFactory delegateFactory =
89+
new TestDelegateFactory(new FlutterActivityAndFragmentDelegate(fragment));
90+
fragment.setDelegateFactory(delegateFactory);
7291

7392
assertEquals("custom_entrypoint", fragment.getDartEntrypointFunctionName());
7493
assertEquals("package:foo/bar.dart", fragment.getDartEntrypointLibraryUri());
@@ -127,6 +146,7 @@ public void itCreatesCachedEngineFragmentThatDelaysFirstDrawWhenRequested() {
127146
public void itCanBeDetachedFromTheEngineAndStopSendingFurtherEvents() {
128147
FlutterActivityAndFragmentDelegate mockDelegate =
129148
mock(FlutterActivityAndFragmentDelegate.class);
149+
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
130150
FlutterFragment fragment =
131151
FlutterFragment.withCachedEngine("my_cached_engine")
132152
.destroyEngineWithFragment(true)
@@ -136,7 +156,7 @@ public void itCanBeDetachedFromTheEngineAndStopSendingFurtherEvents() {
136156
when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
137157
doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
138158

139-
fragment.setDelegate(mockDelegate);
159+
fragment.setDelegateFactory(delegateFactory);
140160
fragment.onStart();
141161
fragment.onResume();
142162
fragment.onPostResume();
@@ -175,13 +195,14 @@ public void itDoesNotReleaseEnginewhenDetachFromFlutterEngine() {
175195
isDelegateAttached = true;
176196
when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
177197
doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
198+
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
178199

179200
FlutterFragment fragment =
180201
FlutterFragment.withCachedEngine("my_cached_engine")
181202
.destroyEngineWithFragment(true)
182203
.build();
183204

184-
fragment.setDelegate(mockDelegate);
205+
fragment.setDelegateFactory(delegateFactory);
185206
fragment.onStart();
186207
fragment.onResume();
187208
fragment.onPostResume();
@@ -201,13 +222,16 @@ public void itReleaseEngineWhenOnDetach() {
201222
isDelegateAttached = true;
202223
when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
203224
doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
225+
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
204226

205227
FlutterFragment fragment =
206-
FlutterFragment.withCachedEngine("my_cached_engine")
207-
.destroyEngineWithFragment(true)
208-
.build();
228+
spy(
229+
FlutterFragment.withCachedEngine("my_cached_engine")
230+
.destroyEngineWithFragment(true)
231+
.build());
232+
when(fragment.getContext()).thenReturn(mock(Context.class));
209233

210-
fragment.setDelegate(mockDelegate);
234+
fragment.setDelegateFactory(delegateFactory);
211235
fragment.onStart();
212236
fragment.onResume();
213237
fragment.onPostResume();
@@ -224,7 +248,8 @@ public void itReleaseEngineWhenOnDetach() {
224248
public void itReturnsExclusiveAppComponent() {
225249
FlutterFragment fragment = FlutterFragment.createDefault();
226250
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(fragment);
227-
fragment.setDelegate(delegate);
251+
TestDelegateFactory delegateFactory = new TestDelegateFactory(delegate);
252+
fragment.setDelegateFactory(delegateFactory);
228253

229254
assertEquals(fragment.getExclusiveAppComponent(), delegate);
230255
}
@@ -255,7 +280,8 @@ public void itDelegatesOnBackPressedAutomaticallyWhenEnabled() {
255280
isDelegateAttached = true;
256281
when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
257282
doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
258-
fragment.setDelegate(mockDelegate);
283+
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
284+
fragment.setDelegateFactory(delegateFactory);
259285

260286
activity.onBackPressed();
261287

@@ -294,11 +320,43 @@ public void handleOnBackPressed() {
294320

295321
FlutterActivityAndFragmentDelegate mockDelegate =
296322
mock(FlutterActivityAndFragmentDelegate.class);
297-
fragment.setDelegate(mockDelegate);
323+
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
324+
fragment.setDelegateFactory(delegateFactory);
298325

299326
assertTrue(fragment.popSystemNavigator());
300327

301328
verify(mockDelegate, never()).onBackPressed();
302329
assertTrue(onBackPressedCalled.get());
303330
}
331+
332+
@Test
333+
public void itRegistersComponentCallbacks() {
334+
FlutterActivityAndFragmentDelegate mockDelegate =
335+
mock(FlutterActivityAndFragmentDelegate.class);
336+
isDelegateAttached = true;
337+
when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
338+
doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
339+
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
340+
341+
Context spyCtx = spy(ctx);
342+
// We need to mock FlutterJNI to avoid triggering native code.
343+
FlutterJNI flutterJNI = mock(FlutterJNI.class);
344+
when(flutterJNI.isAttached()).thenReturn(true);
345+
346+
FlutterEngine flutterEngine =
347+
new FlutterEngine(spyCtx, new FlutterLoader(), flutterJNI, null, false);
348+
FlutterEngineCache.getInstance().put("my_cached_engine", flutterEngine);
349+
350+
FlutterFragment fragment = spy(FlutterFragment.withCachedEngine("my_cached_engine").build());
351+
when(fragment.getContext()).thenReturn(spyCtx);
352+
fragment.setDelegateFactory(delegateFactory);
353+
354+
fragment.onAttach(spyCtx);
355+
verify(spyCtx, times(1)).registerComponentCallbacks(any());
356+
verify(spyCtx, never()).unregisterComponentCallbacks(any());
357+
358+
fragment.onDetach();
359+
verify(spyCtx, times(1)).registerComponentCallbacks(any());
360+
verify(spyCtx, times(1)).unregisterComponentCallbacks(any());
361+
}
304362
}

0 commit comments

Comments
 (0)