方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

[Flutter] How to make a modal display scrollable in WebView.

flutter-scrollable-webview
This article can be read in about 20 minutes.

Introduction.

Currently developing “Mia,” a talking cat-shaped robot that speaks dialect.

Mia
The talking cat-shaped robot Mia shares sadness and joy with more than 100 different rich expressions. It also speaks in...

We have created a website for Mia and a Terms of Use/Prapolicy, and now we will make these accessible from the flutter application.

Mia website: https://mia-cat.com/en
Terms of use: https://mia-cat.com/terms-of-service/
Plapoli: https://mia-cat.com/privacy-policy/

I think it would be possible to transition externally from the app to the web page of the terms of use and plasticoli, but I think the WebView display in the app would improve the UX a little.

The app has a user settings screen, and within that screen, there are items for terms of use and plastic polys, so when this item is tapped, the contents of each item are displayed in WebView and can be closed.

Terms of Use and Display on the PlaPolis Web Site

I tried to embed notion, but..,

Let me start with a quiet question. First of all, I thought it would be easier to display the terms of use/prappolli on the website if I could create them in notion and embed them in the website, since the updating process only involves changing the text in notion. I found several services that allow you to embed it in a wordpress shortcode. I actually tried it and confirmed that it works correctly.

Example: Embed Notion Pages (WordPress plugin available): but for a fee

https://www.embednotionpages.com/embeds

However, all the services are paid services, and I thought to myself, “Why pay for a service that merely embeds a page? I thought that I wouldn’t update it that much, so I put it on a fixed page of wordpress this time. By the way, google document can be embedded and updated for free. No additional plug-ins or services are required.

In the future, we would like to consolidate our FAQs and manuals into notion as well, so perhaps we will use a paid notion embed service. If there is a way to do this for free (including implementing it on my own), I would appreciate it if someone could let me know. m(_ _)m

Introducing the Flutter plugin: webview_flutter

There are several plugins that provide webview in Flutter, but two major ones are candidates.

  • webview_flutterPolylang
    placeholder do not modify
  • flutter_inappwebview

    →webview_flutter Third-party web view packages offering more advanced functionality

    It offers more features than webview_flutter, including JavaScript channel support, HTTP request override, custom context menu creation, and user agent customization.

In this case, a simple implementation will suffice, so introduce webview_flutter

Incidentally, there was a Google Codelabs (hands-on content with guidelines and tutorials on various implementations) titled “Adding WebView to your Flutter app”.

Flutter アプリに WebView を追加する  |  Google Codelabs
この Codelab では、webview_flutter プラグインを Flutter アプリに追加する方法を学びます。
ShellScript
$ flutter pub add webview_flutter
$ flutter pub get

To use the webview_flutter plugin in Android, the minSDK must be set to 19 or 20 depending on the view of the Android platform used, so modify the gradle file if the minSDK does not meet the requirements.

Dart
// android/app/build.gradle
defaultConfig {
    // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
    applicationId "com.example.webview_in_flutter"
    minSdkVersion 20        // MODIFY
    targetSdkVersion 30
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
}

Create a new screen to display the Terms of Use and PlaPoli

See Usage in package webview_flutter.

webview_flutter | Flutter package
A Flutter plugin that provides a WebView widget backed by the system webview.

Create an instance of WebViewController, pass that instance to WebViewWidget as an argument, and display WebViewWidget in the body ofScaffold.

When creating an instance of WebViewController, several methods can be set.

  • setJavaScriptMode(JavaScriptMode.unrestricted): Allow JavaScript execution in WebView
  • setBackgroundColor(const Color(0x00000000)): Sets the background color of WebView.
  • setNavigationDelegate(NavigationDelegate(...)): : .
  • It can allow or deny actions when a page starts loading, completes loading, or encounters an error, as well as allow or deny certain navigation requests. For example, onNavigationRequest controls navigation to a specific URL. Here, navigation is prevented if the URL begins with https://www.youtube.com/.
  • loadRequest(Uri.parse('https://flutter.dev')): Loads a Web page with the specified URL in WebView. In this example, https://flutter.dev is loaded.
Dart
controller = WebViewController()
  ..setJavaScriptMode(JavaScriptMode.unrestricted)
  ..setBackgroundColor(const Color(0x00000000))
  ..setNavigationDelegate(
    NavigationDelegate(
      onProgress: (int progress) {
        // Update loading bar.
      },
      onPageStarted: (String url) {},
      onPageFinished: (String url) {},
      onWebResourceError: (WebResourceError error) {},
      onNavigationRequest: (NavigationRequest request) {
        if (request.url.startsWith('https://www.youtube.com/')) {
          return NavigationDecision.prevent;
        }
        return NavigationDecision.navigate;
      },
    ),
  )
  ..loadRequest(Uri.parse('https://flutter.dev'));

In this case, since it is simple enough to display the URL ( https://mia-cat.com/terms-of-service) of the Terms of Use, only the loadRequest method should be used to load the URL.

Dart
// lib/screens/home/terms_of_service_screen.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class TermsOfServiceScreen extends StatefulWidget {
  const TermsOfServiceScreen({Key? key}) : super(key: key);

  @override
  _TermsOfServiceScreenState createState() => _TermsOfServiceScreenState();
}

class _TermsOfServiceScreenState extends State {
  late WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..loadRequest(Uri.parse('https://mia-cat.com/terms-of-service/'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('利用規約')),
      body: WebViewWidget(controller: _controller),
    );
  }
}

After the Terms of Use are successfully displayed, you can return to the settings screen by clicking the “<:Back” button on the left side of the AppBar.

WebView display when tapping on the Terms of Use or Plapori link

This time, use the showModalBottomSheet function to display the bottom sheet sliding up from the bottom, instead of using navigation.push.

showModalBottomSheet function - material library - Dart API
API docs for the showModalBottomSheet function from the material library, for the Dart programming language.

isScrollControlled: true:.

  • Setting this option to true allows the bottom sheet to be displayed in full screen mode. This allows the height of the bottom sheet to be set freely. Normally, the bottom sheet is only displayed up to half the screen height, but with this option, it can be extended to, for example, 80% of the screen height (specified with heightFactor). Of course, 100% is also possible.

FractionallySizedBox:.

  • This widget is used to define the size of its child widgets as a percentage of its parent’s size. Here, heightFactor is set to 0.8, which sets the height of TermsOfServiceScreen to 80% of the parent’s height (i.e., screen height).
Dart
Widget buildItem(String item, bool isLast) {
    final user = ref.watch(userProvider);
    return Container(
      decoration: BoxDecoration(
        
      ),
      child: Column(
        children: [
          ListTile(
            title: Row(
             
            ),
            trailing: const Icon(Icons.chevron_right),
            onTap: () async {
              switch (item) {
                
                case '利用規約':
                  showModalBottomSheet(
                    context: context,
                    isScrollControlled: true,
                    builder: (_) => const FractionallySizedBox(
                      heightFactor: 0.8,
                      child: TermsOfServiceScreen(),
                    ),
                  );
                  break;
              }
            },
          ),
        ],
      ),
    );
  }

Fix navigation in Terms of Use display.

By default, there is a “<” icon on the left side of the AppBar, and clicking this icon will close the Terms of Use screen and return to the Advanced Settings screen.

However, this behavior is a little confusing, so the “<” should be removed and instead the “X (close)” button should be displayed on the right side of the AppBar, and the screen should be changed so that clicking this button will close the screen.

Override and hide the default back button ( <icon ) by placing a Container()( an empty container that displays nothing) in the leading of theAppBar.

And for the right side of the AppBar, use the actions property of the AppBar widget, which takes a list of widgets, and these widgets are aligned horizontally on the right side of the AppBar.

icon: const Icon(Icons.close), to display the “Close” button on the right side of the AppBar widget

Dart
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('利用規約'),
      leading: Container(),
      actions: [
        IconButton(
          icon: const Icon(Icons.close),
          onPressed: () {
            Navigator.of(context).pop();
          },
        ),
      ],
    ),
    body: WebViewWidget(controller: _controller),
  );
}

It is easier to understand.

Make WebView scrollable.

As it is now, the terms of use are displayed but not scrollable, so make it scrollable.

The showModalBottomSheet function mentioned earlier has a parameter called enableDrag, which is true by default.

If enableDrag is set to true in showModalBottomSheet, the user can drag the entire bottom sheet up and down. This dragging operation may conflict with scrolling operations within WebView. That is, when the user tries to scroll WebView, the gesture is interpreted as a drag operation in the bottom sheet, and as a result, scrolling in WebView may not work correctly.

Therefore, set enableDrag to false to disable the bottom sheet drag operation. In this case, users will not be able to close the bottom sheet by dragging, but since we have prepared a close button on the right side of the modal AppBar, the modal hide function by dragging is not necessary.

Dart
Widget buildItem(String item, bool isLast) {
    final user = ref.watch(userProvider);
    return Container(
      decoration: BoxDecoration(
        
      ),
      child: Column(
        children: [
          ListTile(
            title: Row(
             
            ),
            trailing: const Icon(Icons.chevron_right),
            onTap: () async {
              switch (item) {
                
                case '利用規約':
                  showModalBottomSheet(
                    context: context,
                    isScrollControlled: true,
                    enableDrag: false,
                    builder: (_) => const FractionallySizedBox(
                      heightFactor: 0.8,
                      child: TermsOfServiceScreen(),
                    ),
                  );
                  break;
              }
            },
          ),
        ],
      ),
    );
  }

operation check

After successfully tapping the ListItem of the Terms of Use, the WebView of the Terms of Use is displayed from the bottom of the screen, can be scrolled, and the modal can be closed with the close icon in the upper right corner of the modal.

コメント

Copied title and URL