DEV Community

kanta13jp1
kanta13jp1

Posted on

Flutter PlatformView Guide — Embedding Native UI in Flutter

Flutter PlatformView Guide — Embedding Native UI in Flutter

When you need Google Maps, WebView, or a camera preview in Flutter, PlatformView is the answer. Here's how it works and what to watch out for.

Two Types of PlatformView

Type Platform Use case
AndroidView Android Native Android widgets
UiKitView iOS Native UIKit views
HtmlElementView Web iframe / DOM elements

Android: Register a PlatformViewFactory

// 1. Implement PlatformView
class NativeMapPlatformView(
  context: Context,
  private val messenger: BinaryMessenger,
  params: Map<*, *>?
) : PlatformView {
  private val mapView = MapView(context).apply {
    val zoom = (params?.get("initialZoom") as? Number)?.toFloat() ?: 10f
    setZoomLevel(zoom)
  }
  override fun getView(): View = mapView
  override fun dispose() { mapView.onDestroy() }
}

// 2. Register factory in MainActivity
class MainActivity : FlutterActivity() {
  override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    flutterEngine.platformViewsController.registry
      .registerViewFactory(
        "plugins.example.com/native-map",
        object : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
          override fun create(context: Context, id: Int, args: Any?): PlatformView =
            NativeMapPlatformView(context, flutterEngine.dartExecutor.binaryMessenger, args as? Map<*, *>)
        }
      )
  }
}
Enter fullscreen mode Exit fullscreen mode

Dart: Use AndroidView / UiKitView

class NativeMapWidget extends StatelessWidget {
  const NativeMapWidget({super.key});

  @override
  Widget build(BuildContext context) {
    const viewType = 'plugins.example.com/native-map';
    const params = {'initialZoom': 12};

    return switch (defaultTargetPlatform) {
      TargetPlatform.android => const AndroidView(
          viewType: viewType,
          layoutDirection: TextDirection.ltr,
          creationParams: params,
          creationParamsCodec: StandardMessageCodec(),
        ),
      TargetPlatform.iOS => const UiKitView(
          viewType: viewType,
          layoutDirection: TextDirection.ltr,
          creationParams: params,
          creationParamsCodec: StandardMessageCodec(),
        ),
      _ => const Placeholder(),
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

Flutter Web: HtmlElementView

import 'dart:ui_web' as ui;
import 'dart:html' as html;

void registerIframeFactory() {
  ui.platformViewRegistry.registerViewFactory(
    'embeddedIframe',
    (int viewId) => html.IFrameElement()
      ..src = 'https://example.com/embed'
      ..style.border = 'none'
      ..style.width = '100%'
      ..style.height = '100%',
  );
}

// In your widget:
Widget build(BuildContext context) {
  registerIframeFactory(); // idempotent
  return const SizedBox(
    height: 400,
    child: HtmlElementView(viewType: 'embeddedIframe'),
  );
}
Enter fullscreen mode Exit fullscreen mode

Hybrid Composition vs Virtual Display

// Prefer Hybrid Composition on Android 10+
// It renders native views in their actual position (better performance)
AndroidView(viewType: viewType) // defaults to Hybrid Composition

// Virtual Display (legacy, more compatible but slower)
PlatformViewLink(
  viewType: viewType,
  surfaceFactory: (context, controller) => AndroidViewSurface(
    controller: controller as AndroidViewController,
    gestureRecognizers: const {},
    hitTestBehavior: PlatformViewHitTestBehavior.opaque,
  ),
  onCreatePlatformView: (params) =>
    PlatformViewsService.initExpensiveAndroidView(
      id: params.id,
      viewType: viewType,
      layoutDirection: TextDirection.ltr,
    )..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
     ..create(),
)
Enter fullscreen mode Exit fullscreen mode

Performance Rules

1. Wrap in RepaintBoundary to isolate GPU layers
2. Avoid animating PlatformViews (heavy texture compositing)
3. One PlatformView per screen max
4. Prefer existing packages (google_maps_flutter, webview_flutter)
   over manual PlatformView when available
Enter fullscreen mode Exit fullscreen mode

When NOT to Use PlatformView

If you can implement the UI in pure Flutter, do it. PlatformView adds:

  • Build complexity (native code per platform)
  • Texture compositing overhead
  • Gesture conflict risks
  • Accessibility gaps (native/Flutter boundaries)

After switching to PlatformView for our map component, native map responsiveness improved 3× over our custom Flutter canvas implementation.


Have you embedded native UI in Flutter? Share your approach in the comments!

Top comments (0)