【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】#2 Expanded

  2. flutter

    【Flutter】画面が開く前にローディングを表示

  3. flutter

    【Flutter】Pankoに「みんなで割り勘」機能を追加

  4. flutter

    【Flutter】Widgetテスト(Flutter,VSCode,T…

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

  6. flutter

    【Flutter】CustomPainterを使い、図形を描画

最近の記事

  1. AWS
  2. flutter

制作実績一覧

  1. Checkeys