こんにちは。広瀬マサルです。
これまでのパッケージをまとめて統合Flutterフレームワークを作成しました。
コンセプトは
自動生成を用いて安全かつ高速に高品質のアプリを開発可能にするフレームワーク
です。
使い方をまとめたので興味ある方はぜひ使ってみてください!
masamune
はじめに
まずはこちらを御覧ください。
※動画のサンプルコードはこちらに公開しています。
こちらはメモ帳アプリを全くの空の状態からわずか10分以内で完成させる動画です。
このMasamuneフレームワーク
を利用することでアプリ開発で行うコーディングの大半を削減することが可能になります。
このフレームワークは下記の機能を中心としています。
- CLI(コマンドラインインターフェース)ツールによるコードテンプレートの生成
- build_runner による追加コードの自動生成
つまりコードの大半を機械的に生成させることにより人間がコーディングする部分を極力減らしてくれるということです。
また、人間がコーディングする部分もほぼタイプセーフとなるためIDEのサジェスト機能などのサポートを受けながら迷うことなく実装を行うことが可能です。
このような機能が提供されることにより下記のベネフィットを享受可能です。
-
実装が早くなる
- 実際にコーディングする箇所が減るので実装が早くなります。
-
コーディングミスが少なくなる
- 実際にコーディングする箇所が減るのでミスする確率もその分減ります。
-
ソースコードを追いやすくなる
- 実際にコーディングする箇所が減るので時間を置いて後からソースを見ても追いやすいです。
-
人によってコードの差がでにくい
- 生成されたテンプレートに沿ってコーディングを行うため、人によってのコードの違いがでにくいです。
-
チーム開発を行いやすくなる
-
人によってコードの差がでにくい
ためチーム内でのコード確認や人員の割当もやりやすくなります。
-
さらにこのフレームワークは下記の機能を提供し、アプリ開発を多方面からサポートします。
-
ルーティング機能
- WebのURL対応や条件付きリダイレクト、ネストナビゲーションも可能。
-
データベース機能
- Firestoreの構造をベースにしたNoSQLデータベース。
- アダプターを切り替えることで簡単にローカルとFirestoreのスイッチが可能。
-
状態管理機能
- flutter_hooksのようなシンプルな形で状態管理を行います。
-
翻訳機能
- Googleスプレッドシートを利用した翻訳管理。
-
テーマ管理機能
- テーマの色やテキストを定義することができます。
- 画像ファイルやフォントファイルをコード内でタイプセーフに取り出すことが可能です。
-
SharedPreferences機能
- 上記のデータベースとは別にSharedPreferencesを利用したデータストアが可能です。
-
フォーム構築機能
- フォームを中心としたユーザーからのデータ入力をUIレベルからサポートします。
-
UIサポート
- データの更新があったときに負荷が少なくウィジェットを更新可能なリストウィジェットや簡易モーダルを実装可能な機能を提供します。
-
Firebase/Firestoreサポート
-
Authentication
やCloud Firestore
、Cloud Storage
、Firebase DataConnect
などFirebaseの機能に簡単にスイッチできる機能を提供します。
-
このフレームワークを利用することで例えば簡単なCRUDアプリの場合実装する部分が下記のみになります。
-
DataScheme
- 型と変数名(場合によっては初期値)のみを定義すればよい。
-
View
-
ウィジェットによるアプリデザインの構築を行います。
- フォームなどいくつかの要素についてはサポートされます。
- データバインディングなどは簡単に行うことが可能です。
-
ウィジェットによるアプリデザインの構築を行います。
インストール
下記コマンドでCLI
をインストールします。
flutter pub global activate katana_cli
既存のプロジェクトにMasamune Frameworkを導入する場合は下記のコマンドでパッケージを追加してください。
build_runner や freezed、json_serializable も合わせてインストールする必要があります。
flutter pub add masamune
flutter pub add json_annotation
flutter pub add freezed_annotation
flutter pub add --dev build_runner
flutter pub add --dev masamune_builder
flutter pub add --dev json_serializable
flutter pub add --dev freezed
プロジェクトの作成
プロジェクトを作成したフォルダ内で下記コマンドを実行します。
Application ID
にはリバースドメイン(com.test.myapplication)でIDを記述してください。
katana create [Application ID(e.g. com.test.myapplication)]
基本的にはflutter create
と同じになりますが下記が自動で変更されます。
-
自動で
assets
フォルダ以下に画像ファイルを置けるようにしています。 -
katana.yaml
が置かれます。 - 必要パッケージが自動でインストールされます。
- VSCode用のランチャー設定が自動でセットされます。
-
main.dart
が書き換わり、build_runner でコード生成が行われます。
コード変更の監視
build_runnerの監視機能を用いて対象コードの変更を検知し変更があれば即コードを自動生成します。
build_runner自体のコード解析が非常に遅いため常時監視していた方が開発者体験がとても良くなります。
コードの監視を行う場合は別ターミナルで下記のコマンドを実行します。
katana code watch
コマンドを打つと監視状態に入るのでそのまま放置します。
(そのままでは後述のコードテンプレート作成コマンドが打てなくなるので別ターミナルで起動してください)
コード監視を行わない場合は適宜下記コマンドを入力してください。
katana code generate
実装
ページ
ページの作成
アプリの画面(ページ)を作るには下記のコマンドを実行します。
katana code page [Page name]
lib/pages
以下に(Page name).dart
という名前でファイルが作成されます。
(Page name)Page
というクラスが下記のように作成されます。
StatelessWidgetやStatefulWidgetなどと同じ様にbuild
の内部に画面のUIの内容を記述してください。
// test.dart
// ignore: unused_import, unnecessary_import
import 'package:flutter/material.dart';
// ignore: unused_import, unnecessary_import
import 'package:masamune/masamune.dart';
// ignore: unused_import, unnecessary_import
import 'package:masamune_universal_ui/masamune_universal_ui.dart';
// ignore: unused_import, unnecessary_import
import '/main.dart';
part 'test.page.dart';
@immutable
// TODO: Set the path for the page.
@PagePath("test")
class TestPage extends PageScopedWidget {
const TestPage({
super.key,
// TODO: Set parameters for the page.
});
// TODO: Set parameters for the page in the form [final String xxx].
/// Used to transition to the TestPage screen.
///
/// ```dart
/// router.push(TestPage.query(parameters)); // Push page to TestPage.
/// router.replace(TestPage.query(parameters)); // Replace page to TestPage.
/// ```
@pageRouteQuery
static const query = _$TestPageQuery();
@override
Widget build(BuildContext context, PageRef ref) {
// Describes the process of loading
// and defining variables required for the page.
// TODO: Implement the variable loading process.
// Describes the structure of the page.
// TODO: Implement the view.
return UniversalScaffold();
}
}
@PagePath(””)
内のパスを指定することでディープリンクのパスを指定できます。
@PagePath("user/:user_id")
ページに引数が必要な場合は、単純に下記のようにパラメーターを追加してください。
const TestPage({
super.key,
// TODO: Set parameters for the page.
required this.name,
this.text,
});
// TODO: Set parameters for the page in the form [final String xxx].
final String name;
final String? text;
~~~~~~~~~~~~~~~~~~
ページの遷移
main.dartに定義されているrouter
を利用してページ遷移を行います。
作成されたクラス中にすでに記述されているquery
を指定してそのページに遷移することができます。
// TestPageへの遷移
router.push(TestPage.query());
// ページの置き換え
router.replace(TestPage.query());
// 前のページに戻る
router.pop();
初期ページの設定
main.dartに定義されているinitialQuery
に初期ページに設定したいqueryを渡すことでアプリ立ち上げ時のページを設定できます。
/// Initial page query.
// TODO: Define the initial page query of the application.
final initialQuery = TestPage.query();
その他、下記の機能について詳しく知りたいときはパッケージの詳細ページをご覧ください。
- AppRouterの指定方法
- ディープリンクへの対応
- ネストナビゲーション
データモデル
データ構造
MasamuneフレームワークではFirestoreのデータ構造を参考にしてデータを保存することが出来ます。
コレクション(ドキュメント)モデル作成
コレクションモデルを作成するためには下記のコマンドを入力します。
katana code collection [Collection name]
lib/models
以下に(Collection name).dart
という名前でファイルが作成されます。
(Collection name)Model
というクラスが下記のように作成されます。
上記コマンドでコレクションモデルを作成した場合、同一のデータスキームでドキュメントモデルも利用可能になります。
// test.dart
// ignore: unused_import, unnecessary_import
import 'package:flutter/material.dart';
// ignore: unused_import, unnecessary_import
import 'package:masamune/masamune.dart';
// ignore: unused_import, unnecessary_import
import '/main.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'test.m.dart';
part 'test.g.dart';
part 'test.freezed.dart';
/// Value for model.
@freezed
@formValue
@immutable
// TODO: Set the path for the collection.
@CollectionModelPath("test")
class TestModel with _$TestModel {
const factory TestModel({
// TODO: Set the data schema.
}) = _TestModel;
const TestModel._();
factory TestModel.fromJson(Map<String, Object?> json) => _$TestModelFromJson(json);
/// Query for document.
///
/// ```dart
/// appRef.model(TestModel.document(id)); // Get the document.
/// ref.app.model(TestModel.document(id))..load(); // Load the document.
/// ```
static const document = _$TestModelDocumentQuery();
/// Query for collection.
///
/// ```dart
/// appRef.model(TestModel.collection()); // Get the collection.
/// ref.app.model(TestModel.collection())..load(); // Load the collection.
/// ref.app.model(
/// TestModel.collection().data.equal(
/// "data",
/// )
/// )..load(); // Load the collection with filter.
/// ```
static const collection = _$TestModelCollectionQuery();
/// Query for form value.
///
/// ```dart
/// ref.app.form(TestModel.form(LogModel())); // Get the form controller in app scope.
/// ref.page.form(TestModel.form(LogModel())); // Get the form controller in page scope.
/// ```
static const form = _$TestModelFormQuery();
}
/// [Enum] of the name of the value defined in TestModel.
typedef TestModelKeys = _$TestModelKeys;
/// Alias for ModelRef<TestModel>.
///
/// When defining parameters for other Models, you can define them as follows
///
/// ```dart
/// @RefParam(TestModelDocument) TestModelRef test
/// ```
typedef TestModelRef = ModelRef<TestModel>?;
/// It can be defined as an empty ModelRef<TestModel>.
///
/// ```dart
/// TestModelRefPath("xxx") // Define as a path.
/// ```
typedef TestModelRefPath = _$TestModelRefPath;
/// Class for defining initial values to be passed to `initialValue` of [RuntimeModelAdapter].
///
/// ```dart
/// RuntimeModelAdapter(
/// initialValue: [
/// TestModelInitialCollection(
/// "xxx": TestModel(...),
/// ),
/// ],
/// );
/// ```
typedef TestModelInitialCollection = _$TestModelInitialCollection;
/// Document class for storing TestModel.
typedef TestModelDocument = _$TestModelDocument;
/// Collection class for storing TestModel.
typedef TestModelCollection = _$TestModelCollection;
/// It can be defined as an empty ModelRef<TestModel>.
///
/// ```dart
/// TestModelMirrorRefPath("xxx") // Define as a path.
/// ```
typedef TestModelMirrorRefPath = _$TestModelMirrorRefPath;
/// Class for defining initial values to be passed to `initialValue` of [RuntimeModelAdapter].
///
/// ```dart
/// RuntimeModelAdapter(
/// initialValue: [
/// TestModelMirrorInitialCollection(
/// "xxx": TestModel(...),
/// ),
/// ],
/// );
/// ```
typedef TestModelMirrorInitialCollection = _$TestModelMirrorInitialCollection;
/// Document class for storing TestModel.
typedef TestModelMirrorDocument = _$TestModelMirrorDocument;
/// Collection class for storing TestModel.
typedef TestModelMirrorCollection = _$TestModelMirrorCollection;
コレクションが必要なくドキュメントモデルのみで良い場合は下記コマンドで作成可能です。
katana code document [Document name]
@CollectionModelPath("")
(@DocumentModelPath(””)
)の中身を指定してデータパスを指定します。
Firestoreと同じ様にパス階層の制限があります。
(奇数:コレクション、偶数:ドキュメント)
@CollectionModelPath("user")
また、factory
コンストラクタの中にデータスキームとなる変数の一覧を記載します。
これがこのコレクション(ドキュメント)で取り扱えるデータの内容となります。
const factory TestModel({
// TODO: Set the data schema.
required String name,
String? test,
}) = _TestModel;
モデルの利用
作成されたモデルは、ページ内もしくはScopedWidget
やScoped
で作成されたウィジェット内の場合build
メソッド内で渡されるPageRef(WidgetRef)
からref.app.model
を使用することで取り扱うことが可能です。
ref.app.model
の中に作成されたモデル内で定義されているcollection
やdocument
を渡すことで実態のオブジェクトを取得することができます。
また、取得したオブジェクトのload
メソッドを実行することでデータベースからデータをロードすることができます。
ロードが完了したときやデータを書き換えた場合、ref.app.model
を実行したウィジェットがリビルドされます。
@override
Widget build(BuildContext context, PageRef ref) {
// Describes the process of loading
// and defining variables required for the page.
// TODO: Implement the variable loading process.
final testModelCollection = ref.app.model(TestModel.collection()); // TestModelのコレクションを取得。
testModelCollection.load(); // モデルデータを読み込み
~~~~~~~~~
}
また、ページやウィジェット外で利用したい場合はmain.dartで定義されているappRef
で利用することが出来ます。
final testModelCollection = appRef.model(TestModel.collection());
SharedPreferences機能
SharedPreferencesのように通常のDBとは別にローカルにデータを保存したい場合、ModelAdapter
を用いることで可能です。
詳しくは下記をご参照ください。
Googleスプレッドシートのデータソースとしての利用
GoogleスプレッドシートをCSVデータソースとして利用可能にします。
非エンジニアの方が触れるのでデータの設定等を顧客にお願いする場合などにお使いください。
事前にGoogleスプレッドシートを利用可能にします。
-
こちらのテンプレートからスプレッドシートを自分のGoogleドライブにコピーします。
-
CollectionModelPath
を利用する場合はコレクション用のシートを利用し、DocumentModelPath
を利用する場合はドキュメント用のシートを利用します。 -
ファイル
->コピーの作成
からコピーが可能です。
-
-
コピーしたスプレッドシート内で
ファイル
->共有
->他のユーザーと共有
をクリックします。 -
(作成したスプレッドシート名)を共有
ウィンドウ内で、一般的なアクセス
をリンクを知っている全員
に変更します。 -
作成したクラスに追加する形で「
GoogleSpreadSheetDataSource
」のアノテーションを追加し、ブラウザで表示されているURL(例:https://docs.google.com/spreadsheets/d/1bfNX8clPH9PFOcfFIStNCGNGjeCKwGv-24iChSJn8yM/edit#gid=0
)をコピーし記載します。/// Value for model. @freezed @formValue @immutable @GoogleSpreadSheetDataSource( "https://docs.google.com/spreadsheets/d/1bfNX8clPH9PFOcfFIStNCGNGjeCKwGv-24iChSJn8yM/edit#gid=0", version: 1, ) // TODO: Set the path for the collection. @CollectionModelPath("test") class TestModel with _$TestModel { const factory TestModel({ // TODO: Set the data schema. }) = _TestModel; const TestModel._();
その他、下記の機能について詳しく知りたいときはパッケージの詳細ページをご覧ください。
- データの編集・削除
- データのフィルタークエリーの指定
- コレクションのソート機能
- テキスト検索
- リレーショナルデータの指定・取得
- トランザクション・バッチ処理
- 特殊なフィールド値
コントローラー
ScrollController
やTextEditingController
などFlutterですでに用意されているコントローラーをページやウィジェット内で利用したい場合や、データモデルを束ねて使うなど細かい調整を行いたい場合はコントローラーを作成します。
katana code controller [Controller name]
lib/controllers
以下に(Page name).dart
という名前でファイルが作成されます。
(Page name)Controller
というクラスが下記のように作成されます。
// test.dart
// ignore: unused_import, unnecessary_import
import 'package:flutter/material.dart';
// ignore: unused_import, unnecessary_import
import 'package:masamune/masamune.dart';
// ignore: unused_import, unnecessary_import
import '/main.dart';
part 'test.m.dart';
/// Controller.
@Controller(autoDisposeWhenUnreferenced: false)
class TestController extends ChangeNotifier {
TestController(
// TODO: Define some arguments.
);
// TODO: Define fields and processes.
/// Query for TestController.
///
/// ```dart
/// appRef.controller(TestController.query(parameters)); // Get from application scope.
/// ref.app.controller(TestController.query(parameters)); // Watch at application scope.
/// ref.page.controller(TestController.query(parameters)); // Watch at page scope.
/// ```
static const query = _$TestControllerQuery();
}
利用する場合はPageRef(WidgetRef)
を利用してref.(page/app).controller()
にquery
を渡すことで実態のオブジェクトを取得できます。
ref.app.controller
の場合はページ間をまたがって管理され、ref.page.controller
の場合はそのページのみで管理されます。
ref.app.controller
で定義した場合は基本的にアプリを終了するまでコントローラーは破棄されません。
ref.page.controller
で定義した場合はそのページが破棄されるタイミングでコントローラーも破棄されます。
@override
Widget build(BuildContext context, PageRef ref) {
// Describes the process of loading
// and defining variables required for the page.
// TODO: Implement the variable loading process.
final testController = ref.page.controller(TestController.query()); // TestControllerを取得。
~~~~~~~~~
}
内部としてはChangeNotifier
を継承しているため、notifyLisnteners()
を実行すると読み込んだウィジェットがリビルドされます。
また、appRef
を利用してウィジェット外からも利用することが可能です。
appRef
の場合はページ間をまたがって管理されます。(ref.app.controller
と同じ)
final testController = appRef.controller(TestController.query());
状態管理
基本的には上記ref.app.model
、ref.(page/app).controller
で状態管理の大部分はカバーできるかと思います。
状態管理については拡張も可能なので詳しく知りたいときはパッケージの詳細ページをご覧ください。
翻訳
翻訳はGoogleスプレッドシートを通して行います。
準備は下記のページをご覧ください。
翻訳の更新はmain.dartにAppLocalizeが定義されているのでそこのversion
を更新します。
@GoogleSpreadSheetLocalize(
"https://docs.google.com/spreadsheets/d/1bw7IXEr7BGkZ4U6on0OuF7HQkTMgDSm6u5ThpBkDPeo/edit#gid=551986808",
version: 1, // 更新時はこのバージョンをインクリメント。
)
class AppLocalize extends _$AppLocalize {}
翻訳テキストの取得はl
オブジェクトを利用して行います。
Text(l().success);
その他、下記の機能について詳しく知りたいときはパッケージの詳細ページをご覧ください。
- パラメーターの指定
- 翻訳言語の変更
テーマ管理
アセットの定義
下記の公式サイトを参考にpubspec.yamlを編集し、アプリ内でアセットを読み込めるようにしてください。
// pubspec.yaml
flutter:
assets:
- assets/images/
フォントの定義
下記の公式サイトを参考にpubspec.yamlを編集し、アプリ内でフォントを読み込めるようにしてください。
// pubspec.yaml
flutter:
fonts:
- family: RobotoMono
fonts:
- asset: fonts/RobotoMono-Regular.ttf
- asset: fonts/RobotoMono-Bold.ttf
weight: 700
色とテキストの定義
main.dartの下記の部分を書き換えることで色の指定及びテキストの指定が可能です。
マテリアルデザインのカラースキームに従い下記の色を指定可能です。
また、マテリアルデザインのTypographyに従いテキストを指定可能です。
// main.dart
/// App Theme.
///
/// ```dart
/// theme.color.primary // Primary color.
/// theme.text.bodyMedium // Medium body text style.
/// theme.asset.xxx // xxx image.
/// theme.font.xxx // xxx font.
/// ```
@appTheme
final theme = AppThemeData(
// TODO: Set the design.
primary: Colors.blue,
secondary: Colors.cyan,
onPrimary: Colors.white,
onSecondary: Colors.white,
);
テーマの利用
main.dartのtheme
を利用して下記のテーマの取得が可能です。
-
color
-
AppThemeData
作成時に定義したColorScheme。
-
-
text
-
AppThemeData
作成時に定義したTypeScale
-
-
asset
-
コードジェネレーションで作成した
assets
フォルダ以下のアセット
-
コードジェネレーションで作成した
-
font
- コードジェネレーションで作成したFontFamily。
@override
Widget build(BuildContext context, PageRef ref) {
// Describes the process of loading
// and defining variables required for the page.
// TODO: Implement the variable loading process.
// Describes the structure of the page.
// TODO: Implement the view.
return UniversalScaffold(
appBar: UniversalAppBar(title: Text("Title"), backgroundColor: theme.color.secondary),
body: UniversalColumn(
crossAxisAlignment: CrossAxisAlignment.start,
children:[
Center(child: CircleAvatar(backgroundImage: theme.asset.userIcon.provider)),
Text("User Name", style: theme.text.displayMedium)
]
)
);
}
その他、下記の機能について詳しく知りたいときはパッケージの詳細ページをご覧ください。
- テーマ拡張
- グラデーション
- 変換用メソッド
フォーム構築
フォームコントローラーの取得
まず、フォームの値を制御・保持するためのフォームコントローラーを取得します。
プロフィールデータの編集などデータモデル
を対象にしたフォームを作成する場合、既存のデータモデルに定義されるform
を利用します。
/// Query for form value.
///
/// ```dart
/// ref.app.form(TestModel.form(LogModel())); // Get the form controller in app scope.
/// ref.page.form(TestModel.form(LogModel())); // Get the form controller in page scope.
/// ```
static const form = _$TestModelFormQuery();
form
はコントローラーと同じ様にappスコープ(ref.app.form
)とpageスコープ(ref.page.form
)に渡しますが、その際に元となるオブジェクト(TestModel
)を引数に渡す必要があります。
新規データ登録時はTestModel
を作成して渡せばよく、既存データの編集時にはデータモデル
から読み込んだ値をそのまま渡せばよいです。
// 新規データ作成時
final memo = const MemoModel(title: "", text: "");
final formController = ref.page.form(MemoModel.form( memo ));
// 既存データ作成時
final memo = ref.app.model(MemoModel.document("Memo ID"))..load();
final formController = ref.page.form(MemoModel.form( memo ));
ログインなどデータモデルで定義されていないデータのフォームを作成する場合、フォーム用のデータ定義を下記コマンドで作成します。
katana code value [Value name]
lib/models/(Value name).dart
に下記のファイルが作成されます。
内部では(Value name)Value
というクラスが作成されます。
// login.dart
// ignore: unused_import, unnecessary_import
import 'package:flutter/material.dart';
// ignore: unused_import, unnecessary_import
import 'package:masamune/masamune.dart';
// ignore: unused_import, unnecessary_import
import '/main.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'login.g.dart';
part 'login.m.dart';
part 'login.freezed.dart';
/// Immutable value.
@freezed
@formValue
@immutable
class LoginValue with _$LoginValue {
const factory LoginValue({
// TODO: Set the data schema.
}) = _LoginValue;
const LoginValue._();
factory LoginValue.fromJson(Map<String, Object?> json) =>
_$LoginValueFromJson(json);
/// Query for form value.
///
/// ```dart
/// ref.app.form(LoginValue.form(LogModel())); // Get the form controller in app scope.
/// ref.page.form(LoginValue.form(LogModel())); // Get the form controller in page scope.
/// ```
static const form = _$LoginValueFormQuery();
}
factoryメソッド内に必要なデータスキームを追記してください。
const factory LoginValue({
// TODO: Set the data schema.
required String email,
required String password,
}) = _LoginValue;
フォームコントローラーの取得には、同じ用にこのオブジェクトで定義されているform
を利用してください。
final login = const LoginValue(email: "", password: "");
final formController = ref.app.form(LoginValue.form( login ));
フォームの描画と検証・確定
フォーム用の各ウィジェットのform
パラメーターに上記のフォームコントローラーを渡してください。
またその際、onSaved
のパラメーターに渡された対象の値でフォームの値を書き換えて返す処理を記述してください。
FormTextField(
form: formController,
onSaved: (value) => formController.value.copyWith(email: value),
),
上記の処理を含めながらフォームウィジェットの記述を行った後、確定ボタンを押したときにformController.validate
を実行することでフォームの値の検証と確定が行われます。
その後検証に通った後、返ってきた値で保存処理等を行ってください。
FormButton(
"Login",
onPressed: () async {
final LoginValue loginValue = formController.validate(); // 検証しフォームの値を取得
if (loginValue == null) {
return;
}
try {
// 正常処理
} catch (e) {
// エラー処理
}
},
),
その他、詳しく知りたいときはパッケージの詳細ページをご覧ください。
UIサポート
ダイアログ
ダイアログを下記のコードで表示可能です。
// Alert dialog.
Modal.alert(
title: "Title",
text: "Contents text",
submitText: "OK",
onSubmit: () {
// Processing when the OK button is pressed
},
);
// Confirmation dialog.
Modal.confirm(
title: "Title",
text: "Contents text",
submitText: "Yes",
cancelText: "No",
onSubmit: () {
// Processing when the Yes button is pressed
},
onCancel: () {
// Processing when the No button is pressed
}
);
レスポンシブレイアウト
UniversalUIを用いることで意識することなくPCやモバイルなど画面サイズや向きがことなるプラットフォーム間でUIを作成することができます。
@override
Widget build(BuildContext context, PageRef ref) {
return UniversalScaffold(
breakpoint: Breakpoint.sm,
sideBar: UniversalSideBar(
decoration: const BoxDecoration(
border: Border(right: BorderSide(color: Colors.grey)),
),
children: [
for (var i = 0; i < 100; i++)
ListTile(
tileColor: Colors.blue,
title: Text((i + 1).toString()),
),
],
),
appBar: UniversalSliverAppBar(
title: const Text("UniversalViewPage"),
subtitle: const Text("UniversalViewPage"),
titlePosition: UniversalAppBarTitlePosition.bottom,
background: UniversalAppBarBackground(theme.asset.image.provider),
),
body: UniversalListView(
onRefresh: () {
return Future.delayed(1.s);
},
padding: const EdgeInsets.only(top: 32),
children: [
UniversalColumn(
children: [
...List.generate(100, (i) {
return ListTile(
tileColor: Colors.red,
title: Text((i + 1).toString()),
);
}).mapResponsive(sm: 6, md: 4),
],
),
],
),
);
}
その他、詳しく知りたいときはパッケージの詳細ページをご覧ください。
認証
ユーザー登録や認証を行う場合はmain.dartにあるappAuth
オブジェクトを利用します。
appAuthの各種メソッドを実行することでユーザー登録やログイン、ログアウトが可能です。
// ユーザー登録
await appAuth.register(
EmailAndPasswordAuthQuery.register(
email: "[email protected]",
password: "12345678",
),
);
// ログイン
await appAuth.signIn(
EmailAndPasswordAuthQuery.signIn(
email: "[email protected]",
password: "12345678",
),
);
// ログアウト
await appAuth.signOut();
初期状態だとこれらはアプリのメモリ上に保存されるのみでアプリを再起動すると元に戻ってしまいます。
データを永続化したい場合は後述のFirebase/Firestoreサポート
を参照してください。
その他、詳しく知りたいときはパッケージの詳細ページをご覧ください。
ファイルストレージ
画像ファイルのアップロードなどを行いたい場合はStorage
オブジェクトを利用します。
ファイルピッカーなどを利用してアップロードするファイルのファイルパス
やバイトデータ(UInt8List)
を取得して各種メソッドに渡してください。
final storage = Storage(const StorageQuery("test/file"));
final pickedData = await FilePicker.platform.pickFiles();
storage.upload(pickedData.first.path);
初期状態だとこれらはアプリのメモリ上に保存されるのみでアプリを再起動すると元に戻ってしまいます。
データを永続化したい場合は後述のFirebase/Firestoreサポート
を参照してください。
その他、詳しく知りたいときはパッケージの詳細ページをご覧ください。
Firebase/Firestoreサポート
データモデル(データベース)
、認証
、ファイルストレージ
に関してはデフォルトだとアプリ内のみに保存する機能を提供しますが、アダプターを置き換えることで端末ローカル内やFirebaseを対象としたものに切り替えることが可能です。
Firebase/Firestore用のアダプターを利用する場合は下記のパッケージを予めインポートしておきます。
# If you want to use Firebase Authentication
flutter pub add katana_auth_firebase
# If you want to use Firestore
flutter pub add katana_model_firestore
# If you want to use Cloud Storage for Firebase
flutter pub add katana_storage_firebase
また、FlutterFire等を利用して、Firebaseの初期設定を完了してください。
flutterfire configure
データモデル(データベース)、認証、ファイルストレージをそれぞれFirestore
、Firebase Authentication
、Cloud Storage for Firebase
に切り替えるには、下記のアダプターを対応するものに置き換える必要があります。
/// App Model.
///
/// By replacing this with another adapter, the data storage location can be changed.
// TODO: Change the database.
// final modelAdapter = RuntimeModelAdapter();
final modelAdapter = FirestoreModelAdapter(options: DefaultFirebaseOptions.currentPlatform);
/// App Auth.
///
/// Changing to another adapter allows you to change to another authentication mechanism.
// TODO: Change the authentication.
// final authAdapter = RuntimeAuthAdapter();
final authAdapter = FirebaseAuthAdapter(options: DefaultFirebaseOptions.currentPlatform);
/// App Storage.
///
/// Changing to another adapter allows you to change to another storage mechanism.
// TODO: Change the storage.
// final storageAdapter = LocalStorageAdapter();
final storageAdapter = FirebaseStorageAdapter(options: DefaultFirebaseOptions.currentPlatform);
アダプターを変えるのみ、他のコードを気にすることなくFirebaseへ変更することが可能です。
その他の機能
Masamuneフレームワークにはその他の便利な機能が用意されています。
それぞれ別パッケージで提供されているものなので詳しくはそちらをご覧ください。
短縮記法の提供
Futureを待つ間のインジケーター表示
おわりに
自分で使う用途で作ったものですが実装の思想的に合ってそうならぜひぜひ使ってみてください!
また、こちらにソースを公開しているのでissueやPullRequestをお待ちしてます!
実際にアプリを作りながらMasamuneフレームワークの使い方を学べる記事を投稿しました!
モックデータを作りながらアプリを作成したり、Firebaseとの連携やその他プラグインとの連携など様々なことを行っています。是非ご参照ください!
また仕事の依頼等ございましたら、私のTwitterやWebサイトで直接ご連絡をお願いいたします!
GitHub Sponsors
スポンサーを随時募集してます。ご支援お待ちしております!