Flutter is a versatile framework for building cross-platform applications, allowing developers to write code once and deploy it on multiple platforms, including Android and iOS. However, there are scenarios where you need to integrate native platform-specific components, such as when utilizing certain native APIs or third-party native libraries. This is where PlatformView
comes into play. PlatformView
enables you to host native Android View
s and iOS UIView
s directly within your Flutter application. This guide will provide a comprehensive overview of how to implement PlatformView
in Flutter.
What is Flutter PlatformView
?
PlatformView
is a widget in Flutter that acts as a bridge, allowing you to embed native UI components (Android’s View
or iOS’s UIView
) directly into your Flutter application’s widget tree. This is particularly useful when you want to leverage specific native features or UI elements that are not readily available or easily replicable in Flutter. By using PlatformView
, you can combine the benefits of Flutter’s rapid development with the power and flexibility of native platform capabilities.
Why Use PlatformView
?
- Access to Native Features: Use platform-specific APIs and functionalities directly within your Flutter app.
- Integration of Native UI Components: Incorporate custom native UI elements or third-party native libraries seamlessly.
- Performance Optimization: Utilize native components for performance-critical tasks that might be less efficient in Flutter.
How to Implement PlatformView
in Flutter
Implementing PlatformView
involves creating platform-specific code to define and manage the native view, as well as Flutter code to host it. Let’s go through the process step by step.
Step 1: Setting Up Your Flutter Project
First, create a new Flutter project or navigate to your existing project.
flutter create my_platform_view_app
cd my_platform_view_app
Step 2: Defining the PlatformView
Factory in Flutter
You need to define a PlatformViewFactory
, which is responsible for creating instances of the native view. This factory is registered with a unique identifier that you’ll use in your Flutter code.
import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; class NativeTextView extends StatefulWidget { final String text; const NativeTextView({Key? key, required this.text}) : super(key: key); @override _NativeTextViewState createState() => _NativeTextViewState(); } class _NativeTextViewState extends State { @override Widget build(BuildContext context) { // This is used in the platform side to register the view. const String viewType = 'native_text_view'; // Pass parameters to the platform side. final Map<String, dynamic> creationParams = <String, dynamic>{ "text": widget.text, }; return PlatformViewLink( viewType: viewType, surfaceFactory: (context, controller) { return AndroidViewSurface( controller: controller as AndroidViewController, gestureRecognizers: const <Factory>{}, hitTestBehavior: PlatformViewHitTestBehavior.opaque, ); }, onCreatePlatformView: (params) { return PlatformViewsService.initSurfaceAndroidView( id: params.id, viewType: viewType, layoutDirection: TextDirection.ltr, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), onFocus: () { params.onFocusChanged(true); }, ) ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) ..create(); }, ); } }
Step 3: Implementing the Native View on Android
Navigate to the Android part of your Flutter project (android/app/src/main/kotlin/.../MainActivity.kt
) and implement the PlatformViewFactory
.
package com.example.my_platform_view_app import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.platform.PlatformView import io.flutter.plugin.platform.PlatformViewFactory import android.content.Context import android.view.View import android.widget.TextView class MainActivity: FlutterActivity() { override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) flutterEngine .platformViewsController .registry .registerViewFactory("native_text_view", NativeTextViewFactory(flutterEngine.dartExecutor.binaryMessenger)) } } class NativeTextViewFactory(private val messenger: io.flutter.plugin.common.BinaryMessenger): PlatformViewFactory(StandardMessageCodec.INSTANCE) { override fun create(context: Context, viewId: Int, args: Any?): PlatformView { val creationParams = args as? Map<String?, Any?> return NativeTextView(context, viewId, creationParams?.get("text") as? String ?: "") } } class NativeTextView(context: Context, id: Int, text: String) : PlatformView { private val textView: TextView = TextView(context) override fun getView(): View { return textView } init { textView.text = text } override fun dispose() {} }
Key parts of the Android implementation:
NativeTextViewFactory
: Creates instances ofNativeTextView
.NativeTextView
: The native AndroidTextView
which will be hosted in Flutter.registerViewFactory
: Registers theNativeTextViewFactory
with theFlutterEngine
.
Step 4: Implementing the Native View on iOS
For iOS, you’ll need to modify the AppDelegate.swift
file.
import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let factory = NativeTextViewFactory(messenger: controller.binaryMessenger) controller.register( factory, withId: "native_text_view") GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } class NativeTextViewFactory: NSObject, FlutterPlatformViewFactory { private var messenger: FlutterBinaryMessenger init(messenger: FlutterBinaryMessenger) { self.messenger = messenger super.init() } func create( withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any? ) -> FlutterPlatformView { return NativeTextView(frame: frame, viewIdentifier: viewId, arguments: args, binaryMessenger: messenger) } } class NativeTextView: NSObject, FlutterPlatformView { private var _view: UILabel init(frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger messenger: FlutterBinaryMessenger?) { _view = UILabel() super.init() createNativeView(view: _view, arguments: args) } func view() -> UIView { return _view } func createNativeView(view _view: UILabel, arguments args: Any?){ guard let args = args as? [String: Any] else { _view.text = "No text"; return } _view.text = args["text"] as? String } }
Key points for the iOS implementation:
NativeTextViewFactory
: Creates instances ofNativeTextView
for iOS.NativeTextView
: Implements theFlutterPlatformView
protocol and returns aUILabel
.register
: Registers theNativeTextViewFactory
with the Flutter engine.
Step 5: Using the PlatformView
in Flutter
Now you can use the NativeTextView
widget in your Flutter UI.
import 'package:flutter/material.dart'; import 'native_text_view.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter PlatformView Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: const Text('PlatformView Demo'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Native Text View:', style: TextStyle(fontSize: 20), ), SizedBox(height: 20), SizedBox( width: 300, height: 100, child: NativeTextView(text: "Hello from Native!") ), ], ), ), ), ); } }
In this example, the NativeTextView
widget is placed within a Column
in the Flutter UI. The native text view will render with the provided text.
Advanced Usage
Communication Between Flutter and Native Code
You can also establish communication between your Flutter code and the native PlatformView
using MethodChannel
. This allows you to send data and invoke methods on the native side.
// Flutter side import 'package:flutter/services.dart'; class NativeTextView extends StatefulWidget { // ... @override _NativeTextViewState createState() => _NativeTextViewState(); } class _NativeTextViewState extends State { static const MethodChannel methodChannel = MethodChannel('native_text_view_channel'); @override void initState() { super.initState(); // Call a method on the native side methodChannel.invokeMethod('setText', {'text': widget.text}); } // ... } // Android side import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel class NativeTextView(...) : PlatformView, MethodChannel.MethodCallHandler { private val methodChannel: MethodChannel = MethodChannel(messenger, "native_text_view_channel") init { methodChannel.setMethodCallHandler(this) } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "setText" -> { val text = call.argument("text") ?: "" textView.text = text result.success(null) } else -> { result.notImplemented() } } } // ... }
Conclusion
Flutter’s PlatformView
provides a robust mechanism for embedding native Android and iOS views into your Flutter application. By following this detailed guide, you can integrate native UI components and leverage platform-specific features, enhancing your app’s functionality and performance. Whether you need to access native APIs, integrate third-party native libraries, or optimize performance-critical tasks, PlatformView
allows you to combine the best of both worlds: Flutter’s rapid development and the power of native platforms.