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

Create PlatformView instance right after method channel call from Dart #20500

Merged
merged 5 commits into from
Aug 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
// it is associated with(e.g if a platform view creates other views in the same virtual display.
private final HashMap<Context, View> contextToPlatformView;

private final SparseArray<PlatformViewsChannel.PlatformViewCreationRequest> platformViewRequests;
private final SparseArray<View> platformViews;
private final SparseArray<FlutterMutatorView> mutatorViews;

Expand Down Expand Up @@ -107,18 +106,45 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
@Override
public void createAndroidViewForPlatformView(
@NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
// API level 19 is required for android.graphics.ImageReader.
// API level 19 is required for `android.graphics.ImageReader`.
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT);
platformViewRequests.put(request.viewId, request);

if (!validateDirection(request.direction)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the app developer perspective, this is an invalid state. It's also caught by https://github.com/flutter/engine/blob/master/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java#L107 and propagated as an standard method channel result error.

throw new IllegalStateException(
"Trying to create a view with unknown direction value: "
+ request.direction
+ "(view id: "
+ request.viewId
+ ")");
}

final PlatformViewFactory factory = registry.getFactory(request.viewType);
if (factory == null) {
throw new IllegalStateException(
"Trying to create a platform view of unregistered type: " + request.viewType);
}

Object createParams = null;
if (request.params != null) {
createParams = factory.getCreateArgsCodec().decodeMessage(request.params);
}

final PlatformView platformView = factory.create(context, request.viewId, createParams);
final View view = platformView.getView();
if (view == null) {
throw new IllegalStateException(
"PlatformView#getView() returned null, but an Android view reference was expected.");
}
if (view.getParent() != null) {
throw new IllegalStateException(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who's responsible for handling all these exceptions? Will they propagate all the way up and crash the app? If so, is that the desired behavior, or should they translate to Dart "soft" exceptions? Can they ever happen in the normal operation of an app, or do they signal developer error?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be a Future error in https://github.com/flutter/flutter/blob/f512e86c2a210d8402e43a3352c1b2fcf4308a43/packages/flutter/lib/src/services/platform_views.dart#L749, which will make the app crash with this error message.

It's something that should be addressed in the platform code by the developer before the API can be used in Dart.

"The Android view returned from PlatformView#getView() was already added to a parent view.");
}
platformViews.put(request.viewId, view);
}

@Override
public void disposeAndroidViewForPlatformView(int viewId) {
// Hybrid view.
if (platformViewRequests.get(viewId) != null) {
platformViewRequests.remove(viewId);
}

final View platformView = platformViews.get(viewId);
if (platformView != null) {
final FlutterMutatorView mutatorView = mutatorViews.get(viewId);
Expand Down Expand Up @@ -378,7 +404,6 @@ public PlatformViewsController() {
currentFrameUsedOverlayLayerIds = new HashSet<>();
currentFrameUsedPlatformViewIds = new HashSet<>();

platformViewRequests = new SparseArray<>();
platformViews = new SparseArray<>();
mutatorViews = new SparseArray<>();

Expand Down Expand Up @@ -651,50 +676,15 @@ private void initializeRootImageViewIfNeeded() {

@VisibleForTesting
void initializePlatformViewIfNeeded(int viewId) {
if (platformViews.get(viewId) != null) {
return;
}

PlatformViewsChannel.PlatformViewCreationRequest request = platformViewRequests.get(viewId);
if (request == null) {
throw new IllegalStateException(
"Platform view hasn't been initialized from the platform view channel.");
}

if (!validateDirection(request.direction)) {
throw new IllegalStateException(
"Trying to create a view with unknown direction value: "
+ request.direction
+ "(view id: "
+ viewId
+ ")");
}

PlatformViewFactory factory = registry.getFactory(request.viewType);
if (factory == null) {
throw new IllegalStateException(
"Trying to create a platform view of unregistered type: " + request.viewType);
}

Object createParams = null;
if (request.params != null) {
createParams = factory.getCreateArgsCodec().decodeMessage(request.params);
}

PlatformView platformView = factory.create(context, viewId, createParams);
View view = platformView.getView();

final View view = platformViews.get(viewId);
if (view == null) {
throw new IllegalStateException(
"PlatformView#getView() returned null, but an Android view reference was expected.");
"Platform view hasn't been initialized from the platform view channel.");
}
if (view.getParent() != null) {
throw new IllegalStateException(
"The Android view returned from PlatformView#getView() was already added to a parent view.");
if (mutatorViews.get(viewId) != null) {
return;
}
platformViews.put(viewId, view);

FlutterMutatorView mutatorView =
final FlutterMutatorView mutatorView =
new FlutterMutatorView(
context, context.getResources().getDisplayMetrics().density, androidTouchProcessor);
mutatorViews.put(viewId, mutatorView);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import android.content.Context;
import android.content.res.AssetManager;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
Expand All @@ -28,7 +29,9 @@
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.common.FlutterException;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.common.StandardMethodCodec;
import io.flutter.plugin.localization.LocalizationPlugin;
import java.nio.ByteBuffer;
Expand Down Expand Up @@ -242,7 +245,29 @@ public void getPlatformViewById__hybridComposition() {

@Test
@Config(shadows = {ShadowFlutterJNI.class})
public void initializePlatformViewIfNeeded__throwsIfViewIsNull() {
public void createPlatformViewMessage__initializesAndroidView() {
PlatformViewsController platformViewsController = new PlatformViewsController();

int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));

PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
PlatformView platformView = mock(PlatformView.class);
when(platformView.getView()).thenReturn(mock(View.class));
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);

FlutterJNI jni = new FlutterJNI();
attach(jni, platformViewsController);

// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType");
verify(platformView, times(1)).getView();
}

@Test
@Config(shadows = {ShadowFlutterJNI.class})
public void createPlatformViewMessage__throwsIfViewIsNull() {
PlatformViewsController platformViewsController = new PlatformViewsController();

int platformViewId = 0;
Expand All @@ -259,22 +284,28 @@ public void initializePlatformViewIfNeeded__throwsIfViewIsNull() {

// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType");
assertEquals(ShadowFlutterJNI.getResponses().size(), 1);

final ByteBuffer responseBuffer = ShadowFlutterJNI.getResponses().get(0);
responseBuffer.rewind();

StandardMethodCodec methodCodec = new StandardMethodCodec(new StandardMessageCodec());
try {
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
} catch (Exception exception) {
assertTrue(exception instanceof IllegalStateException);
assertEquals(
exception.getMessage(),
"PlatformView#getView() returned null, but an Android view reference was expected.");
methodCodec.decodeEnvelope(responseBuffer);
} catch (FlutterException exception) {
assertTrue(
exception
.getMessage()
.contains(
"PlatformView#getView() returned null, but an Android view reference was expected."));
return;
}
assertTrue(false);
assertFalse(true);
}

@Test
@Config(shadows = {ShadowFlutterJNI.class})
public void initializePlatformViewIfNeeded__throwsIfViewHasParent() {
public void createPlatformViewMessage__throwsIfViewHasParent() {
PlatformViewsController platformViewsController = new PlatformViewsController();

int platformViewId = 0;
Expand All @@ -293,16 +324,23 @@ public void initializePlatformViewIfNeeded__throwsIfViewHasParent() {

// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType");
assertEquals(ShadowFlutterJNI.getResponses().size(), 1);

final ByteBuffer responseBuffer = ShadowFlutterJNI.getResponses().get(0);
responseBuffer.rewind();

StandardMethodCodec methodCodec = new StandardMethodCodec(new StandardMessageCodec());
try {
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
} catch (Exception exception) {
assertTrue(exception instanceof IllegalStateException);
assertEquals(
exception.getMessage(),
"The Android view returned from PlatformView#getView() was already added to a parent view.");
methodCodec.decodeEnvelope(responseBuffer);
} catch (FlutterException exception) {
assertTrue(
exception
.getMessage()
.contains(
"The Android view returned from PlatformView#getView() was already added to a parent view."));
return;
}
assertTrue(false);
assertFalse(true);
}

@Test
Expand Down Expand Up @@ -481,6 +519,7 @@ public FlutterImageView createImageView() {

@Implements(FlutterJNI.class)
public static class ShadowFlutterJNI {
private static SparseArray<ByteBuffer> replies = new SparseArray<>();

public ShadowFlutterJNI() {}

Expand Down Expand Up @@ -527,7 +566,13 @@ public void setViewportMetrics(

@Implementation
public void invokePlatformMessageResponseCallback(
int responseId, ByteBuffer message, int position) {}
int responseId, ByteBuffer message, int position) {
replies.put(responseId, message);
}

public static SparseArray<ByteBuffer> getResponses() {
return replies;
}
}

@Implements(SurfaceView.class)
Expand Down