Flutter ウェブアプリでプラグインを使用する

Flutter は、1 つのコードベースからネイティブにコンパイルして、モバイル、ウェブ、デスクトップの美しいアプリケーションを作成できる Google の UI ツールキットです。この Codelab では、GitHub リポジトリのスター数をレポートするアプリを完成させます。Dart DevTools を使用して簡単なデバッグを行います。Firebase でアプリをホストする方法を学びます。最後に、Flutter プラグインを使用してアプリを起動し、ホストされているプライバシー ポリシーを開きます。

学習内容

  • ウェブアプリで Flutter プラグインを使用する方法
  • パッケージとプラグインの違い
  • Dart DevTools を使用してウェブアプリをデバッグする方法
  • Firebase でアプリをホストする方法

前提条件: この Codelab は、Flutter の基本的な知識があることを前提としています。Flutter を初めて使用する場合は、まずウェブで初めての Flutter アプリを作成するから始めることをおすすめします。

この Codelab で学びたいことは次のどれですか?

このトピックは初めてなので、簡単に概要を知りたい。このトピックについてはある程度知っているが、復習したい。プロジェクトで使用するサンプルコードを探している。特定の項目に関する説明を確認したい。

プラグイン(プラグイン パッケージともいいます)は、Dart コードで記述された API と 1 つ以上のプラットフォーム固有の実装を組み合わせた、特殊な Dart パッケージです。プラグイン パッケージは、Android(Kotlin または Java を使用)、iOS(Swift または Objective-C を使用)、ウェブ(Dart を使用)、macOS(Dart を使用)、またはその任意の組み合わせ用に作成できます(実際、Flutter は連携プラグインをサポートしており、さまざまなプラットフォームのサポートをパッケージ間で分割できます)。

パッケージは、アプリの機能を拡張または簡略化するために使用できる Dart ライブラリです。前述のように、プラグインはパッケージの一種です。パッケージとプラグインの詳細については、Flutter プラグインまたは Dart パッケージをご覧ください。

この Codelab を完了するには、Flutter SDKエディタChrome ブラウザという 3 つのソフトウェアが必要です。Android Studio や IntelliJ(Flutter プラグインや Dart プラグインがインストールされている)、Visual Studio Code(Dart Code と Flutter の拡張機能を備えている)などの任意のエディタを使用できます。Chrome で Dart DevTools を使用してコードをデバッグします。

この Codelab では開始用コードの大部分を提供しているため、必要なものをすぐに利用できます。

b2f84ff91b0e1396.png テンプレート化された簡単な Flutter アプリを作成します。

star_counter という Flutter プロジェクトを作成し、次のように null 安全に移行します。

$ flutter create star_counter
$ cd star_counter
$ dart migrate --apply-changes

b2f84ff91b0e1396.png pubspec.yaml ファイルを更新します。プロジェクトの一番上にある pubspec.yaml ファイルを更新します。

pubspec.yaml

name: star_counter
description: A GitHub Star Counter app
version: 1.0.0+1
environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  flutter_markdown: ^0.6.0
  github: ^8.0.0
  intl: ^0.17.0

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

b2f84ff91b0e1396.png 更新された依存関係を取得します。IDE で [Pub get] ボタンをクリックするか、コマンドラインでプロジェクトの一番上から flutter pub get を実行します。

b2f84ff91b0e1396.png lib/main.dart の内容を置き換えます。lib/main.dart からすべてのコードを削除します。これにより、マテリアル テーマの count-the-number-of-button-presses アプリが作成されます。次のコードを追加します。これにより、未完了の count-the-number-of-stars-on-a-GitHub-repo アプリが設定されます。

lib/main.dart

import 'package:flutter/material.dart';

void main() {
  runApp(StarCounterApp());
}

class StarCounterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        brightness: Brightness.light,
      ),
      routes: {
        '/': (context) => HomePage(),
      },
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String _repositoryName = "";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ConstrainedBox(
          constraints: BoxConstraints(maxWidth: 400),
          child: Card(
            child: Padding(
              padding: EdgeInsets.all(16.0),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Text(
                    'GitHub Star Counter',
                    style: Theme.of(context).textTheme.headline4,
                  ),
                  TextField(
                    decoration: InputDecoration(
                      labelText: 'Enter a GitHub repository',
                      hintText: 'flutter/flutter',
                    ),
                    onSubmitted: (text) {
                      setState(() {
                        _repositoryName = text;
                      });
                    },
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 32.0),
                    child: Text(
                      _repositoryName,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

b2f84ff91b0e1396.png Chrome でアプリを実行します。IDE を使用する場合は、まずデバイス プルダウンから [Chrome] を選択します。コマンドラインを使用する場合は、パッケージの一番上から flutter run -d chrome を実行します(flutter devices によって、ウェブが構成されているが他の接続済みのデバイスがないことが示された場合、flutter run コマンドはデフォルトで Chrome に設定されます)。

Chrome が起動し、次のように表示されます。

97cb2368f34eb03c.png

テキスト フィールドになんらかのテキストを入力して、Enter キーを押します。入力したテキストがウィンドウの下部に表示されます。

次に、「google/flutter.widgets」フォームに入力したテキストを表示するのではなく、そのリポジトリのスター数を表示するようにアプリを変更します。

b2f84ff91b0e1396.png libstar_counter.dart という新しいファイルを作成します。

lib/star_counter.dart

import 'package:flutter/material.dart';
import 'package:github/github.dart';
import 'package:intl/intl.dart' as intl;

class GitHubStarCounter extends StatefulWidget {
  /// The full repository name, e.g. torvalds/linux
  final String repositoryName;

  GitHubStarCounter({
    required this.repositoryName,
  });

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

class _GitHubStarCounterState extends State<GitHubStarCounter> {
  // The GitHub API client
  late GitHub github;

  // The repository information
  Repository? repository;

  // A human-readable error when the repository isn't found.
  String? errorMessage;

  void initState() {
    super.initState();
    github = GitHub();

    fetchRepository();
  }

  void didUpdateWidget(GitHubStarCounter oldWidget) {
    super.didUpdateWidget(oldWidget);

    // When this widget's [repositoryName] changes,
    // load the Repository information.
    if (widget.repositoryName == oldWidget.repositoryName) {
      return;
    }

    fetchRepository();
  }

  Future<void> fetchRepository() async {
    setState(() {
      repository = null;
      errorMessage = null;
    });

    if (widget.repositoryName.isNotEmpty) {
      var repo = await github.repositories
          .getRepository(RepositorySlug.full(widget.repositoryName));
      setState(() {
        repository = repo;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;
    final textStyle = textTheme.headline4?.apply(color: Colors.green);
    final errorStyle = textTheme.bodyText1?.apply(color: Colors.red);
    final numberFormat = intl.NumberFormat.decimalPattern();

    if (errorMessage != null) {
      return Text(errorMessage!, style: errorStyle);
    }

    if (widget.repositoryName.isNotEmpty && repository == null) {
      return Text('loading...');
    }

    if (repository == null) {
      // If no repository is entered, return an empty widget.
      return SizedBox();
    }

    return Text(
      '${numberFormat.format(repository!.stargazersCount)}',
      style: textStyle,
    );
  }
}

cf1e10b838bf60ee.png 確認内容

  • スターカウンタは、github Dart パッケージを使用して、リポジトリで獲得したスターの数について GitHub にクエリします。
  • パッケージとプラグインは pub.dev にあります。
  • 特定のプラットフォームのパッケージをブラウジング、検索することもできます。ランディング ページで [FLUTTER] を選択した場合は、次のページで [WEB] を選択します。これにより、ウェブで実行されるパッケージがすべて表示されます。パッケージのページをブラウジングするか、検索バーを使用して検索結果を絞り込みます。
  • Flutter コミュニティは、パッケージとプラグインを pub.dev に提供しています。github パッケージのページを見ると、ウェブを含め、ほとんどすべての Dart アプリや Flutter アプリで動作することがわかります。
  • Flutter Favorites」とマークされているパッケージが特に目に付くかもしれません。Flutter Favorites プログラムは、機能の完全性や適切なランタイム動作など、特定の条件を満たすパッケージを識別します。
  • 後ほど、pub.dev からこの例にプラグインを追加します。

b2f84ff91b0e1396.png 次の importmain.dart に追加します。

lib/main.dart

import 'star_counter.dart';

b2f84ff91b0e1396.png 新しい GitHubStarCounter ウィジェットを使用します。

main.dart で、Text ウィジェット(60~62 行目)を、GitHubStarCounterWidget を定義する 3 つの新しい行に置き換えます。

lib/main.dart

Padding(
  padding: const EdgeInsets.only(top: 32.0),
  child: GitHubStarCounter(              // New
    repositoryName: _repositoryName,     // New
  ),                                     // New
),

b2f84ff91b0e1396.png アプリを実行します。

IDE で実行ボタンを再度クリックする(最初にアプリは停止させない)か、IDE でホット リスタート ボタン 293160db29e53878.png をクリックするか、コンソールで「r」と入力することで、アプリをホット リスタートします。これにより、ブラウザを更新せずにアプリが更新されます。

ウィンドウは以前と同様です。既存のリポジトリを入力します(例: flutter/flutter)。スターの数がテキスト フィールドの下に表示されます。次に例を示します。

78a5f531b1acfd58.png

デバッグの実施準備ができたら、実行中のアプリで、存在しないリポジトリ(foo/bar など)を入力します。ウィジェットが「Loading...」と表示したまま停止します。これを今から修正します。

b2f84ff91b0e1396.png Dart DevTools を起動します。

Chrome DevTools に慣れていても、Flutter アプリをデバッグする場合は Dart DevTools を使用することをおすすめします。Dart DevTools は、Dart アプリと Flutter アプリをデバッグし、プロファイリングするように設計されています。ワークフローに応じて、Dart DevTools を起動する方法はいくつかあります。DevTools をインストールして起動する方法については、次のページをご覧ください。

b2f84ff91b0e1396.png デバッガを表示します。

Dart DevTools が起動したとき最初に表示されるブラウザページは、起動方法によって異なります。[Debugger] タブ 3d8c8053deda4caa.png をクリックして、デバッガを表示します。

b2f84ff91b0e1396.png star_counter.dart ソースコードを表示します。

[Libraries] テキスト フィールドで、左下に「star_counter」と入力し、結果リストから [package:star_counter/star_counter.dart] エントリをダブルクリックして [File] ビューで開きます。

b2f84ff91b0e1396.png ブレークポイントを設定します。

ソース内で var repo = await github.repositories という行を探します。52 行目にあるはずです。行番号の左側をクリックすると、ブレークポイントが設定されたことを示す円が表示されます。ブレークポイントは、左側の [Breakpoints] リストにも表示されます。右上の [Break on exceptions] チェックボックスをオンにします。UI は次のようになります。

eeec16d42e7012ba.png

b2f84ff91b0e1396.png アプリを実行します。

存在しないリポジトリを入力し、Enter キーを押します。コードペインの下のエラーペインに、github パッケージが「Repository Not Found」例外をスローしたことが表示されます。

Error: GitHub Error: Repository Not Found: /
    at Object.throw_ [as throw] (http://localhost:52956/dart_sdk.js:4463:11)
    at http://localhost:52956/packages/github/src/common/xplat_common.dart.lib.js:1351:25
    at github.GitHub.new.request (http://localhost:52956/packages/github/src/common/xplat_common.dart.lib.js:10679:13)
    at request.next (<anonymous>)
    at http://localhost:52956/dart_sdk.js:37175:33
    at _RootZone.runUnary (http://localhost:52956/dart_sdk.js:37029:58)
    at _FutureListener.thenAwait.handleValue (http://localhost:52956/dart_sdk.js:32116:29)
    at handleValueCallback (http://localhost:52956/dart_sdk.js:32663:49)
    at Function._propagateToListeners (http://localhost:52956/dart_sdk.js:32701:17)
    at _Future.new.[_completeWithValue] (http://localhost:52956/dart_sdk.js:32544:23)
    at async._AsyncCallbackEntry.new.callback (http://localhost:52956/dart_sdk.js:32566:35)
    at Object._microtaskLoop (http://localhost:52956/dart_sdk.js:37290:13)
    at _startMicrotaskLoop (http://localhost:52956/dart_sdk.js:37296:13)
    at http://localhost:52956/dart_sdk.js:32918:9

b2f84ff91b0e1396.png エラーを捕捉します。

star_counter.dart で、次のコードを探します(52~58 行目)。

if (widget.repositoryName.isNotEmpty) {
  var repo = await github.repositories
      .getRepository(RepositorySlug.full(widget.repositoryName));
  setState(() {
    repository = repo;
  });
}

このコードを try-catch ブロックを使用するコードに置き換え、エラーを捕捉してメッセージを出力すると、より適切に動作します。

lib/star_counter.dart

if (widget.repositoryName.isNotEmpty) {
  try {
    var repo = await github.repositories
        .getRepository(RepositorySlug.full(widget.repositoryName));
    setState(() {
      repository = repo;
    });
  } on RepositoryNotFound {
    setState(() {
      repository = null;
      errorMessage = '${widget.repositoryName} not found.';
    });
  }
}

b2f84ff91b0e1396.png アプリをホット リスタートします。

DevTools で、変更を反映するようにソースコードが更新されます。もう一度、存在しないリポジトリを入力します。次のように表示されます。

f1b3847ee101a85b.png

f5077295022a18df.png次のようなコンテンツができあがりました。

このステップでは、アプリにプライバシー ポリシーのページを追加します。まず、プライバシー ポリシーのテキストを Dart コードに埋め込みます。

b2f84ff91b0e1396.png lib/privacy_policy.dart ファイルを追加します。lib ディレクトリで、privacy_policy.dart ファイルをプロジェクトに追加します。

lib/privacy_policy.dart

import 'package:flutter/widgets.dart';
import 'package:flutter_markdown/flutter_markdown.dart';

class PrivacyPolicy extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Markdown(
      data: _privacyPolicyText,
    );
  }
}

// The source for this privacy policy was generated by
// https://app-privacy-policy-generator.firebaseapp.com/
var _privacyPolicyText = '''
## Privacy Policy

Flutter Example Company built the Star Counter app as an Open Source app. This SERVICE is provided by Flutter Example Company at no cost and is intended for use as is.

This page is used to inform visitors regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service.

If you choose to use our Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that we collect is used for providing and improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy.

The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Star Counter unless otherwise defined in this Privacy Policy.
''';

b2f84ff91b0e1396.png 次の importmain.dart に追加します。

lib/main.dart

import 'privacy_policy.dart';

b2f84ff91b0e1396.png プライバシー ポリシーの新しいルート(ページ)を追加します。

17 行目の後に、プライバシー ポリシー ページのルートを追加します。

lib/main.dart

routes: {
  '/': (context) => HomePage(),
  '/privacypolicy': (context) => PrivacyPolicy(),  // NEW
},

b2f84ff91b0e1396.png プライバシー ポリシーを表示するボタンを追加します。

_HomePageStatebuild() メソッドで、65 行目の後、Column の下部に TextButton を追加します。

lib/main.dart

TextButton(
  style: ButtonStyle(
    foregroundColor: MaterialStateProperty.all(Colors.blue),
    overlayColor: MaterialStateProperty.all(Colors.transparent),
  ),
  onPressed: () => Navigator.of(context).pushNamed('/privacypolicy'),
  child: Text('Privacy Policy'),
),

b2f84ff91b0e1396.png アプリを実行します。

アプリをホット リスタートすると、画面の下部に [Privacy Policy] リンクが表示されます。

ae990c7f6e0918e5.png

b2f84ff91b0e1396.png [Privacy Policy] ボタンをクリックします。

プライバシー ポリシーが表示され、URL が /privacypolicy に変更されます。

c233a1dea9abfaec.png

b2f84ff91b0e1396.png 戻ります。

ブラウザの「戻る」ボタンを使用して最初のページに戻ります。この動作は自由に利用できます。

ホストされたページのメリットは、アプリの新しいバージョンをリリースせずにページを変更できることです。

コマンドラインから、プロジェクトのルートで次の操作を行います。

b2f84ff91b0e1396.png Firebase CLI をインストールします

b2f84ff91b0e1396.png firebase login を使用して Firebase にログインし、認証を行います

b2f84ff91b0e1396.png firebase init. を使用して Firebase プロジェクトを初期化します

次の値を使用します。

  • Firebase の機能: Hosting
  • プロジェクトのセットアップ: Create a new project
  • プロジェクト名: [yourname]-my-flutter-app(一意である必要があります)
  • プロジェクトの名称: Enter キーを押してデフォルトを受け入れます(前の項目で使用したものと同じ名前です)。
  • パブリック ディレクトリ: build/web(重要)
  • シングルページ アプリとして構成: Yes
  • GitHub で自動ビルドとデプロイを設定: No

firebase init の実行が終了すると、コマンドラインに次のような内容が表示されます。

55135b9eda3c41ef.png

init コマンドが完了すると、次のファイルがプロジェクトに追加されます。

  • 構成ファイル firebase.json
  • .firebaserc(プロジェクト データを含む)

firebase.jsonpublic フィールドで build/web が指定されていることを確認します。次に例を示します。

firebase.json

{
  "hosting": {
    "public": "build/web",    # This is important!
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

b2f84ff91b0e1396.png アプリのリリース バージョンをビルドします。

次のいずれかの方法で、アプリのリリース バージョンをビルドするように IDE を構成します。

  • Android Studio または IntelliJ で、[Run] > [Edit Configuration] ダイアログの [Additional arguments] フィールドに --release を指定します。次に、アプリを実行します。
  • コマンドラインで flutter build web --release を実行します。

プロジェクトの build/web ディレクトリを調べて、このステップが機能していることを確認します。ディレクトリには、index.html を含め、複数のファイルが存在している必要があります。

b2f84ff91b0e1396.png アプリをデプロイします。

コマンドラインでプロジェクトの一番上から firebase deploy を実行して、パブリック build/web ディレクトリの内容をデプロイします。ホストされている URL、https://project-id>.web.app. が表示されます。

ブラウザで https://<project-id>.web.app または https://<project-id>.web.app/#/privacypolicy にアクセスし、プライバシー ポリシーの現在のバージョンを確認します。

次に、プライバシー ポリシーを Dart コードに埋め込むのではなく、Firebase を使用して HTML ページとしてホストします。

b2f84ff91b0e1396.png privacy_policy.dart. を削除します。

プロジェクトの lib ディレクトリからファイルを削除します。

b2f84ff91b0e1396.png main.dart. を更新します。

lib/main.dart で、import 文 import privacy_policy.dart と一番下にある PrivacyPolicyPage ウィジェットを削除します。

b2f84ff91b0e1396.png privacy_policy.html. を追加します。

このファイルをプロジェクトの web ディレクトリに配置します。

web/privacy_policy.html

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">

  <title>Privacy Policy</title>
</head>

<body>
<h2 id="privacy-policy">Privacy Policy</h2>
<p>Flutter Example Company built the Star Counter app as an Open Source app. This SERVICE is provided by Flutter Example Company at no cost and is intended for use as is.</p>
<p>This page is used to inform visitors regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service.</p>
<p>If you choose to use our Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that we collect is used for providing and improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy.</p>
<p>The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Star Counter unless otherwise defined in this Privacy Policy.</p>
</body>
</html>

次に、url_launcher プラグインを使用して、新しいタブでプライバシー ポリシーを開きます。

Flutter ウェブアプリはシングルページ アプリ(SPA)です。そのため、ウェブサンプルで標準のルーティング メカニズムを使用すると、同じウェブページでプライバシー ポリシーが開きます。一方、URL ランチャー プラグインは、ブラウザで新しいタブを開き、アプリの別のコピーを起動して、ホストされたページにアプリをルーティングします。

b2f84ff91b0e1396.png 依存関係を追加します。

pubspec.yaml ファイルに、次の依存関係を追加します(YAML ファイルでは空白が重要であるため、行の先頭に空白スペースを 2 つ入れるようにしてください)。

pubspec.yaml

  url_launcher: ^6.0.0

b2f84ff91b0e1396.png 新しい依存関係を取得します。

依存関係を追加するにはアプリを完全に再起動する必要があるため、アプリを停止します。IDE で [Pub get] ボタンをクリックするか、コマンドラインでプロジェクトの一番上から flutter pub get を実行します。

b2f84ff91b0e1396.png 次の importmain.dart に追加します。

lib/main.dart

import 'package:url_launcher/url_launcher.dart';

b2f84ff91b0e1396.png TextButton のハンドラを更新します。

main.dart で、ユーザーが [Privacy Policy] ボタンを押したときに呼び出されるコードも置き換えます。元のコード(71 行目)は、Flutter の通常のルーティング メカニズムを使用しています。

onPressed: () => Navigator.of(context).pushNamed('/privacypolicy'),

onPressed の新しいコードは、url_launcher パッケージを呼び出します。

lib/main.dart

onPressed: () => launch(
  '/privacy_policy.html',
  enableJavaScript: true,
  enableDomStorage: true,
),

b2f84ff91b0e1396.png アプリを実行します。

[Privacy Policy] ボタンをクリックして、ファイルを新しいタブで開きます。url_launcher パッケージを使用する主な利点は、(プライバシー ポリシー ページがホストされた後)ウェブとモバイル プラットフォームで動作することです。もう 1 つの利点は、アプリを再コンパイルせずに、ホストされたプライバシー ポリシー ページを変更できることです。

プロジェクトが完了したら、忘れずにクリーンアップします。

b2f84ff91b0e1396.png Firebase プロジェクトを削除します

これで、GitHub Star Counter アプリが完成しました。Dart DevTools も少し試してみました。このツールを使用すると、ウェブアプリだけでなく、すべての Dart アプリと Flutter アプリのデバッグやプロファイリングを行うことができます。

次のステップ

Flutter についてさらに学びましょう。