top of page

Integrating PlatformView into Flutter (Android)

  • Writer: Pavel Kalinin
    Pavel Kalinin
  • Aug 28
  • 10 min read

Updated: Sep 22

Flutter is a powerful framework for building cross-platform applications. It provides tools to create beautiful and high-performance user interfaces. While Flutter provides a rich ecosystem of packages, ready-made solutions aren’t always sufficient, especially when low-level optimization or custom logic is needed.

In these situations, two key mechanisms come to the rescue: PlatformView and SurfaceTexture. PlatformView allows you to embed native UI components directly into the Flutter widget tree, while SurfaceTexture lets you use native surfaces to output video or graphics to a Flutter texture.


cover image

This article provides a detailed guide on integrating PlatformView into Flutter on Android by embedding an Android view component. Using the camera as an example, we’ll compare two approaches to help you choose the one that best fits your project.


Choosing the Right Tools


Before diving into the code, it's important to understand when to use each tool:

  • PlatformView: Best for embedding interactive native widgets that should respond to gestures and events just like any other Flutter widget. Examples include native maps (Google Maps, Apple Maps) or complex custom UI components.

    PlatformView creates a "real" native widget that is then rendered either on top of or beneath Flutter widgets, which can have its own quirks, for example, with overlaying other widgets.

  • SurfaceTexture (or Texture in Flutter): Ideal for displaying graphics or a video stream where direct interaction with the native UI isn't required. Flutter provides a Texture widget that displays a stream of data from a native "surface" (SurfaceTexture on Android). This approach is often used for camera or video player plugins because it offers high performance and allows you to easily overlay any Flutter widgets on top of the video stream.



Details of PlatformView Implementation on Android: Virtual Display, Hybrid Composition, and Texture Layer Hybrid Composition


On Flutter for Android, there are three primary mechanisms for rendering a PlatformView, each with its own characteristics.


  1. Virtual Display (Legacy Mode):

    • How it works: An Android virtual display is created, which renders the native View into a SurfaceTexture. This SurfaceTexture is then passed to Flutter for display. This is similar to how a SurfaceTexture works, but with an extra layer of abstraction - the virtual display.

    • Pros: Compatibility with older versions of Android.

    • Cons:

      • Low Performance: Rendering occurs in a buffer, and then data is copied to the GPU. This creates latency and can consume more CPU resources.

      • Keyboard/Input Issues: The focus of the native View may not work correctly, making text input awkward.

      • Incorrect Gesture Handling: Gestures and taps are processed in Flutter and then translated to the virtual display, which can lead to desynchronization.

    • Use when: This mode is obsolete. It should only be used in exceptional cases where support for very old Android versions is required and Hybrid Composition is unavailable.

  2. Hybrid Composition:

    • How it works: This mode uses a SurfaceView or TextureView (depending on the implementation) and renders the native widget directly into the app's window. Flutter widgets that need to appear on top of the PlatformView are rendered in a separate layer. Flutter manages these layers to ensure correct overlaying.

    • Pros:

      • Significantly Higher Performance: Rendering happens directly on the GPU without unnecessary data copying.

      • Full Native Input Compatibility: The keyboard, gestures, and other events work just as they would in a standard native app.

      • Correct Layering: Flutter widgets can be layered over the PlatformView with proper transparency.

    • Cons:

      • Doesn’t respect Flutter widgets like Clip, Opacity, Transform.

      • FPS of application will be lower.

      • Can interfere with gesture detection.

    • Use when: You need full native interactivity (e.g., maps, ads, web content).

  3. Texture Layer (Texture Layer Hybrid Composition):

    • How it works: This is an improved version of Hybrid Composition. Instead of using a SurfaceView, which creates a separate layer in the window hierarchy, this approach renders the PlatformView into a SurfaceTexture, but does so more efficiently and without the latency of Virtual Display

    • Pros:

      • Even higher performance and lower memory usage compared to classic Hybrid Composition

      • Addresses some performance issues with a large number of PlatformView instances

      • Full compatibility with Flutter effects (Clip, Opacity, Hero, etc.)

    • Cons:

      • Requires manual SurfaceTexture management.

      • Quick scrolling (e.g. a web view) will be janky.

      • SurfaceViews are problematic in this mode and will be moved into a virtual display

    • Use when: You’re building camera apps, video players, AR, games, or real-time graphics.


    For our article, we'll use Hybrid Composition as the most balanced and modern approach, and Texture Layer as a more optimized approach that is suitable for working with a camera.


Creating the Application


To demonstrate this, we'll build a simple camera plugin. We'll use Pigeon to define an API that is split into two layers: a factory API (AndroidCameraApi) for creating and destroying instances, and an instance API (CameraInstanceApi) for controlling a specific camera.



1. Project Structure, Setup and adding Permissions

Create a new Flutter plugin and add pigeon to your dev_dependencies in pubspec.yaml:


dev_dependencies:
	pigeon: ^26.0.0

Add the following camerax dependencies in your build.gradle file, located at android/build.gradle, in the dependencies section:



Add the following permissions in your AndroidManifest.xml file, located at android/src/main, just above the <application> tag:



Create your Pigeon API using the camera_api.dart file you provided. This file defines three key APIs:

  • AndroidCameraApi: The factory API for initializing and creating/disposing of camera instances. It's responsible for creating a TextureView or PlatformView.

  • CameraInstanceApi: The instance API for controlling a specific camera. This will be called with a suffix matching the cameraId, allowing the Flutter side to communicate with a particular instance.

  • PlatformViewCallbackApi: An API that enables the native side to call methods in Flutter (for example, to notify when video recording has finished).



Generate the code using your camera_api.dart file:

dart run pigeon --input pigeons/camera_api.dart

2. Android Logic (Kotlin)

To avoid code duplication, we will create a class hierarchy that mirrors the modular structure of our Pigeon API.


2.1. CameraManager

The CameraManager is an abstract base class that encapsulates all the core CameraX logic, such as initialization, binding, photo capture, video recording, and zoom. It is completely abstracted from Flutter and UI components.

  • Abstract surfaceProvider() Method: This method must be implemented by its child classes. It provides the Preview.SurfaceProvider that CameraX uses to output the camera stream.

  • bindCamera() Method: This method uses the result of surfaceProvider() to set up the camera preview. This shared logic allows the CameraManager to remain independent of the specific display method.

  • dispose() Method: A common method to release all resources associated with CameraX, ensuring the camera is properly closed.


2.2. TextureCameraManager and PlatformViewCameraManager

The TextureCameraManager class is responsible for managing the lifecycle of the SurfaceTextureEntry and overrides the surfaceProvider() method to use the SurfaceTexture for the camera preview.



The PlatformViewCameraManager class is responsible for displaying the camera through a native PlatformView. It acts as an intermediary between the native view and the base CameraManager. This class implements the SurfaceProviderObserver interface to receive notifications from the PlatformView about when its surface is available or destroyed.

  • In the onSurfaceAvailable() method, it saves the received SurfaceProvider and passes it to the base CameraManager to bind the preview.

  • When the surface is destroyed, onSurfaceDestroyed() clears the surfaceProvider reference, informing the CameraManager that the surface is no longer available.

The overridden surfaceProvider() method returns the last saved surface (or null), allowing the CameraManager to dynamically rebind the preview whenever the surface becomes available.


For clarity, I created a class interaction diagram that illustrates the previously described relationships.


texture interaction diagram



2.3. PlatformView (CameraPreview and CameraPreviewFactory)


The CameraPreviewFactory acts as a factory for native PlatformViews. When the Flutter AndroidViewSurface widget requests to create a view, the factory uses the passed cameraId from creationParams to retrieve the corresponding CameraManager from the PlatformViewPlugin. It then creates and returns an instance of CameraPreview, passing the CameraManager to it, which in this case is the PlatformViewCameraManager.



CameraPreview is a class that implements the PlatformView interface and wraps a native PreviewView from CameraX. It receives the PlatformViewCameraManager as a SurfaceProviderObserver and interacts with it as follows:

  • Upon creation, CameraPreview calls onSurfaceAvailable() on the SurfaceProviderObserver, passing it the surfaceProvider from the PreviewView. This action notifies the PlatformViewCameraManager that the rendering surface is ready.

  • When dispose() is called, CameraPreview invokes onSurfaceDestroyed(), informing the PlatformViewCameraManager that the surface is no longer available.


platformview interaction diagram

Class PlatformViewPlugin is the central class of the plugin, acting as a high-level coordinator that manages the lifecycle of camera instances and handles communication between Flutter and the native side. It implements the necessary Flutter interfaces like FlutterPlugin and ActivityAware, along with the Pigeon API AndroidCameraApi.


Lifecycle Management

The class manages its own lifecycle and that of the camera instances it creates. It implements onAttachedToEngine and onDetachedFromEngine to connect and disconnect from the Flutter engine. By implementing ActivityAware, it gains access to the Activity and its LifecycleOwner, which is crucial for CameraX to manage the camera's state based on the Android application's lifecycle (e.g., stopping the camera when the app is in the background).


Camera Instance Factory

The core logic resides in the create() method. This method serves as a factory for creating camera instances. Based on the viewType parameter received from Flutter, it intelligently decides whether to create a TextureCameraManager for TextureView or a PlatformViewCameraManager for PlatformView. It then assigns a unique cameraId to each new instance and stores a reference to it in the cameraManagers map.


Communication and State Management

For each newly created camera, the plugin sets up a dedicated communication channel using Pigeon's CameraInstanceApi with a unique suffix (_$cameraId). This enables Flutter to send commands to a specific camera instance. The plugin also registers PlatformViewCallbackApi to allow the native side to send asynchronous callbacks back to Flutter. The cameraManagers map is key to this architecture, providing a central place to track and manage all active cameras by their unique IDs.



3. Flutter Logic


3.1 PlatformViewPlatform — The Cross-Platform API Abstraction


The PlatformViewPlatform class is a key component in our plugin's architecture, which uses a federated plugin approach. It is an abstract class that inherits from PlatformInterface and defines a shared interface for the Dart side. It serves as a contract that each platform-specific part of the plugin must implement (for example, PlatformViewAndroid for Android).

The main functions of this class are:

  • Unified API: It provides a complete set of methods for camera control (createWithOptions, switchCamera, takePicture, etc.), completely hiding the native implementation details. A Flutter developer always works with the same interface, making the code portable.

  • Instance Management: The createWithOptions and dispose methods allow for the creation and deletion of camera instances, while a cameraId is used as a unique identifier to interact with each specific instance.

  • Widget Integration: The buildViewWithOptions method is critically important. It abstracts the camera rendering logic, returning the correct widget (Texture or AndroidViewSurface) depending on the native implementation.

  • Event Streams: It defines a Stream (onRecordingFinished) that allows the native side to asynchronously send events back to Flutter without blocking the main thread.

Using PlatformViewPlatform ensures that the core logic of the Flutter application remains platform-independent. You only need to define this interface once, and then create concrete implementations for Android, iOS, web, and other platforms.



3.2 PlatformViewAndroid — The Android Implementation


PlatformViewAndroid is the concrete implementation of PlatformViewPlatform for the Android platform. It connects the high-level Dart API to the Pigeon-generated native code.

  • Initialization and State Management: This class manages the state of each camera instance using internal maps like cameras and cameraViewStates. The createWithOptions method saves the camera's view type (textureView or platformView) to determine which widget to build later.

  • Command Delegation: Methods like switchCamera and takePicture simply delegate their calls to the appropriate CameraInstanceApi instance, which in turn communicates with the native side.

  • Event Handling: It uses HostCameraMessageHandler to listen for native callbacks via Pigeon and funnel them into a Stream, which allows the Flutter side to handle events like a video recording finishing.


Helper Widgets (TextureCamera, PlatformViewCamera)

These simple StatelessWidget classes serve for the final rendering of the camera stream. They abstract direct work with the low-level Texture and AndroidViewSurface widgets from the developer, providing a clean and encapsulated interface.

  • TextureCamera: This widget wraps the built-in Texture widget. It accepts the textureId (which is the same as the cameraId) and uses it to display the data stream from the native surface.

  • PlatformViewCamera: This widget wraps the PlatformViewLink. It passes the registered viewType and creationParams (cameraId) to the native side, which allows the native factory to create and connect the corresponding PlatformView.



3.3. CameraController and CameraPreview — The Developer's API


The CameraController is the main class a developer interacts with. It extends ValueNotifier<CameraValue>, which allows widgets to easily react to changes in the camera's state, such as starting a video recording or completing a photo capture.

  • Initialization: The initialize() method is asynchronous and key to the controller's lifecycle. It calls createWithOptions() method, passing the chosen viewType, and receives a unique cameraId from the native side.

  • State Management: CameraController uses an internal CameraValue class to track states like isInitialized, isRecordingVideo, and isTakingPicture.

  • Commands: Methods like startRecording(), stopRecording(), takePicture(), and etc. simply delegate their calls to the corresponding _cameraPlatform methods.

  • Events: To handle asynchronous events from the native side, startRecording() subscribes to the onRecordingFinished() stream.

  • Resource Disposal: The dispose() method ensures that all resources on the native side are properly released.


The CameraPreview is a StatefulWidget that displays the camera feed on the screen. It listens to the CameraController to get the cameraId. Once a valid cameraId is available, it uses buildViewWithOptions() to render the correct widget (Texture or AndroidViewSurface). This architecture ensures a complete separation between the control logic (CameraController) and the display logic (CameraPreview), making the code robust and easy to test.



Conclusion


Integrating native code into Flutter is more than just a workaround — it’s a way to expand the framework beyond its default capabilities. PlatformView and SurfaceTexture represent two different strategies that allow you to blend native power with Flutter’s flexibility. PlatformView is ideal when you want to embed interactive components directly into your widget tree, while SurfaceTexture shines in scenarios where performance and smooth rendering of media streams are critical.


The real advantage comes when these approaches are combined with a thoughtful architecture. By using abstractions such as PlatformViewPlatform and communication tools like Pigeon, developers can design systems that are adaptable and future-proof. This makes it possible to switch between integration methods with minimal effort, keeping the Flutter codebase clean and maintainable.


Let’s Build the Future Together

At Igniscor, we go beyond standard Flutter development — we deliver seamless integrations that bridge native power with cross-platform flexibility. Whether it’s embedding native views or building smooth, high-performance interfaces, we transform complex ideas into elegant solutions.

Got a project in mind? Let’s make it happen — contact us today! 🚀


Comments


Recent Posts

Leave us a message and we'll get back to you

Our Location

Warszawska 6, lok. 32

Bialystok, Poland

Message was sent! Thanks

Send us your request
bottom of page