目次
概要
Flutterでデータベースを実装するためのライブラリに「sqflite」というものがある。
SQLiteを使用することができるものだ。
今回はその導入部分を実装していく。
準備
まずはターミナルで以下を実行しよう。
$ flutter pub add sqflite
そうすると、pubspec.yamlファイルにsqfliteが追加されていることを確認する。
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
flutter_riverpod: ^2.6.1
sqflite: ^2.4.2
ここまでの確認ができることで準備完了だ。
そして、アーキテクチャはriverpodを使用しているため、こちらの記事を参考にしてもらえるとわかりやすいと思う。
ソースコード
Entity(データベースのクラス)
import 'dart:ui';
import 'package:flutter/material.dart';
enum AttackType {
normal,
explosion,
penetration,
mystery,
vibration;
factory AttackType.fromInt(int type) {
switch (type) {
case 0:
return AttackType.explosion;
case 1:
return AttackType.penetration;
case 2:
return AttackType.mystery;
case 3:
return AttackType.vibration;
default:
return AttackType.normal;
}
}
String description() {
switch (this) {
case AttackType.explosion:
return "爆発";
case AttackType.penetration:
return "貫通";
case AttackType.mystery:
return "神秘";
case AttackType.vibration:
return "振動";
case AttackType.normal:
return "ノーマル";
}
}
Color typeColor() {
switch (this) {
case AttackType.explosion:
return Colors.red;
case AttackType.penetration:
return Colors.yellow;
case AttackType.mystery:
return Colors.blue;
case AttackType.vibration:
return Colors.purple;
case AttackType.normal:
return Colors.indigo;
}
}
}
class BlueArchiveStudentEntity {
final int id;
final String name;
final int level;
final AttackType attackType;
final DateTime registerAt;
BlueArchiveStudentEntity({
required this.id,
required this.name,
required this.level,
required this.attackType,
required this.registerAt,
});
factory BlueArchiveStudentEntity.fromData(dynamic data) {
final int id = data['id'];
final String name = data['name'];
final int level = data['level'];
final int attackType = data['attack_type'];
final DateTime registerAt = DateTime.parse(data['register_at']);
final entity = BlueArchiveStudentEntity(
id: id,
name: name,
level: level,
attackType: AttackType.fromInt(attackType) ,
registerAt: registerAt);
return entity;
}
}
Service
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:sqflite/sqflite.dart';
import 'package:flutter_demo_application/Entity/blue_archive_student_entity.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final homeServiceProvider = Provider<HomeService>((ref) {
return HomeService();
});
class HomeService {
static late Database _database;
static late String _databasePath;
Future<void> initDatabase() async {
try {
_databasePath = await getDatabasesPath();
await Directory(_databasePath).create(recursive: true);
debugPrint(_databasePath);
} on Exception catch (exception) {
throw Exception(exception);
}
try {
HomeService._database = await openDatabase(
'demo_application.db',
version: 1,
onCreate: (Database db, int version) async {
return await db.execute(
'''CREATE TABLE
IF NOT EXISTS blue_archive_student (
id INTEGER PRIMARY KEY,
name TEXT,
level INTEGER,
attack_type INTEGER,
register_at DATETIME
)''');
},
);
} on Exception catch (exception) {
Exception(exception);
}
}
Future<List<BlueArchiveStudentEntity>> getStudentDataService() async {
List<BlueArchiveStudentEntity> studentList = [];
try {
await initDatabase();
List<Map<String, Object?>> students = await HomeService._database
.rawQuery('SELECT * FROM blue_archive_student;');
for (var student in students) {
BlueArchiveStudentEntity entity = BlueArchiveStudentEntity.fromData(student);
studentList.add(entity);
}
} on Exception catch (exception) {
throw Exception(exception);
}
return studentList;
}
}
Repository
import 'package:flutter_demo_application/Entity/blue_archive_student_entity.dart';
import 'package:flutter_demo_application/service/home_service.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final homeRepositoryProvider = Provider<HomeRepository>(
(ref) => HomeRepository(service: ref.read(homeServiceProvider)),
);
class HomeRepository {
final HomeService service;
HomeRepository({required this.service});
Future<void> initDatabase() async {
await service.initDatabase();
}
Future<List<BlueArchiveStudentEntity>> getStudentData() async {
try {
final data = await service.getStudentDataService();
return data;
} on Exception catch (exception) {
throw Exception(exception);
}
}
}
ViewModel
import 'package:flutter_demo_application/Entity/blue_archive_student_entity.dart';
import 'package:flutter_demo_application/Entity/zip_cloud_entity.dart';
import 'package:flutter_demo_application/repository/home_repository.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final homeViewModelProvider = FutureProvider<HomeViewModel>((ref) async {
final viewModel = await HomeViewModel(
repository: ref.read(homeRepositoryProvider),
);
return viewModel;
});
final homeViewModelStudentStateNotifierProvider =
StateNotifierProvider<StudentEntityStateNotifier, List<BlueArchiveStudentEntity>>((ref) {
return StudentEntityStateNotifier(ref);
});
class StudentEntityStateNotifier extends StateNotifier<List<BlueArchiveStudentEntity>> {
StudentEntityStateNotifier(this.ref) : super(<BlueArchiveStudentEntity>[]);
final Ref ref;
Future<List<BlueArchiveStudentEntity>> getStateStudentData() async {
final repository = ref.read(homeRepositoryProvider);
final studentList = await repository.getStudentData();
return studentList;
}
}
class HomeViewModel {
final HomeRepository repository;
HomeViewModel({required this.repository});
late List<ZipCloudEntity> _zipCloudData;
List<ZipCloudEntity> get zipCloudData => _zipCloudData;
late List<BlueArchiveStudentEntity> _studentData = <BlueArchiveStudentEntity>[];
List<BlueArchiveStudentEntity> get studentData => _studentData;
bool isLoading = false;
Future initDatabase() async {
await repository.initDatabase();
}
Future getStudentData() async {
try {
await repository.initDatabase();
final data = await repository.getStudentData();
_studentData = data;
} on Exception catch (exception) {
Exception(exception);
}
}
}
View
import 'package:flutter/material.dart';
import 'package:flutter_demo_application/Entity/blue_archive_student_entity.dart';
import 'package:flutter_demo_application/Entity/zip_cloud_entity.dart';
import 'package:flutter_demo_application/view_model/home_view_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HomeView extends ConsumerWidget {
HomeView({super.key});
List<ZipCloudEntity> zipCloudList = <ZipCloudEntity>[];
List<BlueArchiveStudentEntity> studentList = <BlueArchiveStudentEntity>[];
@override
Widget build(BuildContext context, WidgetRef ref) {
var button = OutlinedButton(
onPressed: () {
Future<List<BlueArchiveStudentEntity>> studentStateProvider =
ref.watch(homeViewModelStudentStateNotifierProvider.notifier).getStateStudentData();
studentStateProvider.then((list) {
studentList = list;
if (studentList.isEmpty) {
debugPrint("データなし");
} else {
debugPrint(studentList.first.name);
}
ref.invalidate(homeViewModelProvider);
});
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.white10,
foregroundColor: Colors.black,
disabledBackgroundColor: Colors.black26,
disabledForegroundColor: Colors.black54,
),
child: Text("データ取得"),
);
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.grey),
home: Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text(''),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
button,
ref.watch(homeViewModelProvider).when(
data: (_) => ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: studentList.length,
itemBuilder: (_, index) {
return studentDataCell(studentList[index]);
},
),
error: (error, _) => const Center(child: Text('通信エラー')),
loading: () => const Center(child: CircularProgressIndicator()),
),
],
),
),
);
}
Widget studentDataCell(BlueArchiveStudentEntity entity) {
return Container(
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: entity.attackType.typeColor().withAlpha(128),
border: const Border(bottom: BorderSide(color: Colors.grey, width: 1.0)),
),
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("${entity.name} Lv.${entity.level}"),
Text("${entity.attackType.description()}"),
],
),
),
);
}
}
デモ動画
詳細
今回は準備、導入の話なので以下の話になる。
- ライブラリの導入
- データベースの作成
- テーブルの作成
- データの取得
ライブラリの導入は準備のところで話したので割愛しよう。
データベースの作成・テーブルの作成
まず、データベースを保存するための場所(ディレクトリ)を作成している処理は以下だ。
Future<void> initDatabase() async {
try {
_databasePath = await getDatabasesPath();
await Directory(_databasePath).create(recursive: true);
debugPrint(_databasePath);
} on Exception catch (exception) {
throw Exception(exception);
}
// 省略
見ての通りだが、データベースのファイル(.dbファイル)を保存する場所を設定し、そこにデータベースのファイルをcreateメソッドで作成する。
そして、以下が本題。
データベースを作成する。同時にテーブルも併せて作成する。
try {
HomeService._database = await openDatabase(
'demo_application.db',
version: 1,
onCreate: (Database db, int version) async {
return await db.execute(
'''CREATE TABLE
IF NOT EXISTS blue_archive_student (
id INTEGER PRIMARY KEY,
name TEXT,
level INTEGER,
attack_type INTEGER,
register_at DATETIME
)''');
},
);
}
openDatabaseメソッドを使用して、データベースを作成&データベースの参照の行う。
データの取得
現段階ではデータが登録されていないので、上記で記載されているコードではデータが取得できないので注意。
データの登録方法は別ページで解説する。今回は.dbファイルを直接操作してデータを登録する。
また、データの方法は本ページでは簡単にすべてのデータを取得するという処理を行う。
.dbファイルの場所はinitDatabaseメソッドの以下の出力箇所を見れば保存場所が出てくる。
_databasePath = await getDatabasesPath();
await Directory(_databasePath).create(recursive: true);
debugPrint(_databasePath); // ここ
今回Macで開いた時はこんな感じだ。
「DB Browser for SQLite」でデータを追加した。

そして、以下のコードでデータベースからデータを取得している。
SELECT文ですべてのデータを取得しているが、条件付きのデータ取得などは別のページに記載する。
List<Map<String, Object?>> students = await HomeService._database
.rawQuery('SELECT * FROM blue_archive_student;');
取得したデータはList<MAP<String, Object>>型、日本語で言うなら連想配列なので、以下のようにレスポンスクラスの変換メソッドを定義し、レスポンスクラスのList型のオブジェクトにする。
factory BlueArchiveStudentEntity.fromData(dynamic data) {
final int id = data['id'];
final String name = data['name'];
final int level = data['level'];
final int attackType = data['attack_type'];
final DateTime registerAt = DateTime.parse(data['register_at']);
final entity = BlueArchiveStudentEntity(
id: id,
name: name,
level: level,
attackType: AttackType.fromInt(attackType) ,
registerAt: registerAt);
return entity;
}
for (var student in students) {
BlueArchiveStudentEntity entity = BlueArchiveStudentEntity.fromData(student);
studentList.add(entity);
}
こうすることで以下のようにViewで表示する時に使いやすい状態になる。
Widget studentDataCell(BlueArchiveStudentEntity entity) {
return Container(
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: entity.attackType.typeColor().withAlpha(128),
border: const Border(bottom: BorderSide(color: Colors.grey, width: 1.0)),
),
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("${entity.name} Lv.${entity.level}"),
Text("${entity.attackType.description()}"),
],
),
),
);
}
参考ページ
- pub.dev「sqflite」
- Qiita「[Flutter] SQLiteを使用してローカル保存を学ぶ」
- Standing on the Shoulder of Linus「CREATE TABLE IF NOT EXISTSは作成済みのテーブルを作ろうとするエラーを防げるが、」
- Zenn「【Flutter】sqfliteパッケージによるDBファイルの配置場所について、もう少し考えてみる」
- Zenn「【Flutter】SQLiteでデータベースの初期設定」
- RESUMY「Flutterで「Unhandled Exception: LateInitializationError: Field ‘X’ has not been initialized」エラーが表示されたときの解決方法」