1. 概要
今回はタイトル通りにPlatformviewとWebViewを使ってNative(Android)側のViewをFlutterに描画して表示する内容です。
対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。
2. FlutterとNativeの連携方法
こちらを参考
3. プロジェクトの準備
3-1. プロジェクトを作成
4. ソースコード(Kotlin)
4-1. android/app/src/main/kotlin/com/example/platformview_web/FlutterWebView.kt
package com.example.platformview_web
import android.content.Context
import android.view.View
import android.webkit.WebView
import android.webkit.WebViewClient
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.platform.PlatformView
class FlutterWebView(context: Context?, messenger: BinaryMessenger, id: Int, creationParams: Map<String?, Any?>?) : PlatformView, MethodChannel.MethodCallHandler {
private val webView : WebView = WebView(context!!)
override fun getView(): View {
return webView
}
override fun dispose() {}
init {
webView.webViewClient = WebViewClient()
webView.apply {
settings.apply {
javaScriptEnabled = true
}
}
val initialUrl = creationParams?.get("initialUrl") as String
initialUrl.let {
webView.loadUrl(it)
}
MethodChannel(messenger, "samples.flutter.dev/webview").also {
it.setMethodCallHandler(this)
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when(call.method) {
"loadUrl" -> {
val arguments = call.arguments as Map<String?, String?>
val url = arguments["url"] ?: ""
loadUrl(url)
result.success(null)
}
"goBack" -> {
goBack()
result.success(null)
}
"goForward" -> {
goForward()
result.success(null)
}
else -> result.notImplemented()
}
}
private fun loadUrl(url: String) {
webView.loadUrl(url)
}
private fun goBack() {
webView.goBack()
}
private fun goForward() {
webView.goForward()
}
}
- PlatformView (flutter.dev)
- MethodChannel.MethodCallHandler (flutter.dev)
- 「samples.flutter.dev/webview」
- Flutter側との通路
The client and host sides of a channel are connected through a channel name passed in the channel constructor. All channel names used in a single app must be unique; prefix the channel name with a unique ‘domain prefix’, for example:
https://docs.flutter.dev/development/platform-integration/platform-channels?tab=type-mappings-kotlin-tabsamples.flutter.dev/battery
.
4-2. android/app/src/main/kotlin/com/example/platformview_web/FlutterWebViewFactory.kt
package com.example.platformview_web
import android.content.Context
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
class FlutterWebViewFactory(private val messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, id: Int, args: Any?): PlatformView {
val creationParams = args as Map<String?, Any?>?
return FlutterWebView(context, messenger, id, creationParams)
}
}
4-3. android/app/src/main/kotlin/com/example/platformview_web/PlatformviewWebPlugin.kt
package com.example.platformview_web
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
class PlatformviewWebPlugin: FlutterPlugin {
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
flutterPluginBinding.platformViewRegistry
.registerViewFactory("samples.flutter.dev/webview/plugin", FlutterWebViewFactory(flutterPluginBinding.binaryMessenger))
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {}
}
- FlutterPlugin
- PlatformViewRegistry (flutter.dev)
- 「samples.flutter.dev/webview/plugin」
- Platform Viewタイプの識別子
- 「samples.flutter.dev/webview/plugin」
4-4. android/app/src/main/kotlin/com/example/platformview_web/MainActivity.kt
- このファイルはFlutterプロジェクトを作成すると、自動生成されるので内容を変更
package com.example.platformview_web
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
flutterEngine.plugins.add(PlatformviewWebPlugin())
}
}
5. ソースコード(Dart)
5-1. lib/main.dart
import 'package:flutter/material.dart';
import 'platform_webview_controller.dart';
import 'platform_webview_widget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final controller = PlatformWebviewController();
String initialUrl = 'https://www.google.com';
@override
void initState() {
super.initState();
}
void goForward() {
controller.goForward();
}
void goBack() {
controller.goBack();
}
void onSubmittedUrl(String url) {
controller.loadUrl(url);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Platformview example app'),
),
body: Center(
child: Column(
children: [
TextField(
controller: TextEditingController(text: initialUrl),
onSubmitted: onSubmittedUrl,
maxLines: 1,
decoration: const InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 8.0),
border: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent)),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue))),
),
Expanded(
child: PlatformWebviewWidget(
initialUrl: initialUrl,
)),
Row(
children: [
IconButton(
onPressed: goBack, icon: const Icon(Icons.arrow_back)),
IconButton(
onPressed: goForward,
icon: const Icon(Icons.arrow_forward)),
],
),
],
),
),
),
);
}
}
5-2. lib/platform_webview_controller.dart
import 'package:flutter/services.dart';
class PlatformWebviewController {
final MethodChannel _channel =
const MethodChannel('samples.flutter.dev/webview');
Future<void> loadUrl(String url) async {
return await _channel.invokeMethod('loadUrl', {'url': url});
}
Future<void> goBack() async {
return await _channel.invokeMethod('goBack');
}
Future<void> goForward() async {
return await _channel.invokeMethod('goForward');
}
}
Native側のメソッドを呼び出す。
- 「MethodChannel」
- samples.flutter.dev/webview
- Native側との通路
- 「loadUrl」「goBack」「goForward」
- Native側に用意されているメソッド名
5-3. lib/platform_webview_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class PlatformWebviewWidget extends StatelessWidget {
final String initialUrl;
// ignore: use_key_in_widget_constructors
const PlatformWebviewWidget({required this.initialUrl});
@override
Widget build(BuildContext context) {
const viewType = 'samples.flutter.dev/webview/plugin';
final creationParams = <String, dynamic>{
'initialUrl': initialUrl,
};
return AndroidView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
}
- AndroidView constructor – AndroidView class – widgets library – Dart API (flutter.dev)
- 「samples.flutter.dev/webview/plugin」
- 上記Kotlinにて作成したPlatform Viewタイプの識別子
- 「samples.flutter.dev/webview/plugin」
6. 結果
7. 参考
- Writing custom platform-specific code | Flutter
- 現場で使える Flutter開発入門 | マイナビブックス (mynavi.jp)
- 6-4. PlatformViewの実装
投稿者プロフィール
-
開発好きなシステムエンジニアです。
卓球にハマってます。