Flutter Engine
The Flutter Engine
FlutterFragmentTest.java
Go to the documentation of this file.
1package io.flutter.embedding.android;
2
3import static org.junit.Assert.assertArrayEquals;
4import static org.junit.Assert.assertEquals;
5import static org.junit.Assert.assertFalse;
6import static org.junit.Assert.assertNotNull;
7import static org.junit.Assert.assertNull;
8import static org.junit.Assert.assertTrue;
9import static org.mockito.ArgumentMatchers.any;
10import static org.mockito.Mockito.doAnswer;
11import static org.mockito.Mockito.mock;
12import static org.mockito.Mockito.never;
13import static org.mockito.Mockito.spy;
14import static org.mockito.Mockito.times;
15import static org.mockito.Mockito.verify;
16import static org.mockito.Mockito.when;
17
18import android.content.Context;
19import androidx.activity.OnBackPressedCallback;
20import androidx.fragment.app.FragmentActivity;
21import androidx.test.core.app.ApplicationProvider;
22import androidx.test.ext.junit.runners.AndroidJUnit4;
23import io.flutter.embedding.engine.FlutterEngine;
24import io.flutter.embedding.engine.FlutterEngineCache;
25import io.flutter.embedding.engine.FlutterJNI;
26import io.flutter.embedding.engine.loader.FlutterLoader;
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.concurrent.atomic.AtomicBoolean;
30import org.junit.Test;
31import org.junit.runner.RunWith;
32import org.robolectric.Robolectric;
33import org.robolectric.annotation.Config;
34
35@Config(manifest = Config.NONE)
36@RunWith(AndroidJUnit4.class)
37public class FlutterFragmentTest {
38 private final Context ctx = ApplicationProvider.getApplicationContext();
40
43
45 this.delegate = delegate;
46 }
47
50 return delegate;
51 }
52 }
53
54 @Test
57 TestDelegateFactory delegateFactory =
59 fragment.setDelegateFactory(delegateFactory);
60
61 assertEquals("main", fragment.getDartEntrypointFunctionName());
62 assertNull(fragment.getDartEntrypointLibraryUri());
63 assertNull(fragment.getDartEntrypointArgs());
64 assertEquals("/", fragment.getInitialRoute());
65 assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray());
66 assertTrue(fragment.shouldAttachEngineToActivity());
67 assertFalse(fragment.shouldHandleDeeplinking());
68 assertNull(fragment.getCachedEngineId());
69 assertTrue(fragment.shouldDestroyEngineWithHost());
70 assertEquals(RenderMode.surface, fragment.getRenderMode());
71 assertEquals(TransparencyMode.transparent, fragment.getTransparencyMode());
72 assertFalse(fragment.shouldDelayFirstAndroidViewDraw());
73 }
74
75 @Test
77 FlutterFragment fragment =
79 .dartEntrypoint("custom_entrypoint")
80 .dartLibraryUri("package:foo/bar.dart")
81 .dartEntrypointArgs(new ArrayList<String>(Arrays.asList("foo", "bar")))
82 .initialRoute("/custom/route")
83 .shouldAttachEngineToActivity(false)
84 .handleDeeplinking(true)
85 .renderMode(RenderMode.texture)
86 .transparencyMode(TransparencyMode.opaque)
87 .build();
88 TestDelegateFactory delegateFactory =
90 fragment.setDelegateFactory(delegateFactory);
91
92 assertEquals("custom_entrypoint", fragment.getDartEntrypointFunctionName());
93 assertEquals("package:foo/bar.dart", fragment.getDartEntrypointLibraryUri());
94 assertEquals("/custom/route", fragment.getInitialRoute());
95 assertArrayEquals(new String[] {"foo", "bar"}, fragment.getDartEntrypointArgs().toArray());
96 assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray());
97 assertFalse(fragment.shouldAttachEngineToActivity());
98 assertTrue(fragment.shouldHandleDeeplinking());
99 assertNull(fragment.getCachedEngineId());
100 assertTrue(fragment.shouldDestroyEngineWithHost());
101 assertEquals(RenderMode.texture, fragment.getRenderMode());
102 assertEquals(TransparencyMode.opaque, fragment.getTransparencyMode());
103 }
104
105 @Test
107 FlutterFragment fragment =
108 FlutterFragment.withNewEngineInGroup("my_cached_engine_group")
109 .dartEntrypoint("custom_entrypoint")
110 .initialRoute("/custom/route")
111 .shouldAttachEngineToActivity(false)
112 .handleDeeplinking(true)
113 .renderMode(RenderMode.texture)
114 .transparencyMode(TransparencyMode.opaque)
115 .build();
116
117 TestDelegateFactory delegateFactory =
119
120 fragment.setDelegateFactory(delegateFactory);
121
122 assertEquals("my_cached_engine_group", fragment.getCachedEngineGroupId());
123 assertEquals("custom_entrypoint", fragment.getDartEntrypointFunctionName());
124 assertEquals("/custom/route", fragment.getInitialRoute());
125 assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray());
126 assertFalse(fragment.shouldAttachEngineToActivity());
127 assertTrue(fragment.shouldHandleDeeplinking());
128 assertNull(fragment.getCachedEngineId());
129 assertTrue(fragment.shouldDestroyEngineWithHost());
130 assertEquals(RenderMode.texture, fragment.getRenderMode());
131 assertEquals(TransparencyMode.opaque, fragment.getTransparencyMode());
132 }
133
134 @Test
136 FlutterFragment fragment =
137 FlutterFragment.withNewEngine().shouldDelayFirstAndroidViewDraw(true).build();
138
139 assertNotNull(fragment.shouldDelayFirstAndroidViewDraw());
140 }
141
142 @Test
144 FlutterFragment fragment = FlutterFragment.withCachedEngine("my_cached_engine").build();
145
146 assertTrue(fragment.shouldAttachEngineToActivity());
147 assertEquals("my_cached_engine", fragment.getCachedEngineId());
148 assertFalse(fragment.shouldDestroyEngineWithHost());
149 assertFalse(fragment.shouldDelayFirstAndroidViewDraw());
150 }
151
152 @Test
154 FlutterFragment fragment =
155 FlutterFragment.withCachedEngine("my_cached_engine")
156 .destroyEngineWithFragment(true)
157 .build();
158
159 assertTrue(fragment.shouldAttachEngineToActivity());
160 assertEquals("my_cached_engine", fragment.getCachedEngineId());
161 assertTrue(fragment.shouldDestroyEngineWithHost());
162 }
163
164 @Test
166 FlutterFragment fragment =
167 FlutterFragment.withCachedEngine("my_cached_engine")
168 .shouldDelayFirstAndroidViewDraw(true)
169 .build();
170
171 assertNotNull(fragment.shouldDelayFirstAndroidViewDraw());
172 }
173
174 @Test
178 TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
179 FlutterFragment fragment =
180 FlutterFragment.withCachedEngine("my_cached_engine")
181 .destroyEngineWithFragment(true)
182 .build();
183
184 isDelegateAttached = true;
185 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
186 doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
187
188 fragment.setDelegateFactory(delegateFactory);
189 fragment.onStart();
190 fragment.onResume();
191 fragment.onPostResume();
192
193 verify(mockDelegate, times(1)).onStart();
194 verify(mockDelegate, times(1)).onResume();
195 verify(mockDelegate, times(1)).onPostResume();
196
197 fragment.onPause();
198 fragment.detachFromFlutterEngine();
199 verify(mockDelegate, times(1)).onPause();
200 verify(mockDelegate, times(1)).onDestroyView();
201 verify(mockDelegate, times(1)).onDetach();
202
203 fragment.onStop();
204 verify(mockDelegate, never()).onStop();
205
206 fragment.onStart();
207 fragment.onResume();
208 fragment.onPostResume();
209 // No more events through to the delegate.
210 verify(mockDelegate, times(1)).onStart();
211 verify(mockDelegate, times(1)).onResume();
212 verify(mockDelegate, times(1)).onPostResume();
213
214 fragment.onDestroy();
215 // 1 time same as before.
216 verify(mockDelegate, times(1)).onDestroyView();
217 verify(mockDelegate, times(1)).onDetach();
218 }
219
220 @Test
224 isDelegateAttached = true;
225 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
226 doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
227 TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
228
229 FlutterFragment fragment =
230 FlutterFragment.withCachedEngine("my_cached_engine")
231 .destroyEngineWithFragment(true)
232 .build();
233
234 fragment.setDelegateFactory(delegateFactory);
235 fragment.onStart();
236 fragment.onResume();
237 fragment.onPostResume();
238 fragment.onPause();
239
240 assertTrue(mockDelegate.isAttached());
241 fragment.detachFromFlutterEngine();
242 verify(mockDelegate, times(1)).onDetach();
243 verify(mockDelegate, never()).release();
244 assertFalse(mockDelegate.isAttached());
245 }
246
247 @Test
251 isDelegateAttached = true;
252 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
253 doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
254 TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
255
256 FlutterFragment fragment =
257 spy(
258 FlutterFragment.withCachedEngine("my_cached_engine")
259 .destroyEngineWithFragment(true)
260 .build());
261 when(fragment.getContext()).thenReturn(mock(Context.class));
262
263 fragment.setDelegateFactory(delegateFactory);
264 fragment.onStart();
265 fragment.onResume();
266 fragment.onPostResume();
267 fragment.onPause();
268
269 assertTrue(mockDelegate.isAttached());
270 fragment.onDetach();
271 verify(mockDelegate, times(1)).onDetach();
272 verify(mockDelegate, times(1)).release();
273 assertFalse(mockDelegate.isAttached());
274 }
275
276 @Test
280 TestDelegateFactory delegateFactory = new TestDelegateFactory(delegate);
281 fragment.setDelegateFactory(delegateFactory);
282
283 assertEquals(fragment.getExclusiveAppComponent(), delegate);
284 }
285
286 @SuppressWarnings("deprecation")
287 private FragmentActivity getMockFragmentActivity() {
288 // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
289 return Robolectric.setupActivity(FragmentActivity.class);
290 }
291
292 @Test
294 // We need to mock FlutterJNI to avoid triggering native code.
295 FlutterJNI flutterJNI = mock(FlutterJNI.class);
296 when(flutterJNI.isAttached()).thenReturn(true);
297
298 FlutterEngine flutterEngine =
299 new FlutterEngine(ctx, new FlutterLoader(), flutterJNI, null, false);
300 FlutterEngineCache.getInstance().put("my_cached_engine", flutterEngine);
301
302 FlutterFragment fragment =
303 FlutterFragment.withCachedEngine("my_cached_engine")
304 // This enables the use of onBackPressedCallback, which is what
305 // sends backs to the framework if setFrameworkHandlesBack is true.
306 .shouldAutomaticallyHandleOnBackPressed(true)
307 .build();
308 FragmentActivity activity = getMockFragmentActivity();
309 activity
310 .getSupportFragmentManager()
311 .beginTransaction()
312 .add(android.R.id.content, fragment)
313 .commitNow();
314
317 isDelegateAttached = true;
318 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
319 doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
320 TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
321 fragment.setDelegateFactory(delegateFactory);
322
323 // Calling onBackPressed now will still be handled by Android (the default),
324 // until setFrameworkHandlesBack is set to true.
325 activity.onBackPressed();
326 verify(mockDelegate, times(0)).onBackPressed();
327
328 // Setting setFrameworkHandlesBack to true means the delegate will receive
329 // the back and Android won't handle it.
330 fragment.setFrameworkHandlesBack(true);
331 activity.getOnBackPressedDispatcher().onBackPressed();
332 verify(mockDelegate, times(1)).onBackPressed();
333 }
334
335 @SuppressWarnings("deprecation")
336 // Robolectric.setupActivity
337 // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
338 @Test
340 // We need to mock FlutterJNI to avoid triggering native code.
341 FlutterJNI flutterJNI = mock(FlutterJNI.class);
342 when(flutterJNI.isAttached()).thenReturn(true);
343
344 FlutterEngine flutterEngine =
345 new FlutterEngine(ctx, new FlutterLoader(), flutterJNI, null, false);
346 FlutterEngineCache.getInstance().put("my_cached_engine", flutterEngine);
347
348 FlutterFragment fragment =
349 FlutterFragment.withCachedEngine("my_cached_engine")
350 .shouldAutomaticallyHandleOnBackPressed(true)
351 .build();
352 FragmentActivity activity = getMockFragmentActivity();
353 activity
354 .getSupportFragmentManager()
355 .beginTransaction()
356 .add(android.R.id.content, fragment)
357 .commitNow();
358 final AtomicBoolean onBackPressedCalled = new AtomicBoolean(false);
359 OnBackPressedCallback callback =
360 new OnBackPressedCallback(true) {
361 @Override
362 public void handleOnBackPressed() {
363 onBackPressedCalled.set(true);
364 }
365 };
366 activity.getOnBackPressedDispatcher().addCallback(callback);
367
370 TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
371 fragment.setDelegateFactory(delegateFactory);
372
373 assertTrue(callback.isEnabled());
374
375 assertTrue(fragment.popSystemNavigator());
376
377 verify(mockDelegate, never()).onBackPressed();
378 assertTrue(onBackPressedCalled.get());
379 assertTrue(callback.isEnabled());
380
381 callback.setEnabled(false);
382 assertFalse(callback.isEnabled());
383 assertTrue(fragment.popSystemNavigator());
384
385 verify(mockDelegate, never()).onBackPressed();
386 assertFalse(callback.isEnabled());
387 }
388
389 @Test
393 isDelegateAttached = true;
394 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
395 doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
396 TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
397
398 Context spyCtx = spy(ctx);
399 // We need to mock FlutterJNI to avoid triggering native code.
400 FlutterJNI flutterJNI = mock(FlutterJNI.class);
401 when(flutterJNI.isAttached()).thenReturn(true);
402
403 FlutterEngine flutterEngine =
404 new FlutterEngine(spyCtx, new FlutterLoader(), flutterJNI, null, false);
405 FlutterEngineCache.getInstance().put("my_cached_engine", flutterEngine);
406
407 FlutterFragment fragment = spy(FlutterFragment.withCachedEngine("my_cached_engine").build());
408 when(fragment.getContext()).thenReturn(spyCtx);
409 fragment.setDelegateFactory(delegateFactory);
410
411 fragment.onAttach(spyCtx);
412 verify(spyCtx, times(1)).registerComponentCallbacks(any());
413 verify(spyCtx, never()).unregisterComponentCallbacks(any());
414
415 fragment.onDetach();
416 verify(spyCtx, times(1)).registerComponentCallbacks(any());
417 verify(spyCtx, times(1)).unregisterComponentCallbacks(any());
418 }
419}
static SkISize times(const SkISize &size, float factor)
FlutterActivityAndFragmentDelegate createDelegate(FlutterActivityAndFragmentDelegate.Host host)
void setFrameworkHandlesBack(boolean frameworkHandlesBack)
static NewEngineFragmentBuilder withNewEngine()
static NewEngineInGroupFragmentBuilder withNewEngineInGroup( @NonNull String engineGroupId)
static CachedEngineFragmentBuilder withCachedEngine(@NonNull String engineId)
ExclusiveAppComponent< Activity > getExclusiveAppComponent()
void setDelegateFactory( @NonNull FlutterActivityAndFragmentDelegate.DelegateFactory delegateFactory)
void put(@NonNull String engineId, @Nullable FlutterEngine engine)
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service host
Definition: switches.h:74
SIT bool any(const Vec< 1, T > &x)
Definition: SkVx.h:530