スマレジの テックファーム(SES 部門) でWebエンジニアとして働いている やまて(@r_yamate) と申します。
実務では、2023 年 3 月末で SES の派遣先で、テーブルオーダーシステムの機能改修業務の設計などを担当していた業務を終えたところです。
4月からは、スマレジの関連アプリの開発業務を担当しています。触ったことのなかった Flutter での開発で、日々奮闘中です。
はじめに
本記事では、 Flutter の Drift という SQLite パッケージを使って、データを永続化する方法について書きます。 Drift を使うと、 SQLite データベースの操作を簡単に、安全に行うことができます。
CRUD 操作(作成、参照、更新、削除)を実装して、メモアプリを作成することを通して、 Drift パッケージの使い方を確認します。
Flutter パッケージ「Drift」
Drift は、 Flutter と Dart のためのリアクティブな永続化ライブラリで、 SQLite の上に構築されており、 Dart でデータベースを操作するための機能を提供するパッケージです。
何が良いかというと、ローカル(スマホの端末)の保存領域にデータを保持できて、データの追加や削除、更新が行える点です。
目次
メモアプリの概要
まず、作成するメモアプリの設計を簡単に確認します。
- このアプリは以下のようなCRUD 操作の機能を持つことを想定しています。
- メモの一覧表示
- メモの作成
- メモの編集
- メモの削除
- 各メモの項目は、「タイトル」と「内容」です。
完成イメージ
環境
- MacBook Pro(intel)
- macOS Monterey 12.6.5
- Android Studio Electric Eel | 2022.1.1
- Android Emulator
- Flutter 3.7.9
- Dart 2.18.6
- Drift 2.4.2
GitHub リポジトリ
本記事で使用するサンプルアプリのソースコードは、以下の GitHub リポジトリに公開しています。
1. Drift のセットアップ
新しい Flutter プロジェクトを作成し、 Drift パッケージをセットアップします。
1-1. Flutter プロジェクトの作成
以下のコマンドを使って新しい Flutter プロジェクトを作成します。
flutter create memo_app
実行時ログ
# r_yamate @ mbp in ~/development [0:21:07] $ flutter create flutter_memo_app Creating project flutter_memo_app... Running "flutter pub get" in flutter_memo_app... Resolving dependencies in flutter_memo_app... (2.6s) + async 2.10.0 (2.11.0 available) + boolean_selector 2.1.1 + characters 1.2.1 (1.3.0 available) + clock 1.1.1 + collection 1.17.0 (1.17.2 available) + cupertino_icons 1.0.5 + fake_async 1.3.1 + flutter 0.0.0 from sdk flutter + flutter_lints 2.0.1 + flutter_test 0.0.0 from sdk flutter + js 0.6.5 (0.6.7 available) + lints 2.0.1 (2.1.1 available) + matcher 0.12.13 (0.12.16 available) + material_color_utilities 0.2.0 (0.5.0 available) + meta 1.8.0 (1.9.1 available) + path 1.8.2 (1.8.3 available) + sky_engine 0.0.99 from sdk flutter + source_span 1.9.1 (1.10.0 available) + stack_trace 1.11.0 + stream_channel 2.1.1 + string_scanner 1.2.0 + term_glyph 1.2.1 + test_api 0.4.16 (0.6.0 available) + vector_math 2.1.4 Changed 24 dependencies in flutter_memo_app! Wrote 127 files. All done! You can find general documentation for Flutter at: https://docs.flutter.dev/ Detailed API documentation is available at: https://api.flutter.dev/ If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev In order to run your application, type: $ cd flutter_memo_app $ flutter run Your application code is in flutter_memo_app/lib/main.dart. # r_yamate @ srC02T54QWH03M in ~/development [0:34:22] $ cd flutter_memo_app
1-2. Drift および関連パッケージの追加
次に、 pubspec.yaml ファイルを開き、次の依存関係を追加します。
dependencies: flutter: sdk: flutter + drift: ^2.4.2 + sqlite3_flutter_libs: ^0.5.0 + path_provider: ^2.0.0 + path: ^1.8.2 cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter + build_runner: ^2.3.3 + drift_dev: ^2.4.1
追加したパッケージ
そして、以下のコマンドでパッケージを取得します。
flutter pub get
実行時ログ
# r_yamate @ mbp in ~/development/flutter_memo_app on git:main x [1:42:52] $ flutter pub get Running "flutter pub get" in flutter_memo_app... Resolving dependencies... (6.3s) _fe_analyzer_shared 50.0.0 (61.0.0 available) analyzer 5.2.0 (5.13.0 available) args 2.4.1 (2.4.2 available) > async 2.10.0 (was 2.9.0) (2.11.0 available) > boolean_selector 2.1.1 (was 2.1.0) build 2.3.1 (2.4.0 available) build_daemon 3.1.1 (4.0.0 available) build_resolvers 2.1.0 (2.2.0 available) build_runner 2.3.3 (2.4.5 available) build_runner_core 7.2.7 (7.2.10 available) characters 1.2.1 (1.3.0 available) checked_yaml 2.0.2 (2.0.3 available) cli_util 0.3.5 (0.4.0 available) code_builder 4.4.0 (4.5.0 available) > collection 1.17.0 (was 1.16.0) (1.17.2 available) crypto 3.0.2 (3.0.3 available) dart_style 2.2.5 (2.3.1 available) drift 2.4.2 (2.8.2 available) drift_dev 2.4.1 (2.8.3 available) file 6.1.4 (7.0.0 available) fixnum 1.0.1 (1.1.0 available) glob 2.1.1 (2.1.2 available) js 0.6.5 (0.6.7 available) json_annotation 4.8.0 (4.8.1 available) lints 2.0.1 (2.1.1 available) logging 1.1.1 (1.2.0 available) > matcher 0.12.13 (was 0.12.12) (0.12.16 available) > material_color_utilities 0.2.0 (was 0.1.5) (0.5.0 available) meta 1.8.0 (1.9.1 available) path 1.8.2 (1.8.3 available) > source_span 1.9.1 (was 1.9.0) (1.10.0 available) sqlparser 0.26.1 (0.30.2 available) > stack_trace 1.11.0 (was 1.10.0) > stream_channel 2.1.1 (was 2.1.0) > string_scanner 1.2.0 (was 1.1.1) > test_api 0.4.16 (was 0.4.12) (0.6.0 available) > vector_math 2.1.4 (was 2.1.2) watcher 1.0.2 (1.1.0 available) win32 4.1.4 (5.0.3 available) yaml 3.1.1 (3.1.2 available) Changed 11 dependencies!
2. データベースの生成
データベースファイルを作成して、アプリを起動したときにデータベースを生成する処理を追加します。
2-1. データベースファイルの作成
lib ディレクトリに database ディレクトリを作成し、memos.dart を作成します。そして、以下のように Memo
テーブルと、 AppDatabase
データベースを定義します。
- 新規作成:lib/database/memos.dart
import 'package:drift/drift.dart'; part 'memos.g.dart'; @DataClassName('Memo') class Memos extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get title => text().withLength(min: 1, max: 50)(); TextColumn get content => text().withLength(min: 1, max: 1000)(); } @DriftDatabase(tables: [Memos]) class AppDatabase extends _$AppDatabase {}
Drift では、テーブルの定義は特殊なクラスを使って行われます。このクラスはテーブルの各行を表すものです。メモアプリを作成するため、 Memo
という名前のテーブルを定義します。
メモのモデルクラスの定義
/// `Memo`という名前で、メモ情報を保持するクラス。 @DataClassName('Memo') class Memos extends Table { /// メモのID。自動インクリメントする整数値。 IntColumn get id => integer().autoIncrement()(); /// メモのタイトル。1から50文字のテキスト。 TextColumn get title => text().withLength(min: 1, max: 50)(); /// メモの内容。1から1000文字のテキスト。 TextColumn get content => text().withLength(min: 1, max: 1000)(); }
Memos
というテーブルを定義し、3つのフィールド(id
, title
, content
)を持たせます。
id
:自動インクリメントする整数型title
、content
:テキスト型で、それぞれ最小値と最大値の長さを設定
また、 @DataClassName('Memo')
というアノテーションを使って、このテーブルの各行の型の名前を Memo
に設定します。これにより、このテーブルの各行は Memo
という型を持つことになります。
データベースクラスの定義
/// `Memos`テーブルを含むデータベースを表現するクラス。 /// /// データベースの構造や動作をコードによってモデリングする。 @DriftDatabase(tables: [Memos]) class AppDatabase extends _$AppDatabase {}
このコードは、データベースクラスの定義です。ここに、データベースの生成処理やデータの追加等の処理を記載します。
2-2. ローカルデータベースの自動生成
以下のコマンドを実行します。
flutter pub run build_runner build
実行時ログ
# r_yamate @ mbp in ~/development/flutter_memo_app on git:main x [16:09:24] $ flutter pub run build_runner build [INFO] Generating build script... [INFO] Generating build script completed, took 587ms [INFO] Initializing inputs [INFO] Reading cached asset graph... [INFO] Reading cached asset graph completed, took 99ms [INFO] Checking for updates since last build... [INFO] Checking for updates since last build completed, took 903ms [INFO] Running build... [INFO] Running build completed, took 32ms [INFO] Caching finalized dependency graph... [INFO] Caching finalized dependency graph completed, took 83ms [INFO] Succeeded after 133ms with 0 outputs (0 actions)
こちらは、 build_runner パッケージの機能で、コマンドを実行することで、drift パッケージを使って Flutter アプリで使用するためのローカルデータベースの構造や設計が自動生成されます。
自動生成されたファイル
以下のファイルが自動生成されました。自動生成されたコードは一般的には、開発者が直接編集することはないようです。代わりに、 Drift パッケージが提供するアノテーションとツールを使用して、データベーススキーマを定義し、そのスキーマに基づいてこのようなコードを自動生成します。
lib/database/memos.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND part of 'memos.dart'; // ignore_for_file: type=lint class $MemosTable extends Memos with TableInfo<$MemosTable, Memo> { @override final GeneratedDatabase attachedDatabase; final String? _alias; $MemosTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn<int> id = GeneratedColumn<int>( 'id', aliasedName, false, hasAutoIncrement: true, type: DriftSqlType.int, requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); static const VerificationMeta _titleMeta = const VerificationMeta('title'); @override late final GeneratedColumn<String> title = GeneratedColumn<String>( 'title', aliasedName, false, additionalChecks: GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 50), type: DriftSqlType.string, requiredDuringInsert: true); static const VerificationMeta _contentMeta = const VerificationMeta('content'); @override late final GeneratedColumn<String> content = GeneratedColumn<String>( 'content', aliasedName, false, additionalChecks: GeneratedColumn.checkTextLength( minTextLength: 1, maxTextLength: 1000), type: DriftSqlType.string, requiredDuringInsert: true); @override List<GeneratedColumn> get $columns => [id, title, content]; @override String get aliasedName => _alias ?? 'memos'; @override String get actualTableName => 'memos'; @override VerificationContext validateIntegrity(Insertable<Memo> instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('title')) { context.handle( _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); } else if (isInserting) { context.missing(_titleMeta); } if (data.containsKey('content')) { context.handle(_contentMeta, content.isAcceptableOrUnknown(data['content']!, _contentMeta)); } else if (isInserting) { context.missing(_contentMeta); } return context; } @override Set<GeneratedColumn> get $primaryKey => {id}; @override Memo map(Map<String, dynamic> data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return Memo( id: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}id'])!, title: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}title'])!, content: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}content'])!, ); } @override $MemosTable createAlias(String alias) { return $MemosTable(attachedDatabase, alias); } } class Memo extends DataClass implements Insertable<Memo> { final int id; final String title; final String content; const Memo({required this.id, required this.title, required this.content}); @override Map<String, Expression> toColumns(bool nullToAbsent) { final map = <String, Expression>{}; map['id'] = Variable<int>(id); map['title'] = Variable<String>(title); map['content'] = Variable<String>(content); return map; } MemosCompanion toCompanion(bool nullToAbsent) { return MemosCompanion( id: Value(id), title: Value(title), content: Value(content), ); } factory Memo.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return Memo( id: serializer.fromJson<int>(json['id']), title: serializer.fromJson<String>(json['title']), content: serializer.fromJson<String>(json['content']), ); } @override Map<String, dynamic> toJson({ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return <String, dynamic>{ 'id': serializer.toJson<int>(id), 'title': serializer.toJson<String>(title), 'content': serializer.toJson<String>(content), }; } Memo copyWith({int? id, String? title, String? content}) => Memo( id: id ?? this.id, title: title ?? this.title, content: content ?? this.content, ); @override String toString() { return (StringBuffer('Memo(') ..write('id: $id, ') ..write('title: $title, ') ..write('content: $content') ..write(')')) .toString(); } @override int get hashCode => Object.hash(id, title, content); @override bool operator ==(Object other) => identical(this, other) || (other is Memo && other.id == this.id && other.title == this.title && other.content == this.content); } class MemosCompanion extends UpdateCompanion<Memo> { final Value<int> id; final Value<String> title; final Value<String> content; const MemosCompanion({ this.id = const Value.absent(), this.title = const Value.absent(), this.content = const Value.absent(), }); MemosCompanion.insert({ this.id = const Value.absent(), required String title, required String content, }) : title = Value(title), content = Value(content); static Insertable<Memo> custom({ Expression<int>? id, Expression<String>? title, Expression<String>? content, }) { return RawValuesInsertable({ if (id != null) 'id': id, if (title != null) 'title': title, if (content != null) 'content': content, }); } MemosCompanion copyWith( {Value<int>? id, Value<String>? title, Value<String>? content}) { return MemosCompanion( id: id ?? this.id, title: title ?? this.title, content: content ?? this.content, ); } @override Map<String, Expression> toColumns(bool nullToAbsent) { final map = <String, Expression>{}; if (id.present) { map['id'] = Variable<int>(id.value); } if (title.present) { map['title'] = Variable<String>(title.value); } if (content.present) { map['content'] = Variable<String>(content.value); } return map; } @override String toString() { return (StringBuffer('MemosCompanion(') ..write('id: $id, ') ..write('title: $title, ') ..write('content: $content') ..write(')')) .toString(); } } abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); late final $MemosTable memos = $MemosTable(this); @override Iterable<TableInfo<Table, Object?>> get allTables => allSchemaEntities.whereType<TableInfo<Table, Object?>>(); @override List<DatabaseSchemaEntity> get allSchemaEntities => [memos]; }
$MemosTable
クラス:「memos」という名前のテーブルについて定義しています。このテーブルには、id
、title
、content
の3つのカラムがあります。それぞれのカラムはGeneratedColumn
として、各カラムの型や他の制約を定義しています。Memo
クラス: memos テーブルのレコードを定義するためのデータクラスです。各メモは、整数型のid
、文字列型のtitle
、文字列型のcontent
を持っています。MemosCompanion
クラス:新しいメモをデータベースに挿入する際のヘルパークラスです。任意のカラムを含むレコードを作成することができます。_$AppDatabase
クラス:全てのテーブルとその他のデータベーススキーマエンティティを保持します。今回は、 memos テーブルのみが存在します。
補足. ファイルを再生成したい場合
ファイルを再生成したい場合は、--delete-conflicting-outputs
というオプションをコマンドに付けて実行します。
flutter pub run build_runner build --delete-conflicting-outputs
2-3. データベースの生成
アプリを起動したときにデータベースを生成する処理を追加します。
編集:lib/database/memos.dart
import 'dart:io'; import 'package:drift/drift.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; part 'memos.g.dart'; @DataClassName('Memo') class Memos extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get title => text().withLength(min: 1, max: 50)(); TextColumn get content => text().withLength(min: 1, max: 1000)(); } /// `Memos`テーブルを含むデータベースを表現するクラス。 @DriftDatabase(tables: [Memos]) class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); /// データベースのスキーマバージョンを返す。現在は1。 @override int get schemaVersion => 1; } /// アプリのドキュメントディレクトリにデータベースファイルを生成し、接続する。 LazyDatabase _openConnection() { return LazyDatabase(() async { final dbFolder = await getApplicationDocumentsDirectory(); final file = File(p.join(dbFolder.path, 'memos.sqlite')); return NativeDatabase(file); }); }
データベースの生成は、main.dart
のmain
関数の中で、runApp
の前で行います。
編集:lib/main.dart
import 'package:flutter/material.dart';
import 'database/memos.dart';
void main() {
+ final database = AppDatabase();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
3. メモの CRUD 操作の作成
以下の CRUD の操作を作成します。
- メモの一覧表示
- メモの作成
- メモの編集
- メモの削除
3-1. データベースクラスの編集
メモの作成、読み取り、更新、削除(CRUD)の操作は、 memos.dart ファイルの AppDatabase
クラスにメソッドとして追加します。
編集:lib/database/memos.dart
import 'dart:io'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; part 'memos.g.dart'; @DataClassName('Memo') class Memos extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get title => text().withLength(min: 1, max: 50)(); TextColumn get content => text().withLength(min: 1, max: 1000)(); } @DriftDatabase(tables: [Memos]) class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override int get schemaVersion => 1; } /// データベースから全てのメモをストリームとして取得する。 /// メモが追加、更新、削除されると、このストリームは新しいリストを返す。 Stream<List<Memo>> watchAllMemos(AppDatabase db) { return db.select(db.memos).watch(); } /// データベースから全てのメモを一度だけ取得する。 Future<List<Memo>> getAllMemos(AppDatabase db) { return db.select(db.memos).get(); } /// 新しいメモをデータベースに挿入する。 Future insertMemo(AppDatabase db, Memo memo) { return db.into(db.memos).insert(memo); } /// メモを更新する。 Future updateMemo(AppDatabase db, Memo memo) { return db.update(db.memos).replace(memo); } /// データベースからメモを削除する。 Future deleteMemo(AppDatabase db, Memo memo) { return db.delete(db.memos).delete(memo); } LazyDatabase _openConnection() { return LazyDatabase(() async { final dbFolder = await getApplicationDocumentsDirectory(); final file = File(p.join(dbFolder.path, 'memos.sqlite')); return NativeDatabase(file); }); }
これで、アプリのバックエンド部分であるデータベースとデータ操作が完成しました。
3-2. フロントエンドの UI の作成
フロントエンドのUI部分を作成します。
編集:lib/main.dart
import 'package:flutter/material.dart'; import 'database/memos.dart'; void main() { final database = AppDatabase(); runApp(MyApp(database: database)); } class MyApp extends StatelessWidget { /// MyApp のコンストラクタ。 /// /// [database] はメモの保存や取得に使われるデータベースオブジェクト。 const MyApp({ Key? key, required this.database, }) : super(key: key); final AppDatabase database; /// MyApp のビルドメソッド。MaterialApp ウィジェットを返す。 @override Widget build(BuildContext context) { return MaterialApp( title: 'Drift Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage( title: 'Drift Memo App', database: database, ), ); } } /// アプリのホームページとなるウィジェット。 class MyHomePage extends StatefulWidget { /// MyHomePage のコンストラクタ。 /// /// [title] はアプリのタイトル。 /// [database] はメモの保存や取得に使われるデータベースオブジェクト。 const MyHomePage({ Key? key, required this.title, required this.database, }) : super(key: key); final String title; final AppDatabase database; /// MyHomePage の State を生成する。 @override State<MyHomePage> createState() => _MyHomePageState(); } /// MyHomePage の状態を管理するクラス。 class _MyHomePageState extends State<MyHomePage> { late final TextEditingController _titleController; late final TextEditingController _contentController; int? _editingMemoId; /// State の初期化時に実行される。 /// /// メモのタイトルと内容を編集するための TextEditingController を初期化する。 @override void initState() { super.initState(); _titleController = TextEditingController(); _contentController = TextEditingController(); } /// Stateの破棄時に実行される。 /// /// メモのタイトルと内容を編集するための TextEditingController を破棄する。 @override void dispose() { _titleController.dispose(); _contentController.dispose(); super.dispose(); } /// 新しいメモを挿入または既存のメモを更新する。 Future<void> _insertOrUpdateMemo() async { final title = _titleController.text; final content = _contentController.text; if (_editingMemoId != null) { final memo = Memo( id: _editingMemoId!, title: title, content: content, ); await updateMemo(widget.database, memo); } else { final id = DateTime.now().millisecondsSinceEpoch; final memo = Memo( id: id, title: title, content: content, ); await insertMemo(widget.database, memo); } _titleController.clear(); _contentController.clear(); _editingMemoId = null; } /// メモを削除する。 Future<void> _deleteMemo(Memo memo) async { await deleteMemo(widget.database, memo); } /// メモの編集を開始する。 void _startEditingMemo(Memo memo) { _editingMemoId = memo.id; _titleController.text = memo.title; _contentController.text = memo.content; } /// MyHomePage のビルドメソッド。 UI を構築する。 @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: true, appBar: AppBar( title: Text(widget.title), ), body: StreamBuilder<List<Memo>>( stream: watchAllMemos(widget.database), builder: (context, snapshot) { final memos = snapshot.data ?? []; return ListView.builder( itemCount: memos.length, itemBuilder: (context, index) { final memo = memos[index]; return ListTile( title: Text(memo.title), subtitle: Text(memo.content), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.edit), onPressed: () => _startEditingMemo(memo), ), IconButton( icon: const Icon(Icons.delete), onPressed: () => _deleteMemo(memo), ), ], ), ); }, ); }, ), floatingActionButton: FloatingActionButton( onPressed: _insertOrUpdateMemo, tooltip: 'Insert Memo', child: const Icon(Icons.add), ), bottomSheet: SizedBox( child: SingleChildScrollView( padding: const EdgeInsets.only( left: 16, top: 32, right: 16, bottom: 16, ), child: Column( children: [ TextField( controller: _titleController, decoration: const InputDecoration( labelText: 'Title', border: OutlineInputBorder(), ), ), const SizedBox(height: 16), TextField( controller: _contentController, decoration: const InputDecoration( labelText: 'Content', border: OutlineInputBorder(), ), ), ], ), ), ), ); } }
説明は割愛しますが、以下の機能を作成しました。
- メモの一覧表示
- メモの作成
- メモの編集
- メモの削除
上記のとおり UI については微妙な仕上がりで手直ししたい気持ちですが、主目的は Drift パッケージの理解なので、今回は深入りしません。
まとめ
以上で基本的なメモアプリの機能が完成しました。
Flutter の Drift パッケージを使ってデータを永続化する方法について、メモアプリの作成、CRUD 操作(作成、参照、更新、削除)の実装を通して書きました。
ありがとうございました。
できないことは山ほどあるけど、できることは着実に増えてる。
— やまて|ソフトウェアエンジニア2年目 (@r_yamate) 2023年6月9日
今回の記事の内容も然り。