Flutter excels at providing a cross-platform UI toolkit, enabling developers to build natively compiled applications from a single codebase. However, there are situations where you might want to incorporate native UI components directly into your Flutter app. This is where PlatformView comes into play. PlatformView allows you to embed native Android and iOS views within your Flutter application.
What is PlatformView?
PlatformView is a Flutter mechanism that lets you render platform-specific (native Android or iOS) UI components within a Flutter application. It’s essentially a bridge between Flutter’s rendering pipeline and the native platform’s UI rendering capabilities.
Why Use PlatformView?
- Leverage Native Components: Integrate native features and UI components not yet available in Flutter.
- Performance: For computationally heavy or graphically intensive UI tasks, native components may offer better performance.
- Access to Native APIs: Use native APIs directly through the platform view.
- Existing Native Codebases: Integrate existing native UI components with minimal rewriting.
How to Implement PlatformView in Flutter
Implementing PlatformView involves several steps:
Step 1: Set Up Platform-Specific Code
First, you need to create the native view in either Android (Java/Kotlin) or iOS (Swift/Objective-C).
Android (Kotlin)
import android.content.Context
import android.view.View
import android.widget.TextView
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
class NativeTextView(context: Context, id: Int, creationParams: Map<String, Any?>?) : PlatformView, MethodCallHandler {
private val textView: TextView = TextView(context)
private val channel: MethodChannel
init {
textView.text = "Native Text View (Android)"
channel = MethodChannel(FlutterBoost.instance().engineProvider.dartExecutor, "native_text_view_$id")
channel.setMethodCallHandler(this)
creationParams?.let {
val text : String? = it["text"] as? String
text?.let {
textView.text = it
}
}
}
override fun getView(): View {
return textView
}
override fun dispose() {}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"setText" -> {
val text = call.arguments as? String
text?.let {
textView.text = it
result.success(null)
} ?: result.error("ARGUMENT_ERROR", "Text cannot be null", null)
}
else -> result.notImplemented()
}
}
}
This Kotlin code creates a simple TextView.
iOS (Swift)
import Flutter
import UIKit
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, viewId: viewId, args: args, messenger: messenger)
}
}
class NativeTextView: NSObject, FlutterPlatformView {
private var _view: UIView
init(
frame: CGRect,
viewId: Int64,
args: Any?,
messenger: FlutterBinaryMessenger
) {
_view = UIView()
super.init()
createNativeView(view: _view, args: args)
}
func view() -> UIView {
return _view
}
func createNativeView(view _view: UIView, args: Any?){
let nativeLabel = UILabel(frame: _view.bounds)
nativeLabel.text = "Native Text View (iOS)"
nativeLabel.textAlignment = .center
if let arguments = args as? [String: Any],
let text = arguments["text"] as? String {
nativeLabel.text = text
}
_view.addSubview(nativeLabel)
}
}
This Swift code creates a simple UILabel wrapped in a UIView.
Step 2: Register the Platform View Factory
You need to register the platform view factory in your platform-specific code.
Android (Kotlin) – In your MainActivity.kt or Flutter Plugin
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine
.platformViewsController
.registry
.registerViewFactory("native_text_view", NativeTextViewFactory(this))
}
}
class NativeTextViewFactory(private val context: Context): 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)
}
}
iOS (Swift) – In your AppDelegate.swift
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 nativeViewFactory = NativeTextViewFactory(messenger: controller.binaryMessenger)
controller.registrar(forPlugin: "native_text_view")!.register(
nativeViewFactory,
withId: "native_text_view")
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Step 3: Use PlatformView in Flutter
Now, use the PlatformView widget in your Flutter code:
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'dart:io' show Platform;
class PlatformTextView extends StatelessWidget {
final String text;
const PlatformTextView({Key? key, required this.text}) : super(key: key);
@override
Widget build(BuildContext context) {
String viewType = 'native_text_view';
// Pass parameters to the platform view.
final Map<String, dynamic> creationParams = <String, dynamic>{};
creationParams["text"] = text;
if (Platform.isAndroid) {
return PlatformViewLink(
viewType: viewType,
surfaceFactory: (context, controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: const
Usage:
import 'package:flutter/material.dart';
import 'platform_text_view.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('PlatformView Example'),
),
body: Center(
child: PlatformTextView(text: "Hello from Flutter!"),
),
),
);
}
}
This code uses the PlatformView widget to display the native text view in both Android and iOS. The platform-specific view is selected based on the running platform.
Step 4: Communicate Between Flutter and Native Code (Optional)
You can set up method channels to communicate between Flutter and the native view. In the examples above, a method channel has been implemented.
To invoke the Method Channel from Flutter, the following can be performed:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class NativeTextView extends StatefulWidget {
@override
_NativeTextViewState createState() => _NativeTextViewState();
}
class _NativeTextViewState extends State<NativeTextView> {
static const platform = MethodChannel('native_text_view');
Future<void> _setText(String text) async {
try {
await platform.invokeMethod('setText', {'text': text});
} on PlatformException catch (e) {
print("Failed to set text: '${e.message}'.");
}
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
// Existing Native View Implementation using PlatformView
ElevatedButton(
child: Text('Update Text'),
onPressed: () {
_setText("Text Updated from Flutter!");
},
),
],
);
}
}
In this Dart code:
- A
MethodChannelis set up to invoke a native method - The Flutter-side invokes this
MethodChannelupon button press.
Considerations
- Performance Overhead:
PlatformViewcan introduce performance overhead due to the context switching between Flutter and native environments. - Complexity: Implementing
PlatformViewrequires writing and maintaining platform-specific code. - Accessibility: Make sure your native views properly implement accessibility features.
- Widget Lifecycle: Native views have their own lifecycle. It is necessary to correctly handle lifecycle events.
Conclusion
PlatformView offers a powerful way to incorporate native UI components into your Flutter application, providing flexibility and access to platform-specific features. While it introduces some complexity and overhead, it is an invaluable tool when you need to leverage native capabilities. Carefully consider your requirements and weigh the benefits against the costs when deciding whether to use PlatformView. Understanding the setup and use of this mechanism enables Flutter developers to deliver feature-rich and high-performance apps by combining Flutter's advantages with native strengths.