はじめに
よく使うWidgetのテンプレートがあると便利ですよね?
自分は以前、以下の記事で主にスナックバー、ダイアログ、ボトムシートのテンプレートをご紹介しました。
ただし、このテンプレートの問題点はプロジェクトごとにいちいちコードをコピペする必要がある点です。
ちゃんとしたプロジェクトであればこのテンプレートをコピペしてさらにカスタマイズして、といったことは必要だと思います。
しかし、何かしらのパッケージを試すためのサンプルを作りたいなどの場合には少々面倒です。
そこで、自分がよく使う上記のWidgetをパッケージ化して簡単に呼び出せるようにしてみたのでご紹介します。
pub.devなどに公開する用途は対象外ですのでご了承ください。
記事の対象者
- 自分のお気に入りWidgetをパッケージ化してみたい方
- GitHubがある程度使える方
- 個人用または社内用のパッケージを作りたい方
記事を執筆時点での筆者の環境
[✓] Flutter (Channel stable, 3.24.1, on macOS 15.0.1 24A348 darwin-arm64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.0)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.3)
[✓] VS Code (version 1.94.0)
サンプルプロジェクト
ソースコード
パッケージの作成手順
基本的には以下の流れで進んでいきます。
- パッケージプロジェクト作成
- 作成内容を確認するための実行環境を用意
- ソースコード記入
- 動作の確認
- READMEやCHANGELOGを編集
- 完成したコミットにタグをつける
- GitHub経由で読み込む場合
1. パッケージプロジェクト作成
ターミナルで作成したいディレクトリに移動し、以下のコマンドで作成します。
flutter create --template=package パッケージ名
すると自動で雛形を作成してくれます!
開いてみると以下のようなディレクトリ構造になっています。
ソースコードはlibディレクトリ内に記述していきます。
ひとまず雛形にはサンプルのクラスが入っています。
2. 作成内容を確認するための実行環境を用意
実行環境は以下の2通りがあります。
- 専用のテストプロジェクトを別に作ってパッケージをローカルで読み込む
- パッケージ内に確認用のプロジェクトを作成する
2-1. 専用のテストプロジェクトを別に作ってパッケージをローカルで読み込む場合
まず、パッケージのpathが必要なので、パッケージのコマンドラインにてpwdコマンドで現在のパスを取得します。
$ pwd
/Users/hogetaro/test_directory/test_hoge
次に適当なプロジェクトを作成したらpubspec.yamlに以下のようにパッケージ名とpathを記載し保存します。
dependencies:
flutter:
sdk: flutter
test_hoge:
path: /Users/motokawaharuhiko/Flutter_Practice/test_hoge
すると以下のように呼び出すことができるようになります。
final cal = Calculator();
cal.addOne(1);
パッケージのソースコードを変更したら再度flutter pub get
が必要です。
こちらの方法は動作確認以外でもローカルにソースコードを置いて使用する場合などにも有効です。
2-2. パッケージ内に確認用のプロジェクトを作成する場合
今回のutility_widgetsはこちらのパターンで作成しました。
まず、パッケージのルート直下にexampleディレクトリを作成します。
ターミナルでexampleディレクトリに移動し、プロジェクト作成コマンドを実行します。
~/test_directory/utility_widgets [main]
$ cd example/
~/test_directory/utility_widgets/example [main]
$ flutter create -e .
するとプロジェクトが出来上がるので、あとはpubspec.yamlに以下のように書いて依存関係を結びます。
dependencies:
flutter:
sdk: flutter
utility_widgets:
path: ../
pathが../
でいいので少し楽なのと、わざわざ別プロジェクトを開かないでいいので、私はこちらがおすすめです。
3. ソースコード記入
3-1. エントリーポイントとディレクトリ構造
パッケージのlib直下にて配置されたパッケージ名.dart
に全てのソースコードを書いていってもいいのですが、コード量が増えると可読性が悪い上に保守しにくくなりす。
このパッケージ名.dart
がパッケージへのエントリーポイントですので、その部分に専念させるのが良さそうです。
世に公開せれているパッケージを参考に以下のような構成にしてみました。
パッケージ名.dart
にはエントリーポイントの宣言であるlibrary utility_widgets;
を記載しています。
その下にはexport
キーワードに続けてソースコードを記載しています。
それぞれのソースコードはsrcディレクトリの配下に置いています。
こうすることで機能やwidget単位で管理しやすくなっています。
tips
export
を忘れると外部から参照出来ないので確認しましょう。
export
に記載するのはパスなので、今回で行けばsrc/hoge.dart
なので注意です。
3-2. 実装
実装は普段のコーディング同様です。
例えば、独自のスナックバーを定義したlib/src/custom_snack_bar.dart
は以下のようになっています。
import 'package:flutter/material.dart';
/// カスタマイズしたスナックバーを表示する
///
/// [message]はスナックバーに表示したいメッセージ
/// [duration]はスナックバーを表示する時間(デフォルトは2秒で設定)
void showCustomSnackbar(
BuildContext context,
String message, {
int duration = 2,
}) {
// スナックバーの設定
final snackbar = SnackBar(
content: Text(
message,
style: Theme.of(context).textTheme.bodyMedium,
),
duration: Duration(seconds: duration),
// スナックバーを浮かせる設定
behavior: SnackBarBehavior.floating,
// スナックバーの内部のテキストとスナックバーの外側のパディング
padding:
const EdgeInsetsDirectional.symmetric(horizontal: 14, vertical: 16),
// スナックバーと画面とのマージン
margin: const EdgeInsets.fromLTRB(20, 0, 20, 50),
// 角丸の設定
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
backgroundColor: Colors.grey);
// ScaffoldMessengerを使用してスナックバーを表示
ScaffoldMessenger.of(context).showSnackBar(snackbar);
}
ダイアログやボトムシートの実装はソースコードをご覧ください。
4. 動作の確認
今回の場合はexampleディレクトリ内のプロジェクトで行いました。
main.dart
に通常のUI構築と同様にコーディングしています。
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:utility_widgets/utility_widgets.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
child: const Text('スナックバーを表示'),
// カスタマイズされたスナックバーはグローバル関数なので、以下のように呼び出す
onPressed: () => showCustomSnackbar(context, 'スナックバーを表示しました'),
),
const SizedBox(height: 40),
ElevatedButton(
child: const Text('ボトムシートを表示'),
onPressed: () async => onTapBottomSheetButton(context),
),
const SizedBox(height: 40),
ElevatedButton(
child: const Text('ダイアログを表示'),
onPressed: () async => onTapDialogButton(context),
)
],
),
),
);
}
}
extension on HomeScreen {
Future<void> onTapBottomSheetButton(BuildContext context) async {
// [ActionBottomSheet]の[show]メソッドを使ってボトムシートを表示
final result = await ActionBottomSheet.show<int>(
context,
actions: [
// [ActionItem]はアイコン、テキスト、戻り値を持つ
// タップした場合の挙動はボトムシートを閉じるのと同時にreturnValueに設定した値を返す
const ActionItem(
icon: Icons.add,
text: '追加',
returnValue: 0,
),
const ActionItem(
icon: Icons.edit,
text: '編集',
returnValue: 1,
),
const ActionItem(
icon: Icons.delete,
text: '削除',
returnValue: 2,
),
// ボトムシート内はListViewになっているのでいるので、スクロールできるため
// いくつでも追加できる
],
);
// 基本は以下のように受け取った値によって処理を分岐する書くことを前提にしている
if (!context.mounted) return;
switch (result) {
case 0:
showCustomSnackbar(context, '追加しました');
case 1:
showCustomSnackbar(context, '編集しました');
case 2:
showCustomSnackbar(context, '削除しました');
// nullもあり得るので、その場合も考慮する
case _:
// 何もしない
return;
}
}
Future<void> onTapDialogButton(BuildContext context) async {
// [PlatformDialog]の[show]メソッドを使ってダイアログを表示
// [show]メソッドの引数には[AdaptiveAction]のリストを渡す
final result = await PlatformDialog.show<int>(
context: context,
title: const Text('タイトル'),
content: const Text('内容'),
actionsBuilder: (context) => [
// [AdaptiveAction]はテキスト、戻り値を持つ
const AdaptiveAction(text: Text('キャンセル'), returnValue: 0),
const AdaptiveAction(text: Text('OK'), returnValue: 1),
],
);
// 基本は以下のように受け取った値によって処理を分岐する書くことを前提にしている
if (!context.mounted) return;
switch (result) {
case 0:
showCustomSnackbar(context, 'キャンセルしました');
case 1:
showCustomSnackbar(context, 'OKしました');
// nullもあり得るので、その場合も考慮する
case _:
// 何もしない
return;
}
}
}
5. READMEやCHANGELOGを編集
ここは必要に応じて編集しましょう。
個人利用の目的であればそこまで必要しっかりやらなくてもいいかもしれません。
READMEはパッケージの概要と簡単な使い方を記載しましょう。
バージョン管理している場合はCHANGELOGにそのバージョンの変更点を記載するといいでしょう。
6. 完成したコミットにタグをつける
こちらもバージョン管理していなければいいのですが、後々管理が大変になるのでつけておいた方がいいでしょう。
パッケージの実装が完了した場合に最後のコミットの時点でターミナルまたはVSCodeのGUIで操作しましょう。
ターミナルの場合のコマンドは以下のとおりです。
git tag v1.0.0
そしてGithubにもプッシュしておきます。
git push origin v1.0.0
7. GitHub経由で読み込む場合
自分のプロジェクトで使用するが、ローカルにソースを置いておきたくない場合はGitHub経由で読みこむことが可能です。
GitHub上のCodeボタンをタップしてリポジトリのURLをコピーしておきます。
あとは使用したい該当プロジェクトのpubspec.yaml
に以下のように書いて保存、またはflutter pub get
を実行することでパッケージを使用することができます。
dependencies:
utility_widgets:
git:
url: https://github.com/HaruhikoMotokawa/utility_widgets.git
ref: v1.0.0
urlに先ほコピーしたリポジトリのurlを貼り付けます。
refにはタグ付けしたタグ名を記載します。
今回はv1.0.0としていますが、バージョン1とか1.0.0などでも大丈夫です。
終わりに
今回の記事では、よく使うWidgetをパッケージ化する手順を解説しました。
この方法を使えば、サンプル作成の際に時間を大幅に短縮できるでしょう。
今回はスナックバー、ダイアログ、ボトムシートの3つのWidgetに焦点を当てました。
これに加えてさらに種類を増やしたり、よく使う便利メソッドを追加したりすることで、より汎用性の高いパッケージに発展させることもできます。
パッケージの作成は難しいと感じるかもしれませんが、今回の手順を参考にすれば、誰でも簡単に実現できます。
ぜひ自分のお気に入りのWidgetをパッケージ化して、開発効率を高めてみてください!
最後まで読んでいただき、ありがとうございました!