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

Commit 88ebc37

Browse files
author
Emmanuel Garcia
authored
Remove android view from the Mutator stack (#19972)
1 parent 5df5cbb commit 88ebc37

File tree

2 files changed

+162
-31
lines changed

2 files changed

+162
-31
lines changed

shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,12 @@ public void disposeAndroidViewForPlatformView(int viewId) {
118118
if (platformViewRequests.get(viewId) != null) {
119119
platformViewRequests.remove(viewId);
120120
}
121-
if (platformViews.get(viewId) != null) {
122-
((FlutterView) flutterView).removeView(mutatorViews.get(viewId));
121+
122+
final View platformView = platformViews.get(viewId);
123+
if (platformView != null) {
124+
final FlutterMutatorView mutatorView = mutatorViews.get(viewId);
125+
mutatorView.removeView(platformView);
126+
((FlutterView) flutterView).removeView(mutatorView);
123127
platformViews.remove(viewId);
124128
mutatorViews.remove(viewId);
125129
}
@@ -679,13 +683,22 @@ void initializePlatformViewIfNeeded(int viewId) {
679683

680684
PlatformView platformView = factory.create(context, viewId, createParams);
681685
View view = platformView.getView();
686+
687+
if (view == null) {
688+
throw new IllegalStateException(
689+
"PlatformView#getView() returned null, but an Android view reference was expected.");
690+
}
691+
if (view.getParent() != null) {
692+
throw new IllegalStateException(
693+
"The Android view returned from PlatformView#getView() was already added to a parent view.");
694+
}
682695
platformViews.put(viewId, view);
683696

684697
FlutterMutatorView mutatorView =
685698
new FlutterMutatorView(
686699
context, context.getResources().getDisplayMetrics().density, androidTouchProcessor);
687700
mutatorViews.put(viewId, mutatorView);
688-
mutatorView.addView(platformView.getView());
701+
mutatorView.addView(view);
689702
((FlutterView) flutterView).addView(mutatorView);
690703
}
691704

shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java

Lines changed: 146 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
11
package io.flutter.plugin.platform;
22

33
import static io.flutter.embedding.engine.systemchannels.PlatformViewsChannel.PlatformViewTouch;
4-
import static org.junit.Assert.assertEquals;
5-
import static org.junit.Assert.assertNotEquals;
6-
import static org.junit.Assert.assertNotNull;
7-
import static org.junit.Assert.assertNull;
8-
import static org.mockito.Matchers.eq;
9-
import static org.mockito.Mockito.any;
10-
import static org.mockito.Mockito.mock;
11-
import static org.mockito.Mockito.never;
12-
import static org.mockito.Mockito.times;
13-
import static org.mockito.Mockito.verify;
14-
import static org.mockito.Mockito.when;
4+
import static org.junit.Assert.*;
5+
import static org.mockito.Matchers.*;
6+
import static org.mockito.Mockito.*;
157

168
import android.content.Context;
179
import android.content.res.AssetManager;
1810
import android.view.MotionEvent;
1911
import android.view.View;
12+
import android.view.ViewParent;
2013
import io.flutter.embedding.android.FlutterView;
2114
import io.flutter.embedding.android.MotionEventTracker;
2215
import io.flutter.embedding.engine.FlutterJNI;
2316
import io.flutter.embedding.engine.dart.DartExecutor;
17+
import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorView;
2418
import io.flutter.plugin.common.MethodCall;
2519
import io.flutter.plugin.common.StandardMethodCodec;
2620
import java.nio.ByteBuffer;
@@ -208,39 +202,126 @@ public void getPlatformViewById__hybridComposition() {
208202
int platformViewId = 0;
209203
assertNull(platformViewsController.getPlatformViewById(platformViewId));
210204

205+
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
206+
PlatformView platformView = mock(PlatformView.class);
207+
View androidView = mock(View.class);
208+
when(platformView.getView()).thenReturn(androidView);
209+
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
210+
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
211+
211212
FlutterJNI jni = new FlutterJNI();
212-
AssetManager assetManager = mock(AssetManager.class);
213-
Context context = RuntimeEnvironment.application.getApplicationContext();
213+
attach(jni, platformViewsController);
214214

215-
DartExecutor executor = new DartExecutor(jni, assetManager);
216-
executor.onAttachedToJNI();
217-
platformViewsController.attach(context, null, executor);
218-
platformViewsController.attachToView(mock(FlutterView.class));
215+
// Simulate create call from the framework.
216+
createPlatformView(jni, platformViewsController, platformViewId, "testType");
217+
218+
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
219+
220+
View resultAndroidView = platformViewsController.getPlatformViewById(platformViewId);
221+
assertNotNull(resultAndroidView);
222+
assertEquals(resultAndroidView, androidView);
223+
}
224+
225+
@Test
226+
public void initializePlatformViewIfNeeded__throwsIfViewIsNull() {
227+
PlatformViewsController platformViewsController = new PlatformViewsController();
228+
229+
int platformViewId = 0;
230+
assertNull(platformViewsController.getPlatformViewById(platformViewId));
231+
232+
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
233+
PlatformView platformView = mock(PlatformView.class);
234+
when(platformView.getView()).thenReturn(null);
235+
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
236+
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
237+
238+
FlutterJNI jni = new FlutterJNI();
239+
attach(jni, platformViewsController);
240+
241+
// Simulate create call from the framework.
242+
createPlatformView(jni, platformViewsController, platformViewId, "testType");
243+
244+
try {
245+
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
246+
} catch (Exception exception) {
247+
assertTrue(exception instanceof IllegalStateException);
248+
assertEquals(
249+
exception.getMessage(),
250+
"PlatformView#getView() returned null, but an Android view reference was expected.");
251+
return;
252+
}
253+
assertTrue(false);
254+
}
255+
256+
@Test
257+
public void initializePlatformViewIfNeeded__throwsIfViewHasParent() {
258+
PlatformViewsController platformViewsController = new PlatformViewsController();
259+
260+
int platformViewId = 0;
261+
assertNull(platformViewsController.getPlatformViewById(platformViewId));
219262

220263
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
221264
PlatformView platformView = mock(PlatformView.class);
222265
View androidView = mock(View.class);
266+
when(androidView.getParent()).thenReturn(mock(ViewParent.class));
223267
when(platformView.getView()).thenReturn(androidView);
224268
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
269+
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
270+
271+
FlutterJNI jni = new FlutterJNI();
272+
attach(jni, platformViewsController);
273+
274+
// Simulate create call from the framework.
275+
createPlatformView(jni, platformViewsController, platformViewId, "testType");
276+
try {
277+
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
278+
} catch (Exception exception) {
279+
assertTrue(exception instanceof IllegalStateException);
280+
assertEquals(
281+
exception.getMessage(),
282+
"The Android view returned from PlatformView#getView() was already added to a parent view.");
283+
return;
284+
}
285+
assertTrue(false);
286+
}
225287

288+
@Test
289+
public void disposeAndroidView__hybridComposition() {
290+
PlatformViewsController platformViewsController = new PlatformViewsController();
291+
292+
int platformViewId = 0;
293+
assertNull(platformViewsController.getPlatformViewById(platformViewId));
294+
295+
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
296+
PlatformView platformView = mock(PlatformView.class);
297+
298+
Context context = RuntimeEnvironment.application.getApplicationContext();
299+
View androidView = new View(context);
300+
301+
when(platformView.getView()).thenReturn(androidView);
302+
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
226303
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
227304

305+
FlutterJNI jni = new FlutterJNI();
306+
attach(jni, platformViewsController);
307+
228308
// Simulate create call from the framework.
229-
Map<String, Object> platformViewCreateArguments = new HashMap<>();
230-
platformViewCreateArguments.put("hybrid", true);
231-
platformViewCreateArguments.put("id", platformViewId);
232-
platformViewCreateArguments.put("viewType", "testType");
233-
platformViewCreateArguments.put("direction", 0);
234-
MethodCall platformCreateMethodCall = new MethodCall("create", platformViewCreateArguments);
309+
createPlatformView(jni, platformViewsController, platformViewId, "testType");
310+
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
235311

236-
jni.handlePlatformMessage(
237-
"flutter/platform_views", encodeMethodCall(platformCreateMethodCall), /*replyId=*/ 0);
312+
assertNotNull(androidView.getParent());
313+
assertTrue(androidView.getParent() instanceof FlutterMutatorView);
238314

315+
// Simulate dispose call from the framework.
316+
disposePlatformView(jni, platformViewsController, platformViewId);
317+
assertNull(androidView.getParent());
318+
319+
// Simulate create call from the framework.
320+
createPlatformView(jni, platformViewsController, platformViewId, "testType");
239321
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
240322

241-
View resultAndroidView = platformViewsController.getPlatformViewById(platformViewId);
242-
assertNotNull(resultAndroidView);
243-
assertEquals(resultAndroidView, androidView);
323+
assertNotNull(androidView.getParent());
324+
assertTrue(androidView.getParent() instanceof FlutterMutatorView);
244325
}
245326

246327
private static byte[] encodeMethodCall(MethodCall call) {
@@ -250,4 +331,41 @@ private static byte[] encodeMethodCall(MethodCall call) {
250331
buffer.get(dest);
251332
return dest;
252333
}
334+
335+
private static void createPlatformView(
336+
FlutterJNI jni,
337+
PlatformViewsController platformViewsController,
338+
int platformViewId,
339+
String viewType) {
340+
Map<String, Object> platformViewCreateArguments = new HashMap<>();
341+
platformViewCreateArguments.put("hybrid", true);
342+
platformViewCreateArguments.put("id", platformViewId);
343+
platformViewCreateArguments.put("viewType", viewType);
344+
platformViewCreateArguments.put("direction", 0);
345+
MethodCall platformCreateMethodCall = new MethodCall("create", platformViewCreateArguments);
346+
347+
jni.handlePlatformMessage(
348+
"flutter/platform_views", encodeMethodCall(platformCreateMethodCall), /*replyId=*/ 0);
349+
}
350+
351+
private static void disposePlatformView(
352+
FlutterJNI jni, PlatformViewsController platformViewsController, int platformViewId) {
353+
Map<String, Object> platformViewDisposeArguments = new HashMap<>();
354+
platformViewDisposeArguments.put("hybrid", true);
355+
platformViewDisposeArguments.put("id", platformViewId);
356+
MethodCall platformDisposeMethodCall = new MethodCall("dispose", platformViewDisposeArguments);
357+
358+
jni.handlePlatformMessage(
359+
"flutter/platform_views", encodeMethodCall(platformDisposeMethodCall), /*replyId=*/ 0);
360+
}
361+
362+
private void attach(FlutterJNI jni, PlatformViewsController platformViewsController) {
363+
DartExecutor executor = new DartExecutor(jni, mock(AssetManager.class));
364+
executor.onAttachedToJNI();
365+
366+
Context context = RuntimeEnvironment.application.getApplicationContext();
367+
platformViewsController.attach(context, null, executor);
368+
369+
platformViewsController.attachToView(mock(FlutterView.class));
370+
}
253371
}

0 commit comments

Comments
 (0)