👨‍🔧

Flutter Webでシンプルモダンな管理画面を開発する方法

2023/08/29に公開
2
CHANGELOG

はじめに

Flutter で管理画面を開発するメリットは多いです。特に、Flutter 製のアプリケーションをすでにユーザーに提供している場合です。例えば、ユーザーに提供するアプリケーションと運営が利用する管理画面の開発言語を Dart に統一できるため、チームメンバーの学習コストがかかりません。また、モデルや汎用 Widgetなどの共通部分のソースコードが集約されるため、運用コストも低減できます。他にも Sandbox 環境として検証する目的や教育目的にも上手く利用できます。詳細は、以前 LT 会でお話したこともあるのでこちらをご参照ください。

https://speakerdeck.com/htsuruo/an-xin-sitexian-chang-dexue-xi-dekiruhuan-jing-tosite-flutter-web-noguan-li-saitogaosusumenahua

Flutter での管理画面(ダッシュボード)開発のトピックに関しては以前から数多あり、公式リポジトリの web_dashboard サンプルや、16 Open-source Free Flutter Dashboard and Admin Panel Templates などを参考にこれまでも効率的に管理画面を開発できました。ただ、リポジトリをご覧の通り更新が数年前で止まっているものが多く、自ずと書かれているコードも古くなってしまっていました。

今回は、Material 3や go_router などの最新に適用したシンプルな管理画面テンプレートを作ったのでその紹介と、管理画面に必要な機能を Flutter でどう開発できるか、を最新のアップデートに追従しながら記載します。

Flutter Admin Dashboard Template

こちらが、作成した管理画面テンプレートです。テンプレートリポジトリなので、Use this template でそのまま利用できます(手間だと思いつつ、スクリーンショットやダミーデータなど不要なファイルは適宜削除してもらう想定です)。

https://github.com/htsuruo/flutter-admin-dashboard-template

README にも記載がありますが、主な特徴は以下です。それぞれについて詳細を後述します。

ちなみに、リポジトリの Platform OS には web の他に macOS がありますが、これは Hot Reload が効かない Web デバッグでは開発効率が落ちてしまう実装(UI 部分など)を開発するのに利用しています。

元々、React でいう tremor のようなものを作ろうと思っていたのですが、あまり制約が強すぎるのも微妙に思ったので最低限の枠組みだけ用意し、レイアウト含めて残りは Material Design で自由に組んでもらう方針にしています(単純に tremor 相当のものを短期間で開発できる見込みもなかったというのも理由にあります)。

最近は React / Next.js を触る機会も増えてきたのですが、管理画面の開発然り Web に関するエコシステムは Flutter に比べて大きいので、まだ物足りなさを感じるのは正直な感想です(特に後述するチャート周り)。

宣言的ルーティング

一般に管理画面の要件定義をする際に、何らかの一覧画面と詳細画面を用意することが多いと思います。1つのユースケースとして、詳細画面のリンクをチーム内に共有し「このリンク先についてなのですが…」というコミュニケーションが発生することも比較的多いと思います。
Navigator 1.0では URL が動的に切り替わらないためパスの指定ができず、前述のユースケースを満たせません。というより、Web の一般的な利用シーンにおいて要件を満たせないことが多いはずです。Navigator 2.0では従来の push/pop による命令的なスタック管理ではなく、アプリケーションの状態に応じて宣言的なルーティングを実現できます。URL も画面ごとに切り替わるため、前述したユースケースを満たすことができます。

宣言的ルーティングを実装する際に、公式の go_router か支持者の多い auto_route が候補にあがることが多いと思いますが、ちょうど go_router_builderv.2.3.0StatefulShellRoute に対応しましたので、go_router を利用しました。
https://pub.dev/packages/go_router_builder

どの管理画面でもナビゲーション UI(レイアウト問わず)が搭載されていることが多いですが、これまでの go_router ではタブごとの状態を管理できなかったため、タブを切り替える度にそれまでの操作はリセットされてしまっていました。
StatefulShellRoute を利用すればタブの状態を保持できるので、タブ切り替え後もそれまでの操作はそのまま継続できます。簡単な Playground もありますので、参考までにご参照ください。

https://github.com/htsuruo/go-router-playground

レスポンシブ対応

responsive_framework を利用しました。
https://pub.dev/packages/responsive_framework

最初は LayoutBuilder を使って以下のコードのようにミニマムに実装していましたが、ブレークポイント増やしたり調整したりしているうちに、結局パッケージを利用した方が楽そうで切り替えました。


Widget build(BuildContext context) {
  return LayoutBuilder(builder: (context, constraints) {
    if (constraints.maxWidth < 450) {
      return ScaffoldWithNavigationBar(
        body: navigationShell,
        selectedIndex: navigationShell.currentIndex,
        onDestinationSelected: _goBranch,
      );
    } else {
      return ScaffoldWithNavigationRail(
        body: navigationShell,
        selectedIndex: navigationShell.currentIndex,
        onDestinationSelected: _goBranch,
      );
    }
  });
}

Adaptive Scaffold

flutter_adaptive_scaffold も選択肢ではありましたが、スマートフォンなどの端末サイズが小さい場合はボトムナビゲーションバーではなくドロワーで表示したかったため、今回は見送りました。あまり深く検討はしていないのでこちらでも実現可能だったかもしれません。
https://pub.dev/packages/flutter_adaptive_scaffold

There is a Material 3 version of this component, NavigationDrawer, that's preferred for applications that are configured for Material 3 (see ThemeData.useMaterial3).

また、Material 3では NavigationDrawer を利用して下部に配置するべきだと思うのですが、管理画面やダッシュボード系のアプリケーションではコンテンツが肥大化するはずなので、タブ表現ではそのうち頭打ちになると思いここは臨機応変にドロワーを選択しました。

ダークモード対応

普段は Riverpod を利用する機会が多いので自前実装していますが、今回のテンプレートでは状態管理パッケージは含めずに構成したかったため、adaptive_theme を採用しました。使い方も非常に簡単で、内部実装も外部の状態管理パッケージに依存せず findAncestorStateOfTypeof を生やしています。
https://pub.dev/packages/adaptive_theme

管理画面でよく使われる機能と実装アプローチ

テンプレートとは別に、管理画面を作る際に一般的に必要になりそうな機能と Flutter での実装アプローチを記載します。

Widgetを選択可能にする

Flutter ではデフォルトでテキストの選択(コピーなど)はできません。
管理画面での利用では、ユーザーID や詳細情報をクリップボードにコピーしたいケースは多いと思います。Clipboard などを使ってクリップボードコピーの実装を入れても良いですが、未実装 Widgetはコピー不可となり使い勝手が悪いです。
Flutter 3.3から利用できるようになった SelectionArea を利用すると、ツリー配下の全ての Widgetを選択可能にできます。
https://api.flutter.dev/flutter/material/SelectionArea-class.html

以前から利用できた SelectableText もありますが、こちらは Text Widgetのみへの適用となり、前述の SelectionArea と比べて使い勝手が良くないのでほぼ使う機会はありません。

例えば、以下のように MaterialApp 直下を SelectionArea で包むと、アプリケーションの全てのテキストを選択可能にできます。


Widget build(BuildContext context) {
  return MaterialApp(
    theme: ThemeData.light(useMaterial3: true),
    home: const SelectionArea(
      child: HomePage(),
    ),
  );
}

基本的にはこのような方法で十分だと思いますが、標準のボタン系 Widgetではなく GestureDetector などで独自にカーソルをポインターに指定したい場合は、そのままでは SelectionArea の影響で MouseRegion が効かないので SelectionContainer.disabled コンストラクタで指定した箇所を SelectionRegion の対象外にして制御する必要があります。

// この配下のWidgetツリーをSelectionRegionの対象外とする
SelectionContainer.disabled(
  child: MouseRegion(
    // カーソルがポインター表示となる
    cursor: SystemMouseCursors.click,
    child: GestureDetector(
        onTap: () {},
        child: Text('foo'),
      ),
    ),
);

上記の実装の様子が以下の gif です。Text Widgetを選択可能にし、ポインターが表示されている様子がわかります。

2次元テーブル

つい先日(2023年8月)に公開された、two_dimensional_scrollables で実現できます。

https://pub.dev/packages/two_dimensional_scrollables

触っていただくのが早いですが、縦横にスクロールができかつヘッダー固定も柔軟に指定できて、便利なパッケージです。

https://twitter.com/h_tsuruo/status/1693635947554861552

特に管理画面は扱う情報量が多くなりがちですので、この機能はほぼ必須レベルだったのかなと思います。これまでも自作するなどで実現できましたが、公式からパッケージとして提供されるのは安心ですね。セル内のクリック判定もできますので、テーブル表示からそのまま詳細画面に遷移させるなどの設計も可能です。

構造もシンプルで TableView.builder で利用できます。example も置いておきます。
https://github.com/flutter/packages/blob/78ef1d2e1028db62c2fa4462401ddb7a9c9e0bcf/packages/two_dimensional_scrollables/example/lib/main.dart#L55

TableView.builder(
  columnCount: 20,  // 横アイテムの個数
  rowCount: 50, // 縦アイテムの個数
  pinnedColumnCount: 1, // 固定するヘッダーの個数
  columnBuilder: (index) {}
  rowBuilder: (index) {}
  cellBuilder: (index) {}
)

CSVダウンロード

テーブル表示したコンテンツをそのまま CSV ダウンロードしたいケースも多いと思います。universal_ioAnchorElement を使って簡単にエクスポートが可能です。詳細はこちらの記事をご参照ください。
https://zenn.dev/tsuruo/articles/0e5f2f3696148f

チャート系

ダッシュボード表示の要件といえば必須となるのがチャート表示だと思います。Pinterest の管理画面 UI などを参考に見ても、一目で情報を見やすくまとめるアプローチとしてチャートを利用するのは自然です。

ただ、個人的にはチャートが管理画面開発の大きな Missing Piece だと思っていて、Chart.js に代表されるように、正直 JavaScript や Python の方がチャートの表現力や使い勝手は良いと思っています。
現状、Flutter で利用できる優れたチャートパッケージも何点かありますが、どれも一長一短なのでケースバイケースで選択する形になると思います。その中でも fl_chart や syncfusion_flutter_charts などは人気で比較的よく使われている印象です。

詳細については、こちらの記事にまとめておりますのでご参照ください。
https://zenn.dev/tsuruo/articles/a8fc96ff5aa43a

まとめ

本記事では、Material 3や go_router などの最新に適用したシンプルな管理画面テンプレートの紹介と、管理画面に必要な機能の開発方法について記載しました。
私が昔に管理画面を開発した際は、確か NavigationRail もまだ存在せず、Stack で積み上げてレイアウトを作っていました。数々のアップデートでかなり効率的にかつ Material 3ベースで開発できるようになったのだと嬉しくなり、「今だったらこう作るかな」という観点で開発したのが、前半にご紹介したテンプレートでした。他にも、管理画面やダッシュボードでこんな機能がテンプレートとして備わっているべきでは、などフィードバックございましたらぜひお申し付けください。

参考

Discussion

Tomohiko TanihataTomohiko Tanihata

enechain のチャートライブラリに触れていただきありがとうございます!!

国内企業の enechain 社 が開発している flutter_echarts などもありますが

名前がややこしくて大変申し訳ないのですが、正しくは echart_flutter になります!

ツルオカツルオカ

ご指摘ありがとうございます。大変失礼しました🙏
コピペが誤っており先程修正しました。
echart_flutterはまだ触ったことはないのですが、線形チャートの描画であれば高い候補に上がる気がしています。近年のFlutterにおけるチャート事情として別途まとめようとも思っているので、その際にまたフィードバックいただけると嬉しいです。