【Flutter】PlatformviewとWebViewを使い、ネイティブのViewをFlutterに表示

flutter

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()
    }
}

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: samples.flutter.dev/battery.

https://docs.flutter.dev/development/platform-integration/platform-channels?tab=type-mappings-kotlin-tab

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) {}
}

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(),
    );
  }
}

6. 結果

7. 参考

投稿者プロフィール

Sondon
開発好きなシステムエンジニアです。
卓球にハマってます。

関連記事

  1. 【Widget of the Week】#7 FadeTransiti…

  2. flutter

    【Flutter】2回目以降のリリース,Android,iOS

  3. flutter

    【Flutter】KotlinでNative側を実装しFlutterと…

  4. flutter

    Flutterのすゝめ

  5. 【予告】Widget of the Week 解説スタート!

  6. flutter

    【Flutter】開発環境構築(Flutter,VSCode,Wind…

最近の記事

  1. 日記アプリ第5回
  2. 日記アプリ第4回
  3. React
  4. 日記アプリ第3回
  5. 日記アプリ第2回
  6. 日記アプリ第1回

制作実績一覧

  1. Vivaya
  2. Checkeys