Flutter - 로컬 데이터베이스 ①: sqflite(생성, 삽입, 업데이트, 삭제, 쿼리)
목차
1: Flutter - 로컬 데이터베이스 ①: sqflite(생성, 삽입, 업데이트, 삭제, 쿼리) <현재>현재>
sqflite
를 사용하여Specs(id, type, category, method, contents, money, dateTime)
라는 로컬 데이터베이스를 생성- 로컬 데이터베이스에 접근하는
SpecProvider
클래스를 생성하고, DB에 읽고 쓰는 함수들을 정의
2: Flutter - 로컬 데이터베이스 ②: Future(데이터베이스 화면에 띄우기, FutureBuilder)
- DB에 접근하는 것은
async
한 작업 - 이를
Future
키워드를 사용해 다루고,FutureBuilder
로 화면에 값을 보여줌
3: Flutter - 로컬 데이터베이스 ③: Singleton, factory
- DB 인스턴스를 Singleton으로 생성
4: Flutter - 로컬 데이터베이스 ④: 이미지 파일을 데이터베이스에 저장하고 읽기
- 이미지 파일을 데이터베이스에 저장하고 읽기
플러터에서 로컬 데이터베이스를 써 보자
sqflite와 path를 써서 그냥 SQL처럼 쓸 수 있다.
보기 편하게 DBHelper.dart 파일을 만들어서 그 안에 메소드들을 모았다
사용 준비
dependencies:
flutter:
sdk: flutter
sqflite:
path:
pubspec.yaml
의 dependencies에 sqflite
와 path
를 추가해준다.
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
dart 파일에 해당 패키지를 임포트하면 끝
async도 DB는 비동기로 접근하기 때문에 필요하다
모델 만들기
class Spec {
int? id;
int type;
String? category;
int? method;
String? contents;
int money;
String? dateTime;
Spec({this.id, required this.type, this.category, this.method, this.contents, required this.money, this.dateTime});
Map<String, dynamic> toMap(){
return {
'id': id,
'type': type,
'category': category,
'method': method,
'contents': contents,
'money': money,
'dateTime': dateTime,
};
}
나는 지출/수입 내역을 저장하는 Spec
을 모델로 만들었다.
id
는 실제 사용하는 Spec의 내용은 아니고, update하거나 delete할 때 접근하기 위해 부여 받은 고유한 수로, 즉 primary key다.
DB에 새 Spec을 insert할 때마다 새 id를 부여받을 수 있다. 아래에 기술
필수 항목으로 Null 값이 되어선 안 되는 것이 있다면 생성자에 required
를 넣어서 표시해줄 수 있다. DB 테이블 만들 때 Not Null 항목들
toMap()
은 Spec을 map 타입으로 변환해준다. DB에 실제로 insert할 땐 map 타입이어야 하기 때문이다.
참고: DateTime 타입은 불가능
https://pub.dev/packages/sqflite
DateTime
is not a supported SQLite type. Personally I store them as int (millisSinceEpoch) or string (iso8601)
DateTime은 SQLite 타입에 없기 때문에 String 등으로 변환해서 넣자
데이터베이스 만들기
class SpecProvider {
late Database _database;
Future<Database?> get database async {
_database = await initDB();
return _database;
}
initDB() async {
String path = join(await getDatabasesPath(), 'Specs.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE Specs(
id INTEGER PRIMARY KEY AUTOINCREMENT,
type INTEGER NOT NULL,
category TEXT,
method INTEGER,
contents TEXT,
money INT NOT NULL,
dateTime TEXT
)
''');
},
onUpgrade: (db, oldVersion, newVersion){}
);
}
}
SpecProvider
클래스를 만들었다.
DB에 접근하는 거는 시간이 오래 걸릴 수 있기 때문에 비동기로 동작한다.
initDB()
로 DB를 가져온다. getDatabasesPath()
로 안드로이드, IOS등 각 기기에 저장된 DB의 위치를 가져올 수 있다.
openDatabase()
로 DB를 여는데, 만약 첫 사용자라서 DB가 비어 있는 경우, 새 DB를 만들어 준다.
execute()
에 쿼리문으로 테이블을 CREATE해 주면 된다.
id는 Primary Key로 AUTOINCREMENT 속성을 주었는데, 위에서 말했듯이 DB에 새 Spec을 insert할 때마다 새 id를 부여한다.
데이터베이스 접근하기
이제 DB에 직접 넣고, 업데이트하고, 삭제할 수 있어야 한다.
SpecProvider
클래스 안에 함수들을 만들어 준다.
DB 전체 들고 오기(db.query()
)
Future<List<Spec>> getDB() async {
final db = await database;
final List<Map<String, dynamic>> maps = await db!.query(tableName);
if( maps.isEmpty ) return [];
List<Spec> list = List.generate(maps.length, (index) {
return Spec(
id: maps[index]["id"],
type: maps[index]["type"],
category: maps[index]["category"],
method: maps[index]["method"],
contents: maps[index]["contents"],
money: maps[index]["money"],
dateTime: maps[index]["dateTime"],
);
});
return list;
}
db.query(tableName)
으로 List<Map<String, dynamic>> maps
에 DB를 다 들고 올 수 있다.
그럼 이제 map들 List를 원래 모델인 Spec들 List로 변환해서 리턴하면 끝이다.
List.generate()
로 간단하게 바꿀 수 있다.
db!.query(
tableName,
columns: columnsToSelect,
where: whereString,
whereArgs: whereArguments);
이런 식으로 sql의 WHERE 절도 쓸 수 있다.
DB에 쿼리 날려서 들고 오기(db.rawQuery()
)
db.query()
도 쿼리를 날리는 거긴 하지만, 그것보다는 그냥 sql문이 개인적으로 더 편해서 나는 이 쪽이 좋은 것 같다
Future<List<Spec>> getQuery(String query) async {
final db = await database;
final List<Map<String, dynamic>> maps = await db!.rawQuery(query);
if( maps.isEmpty ) return [];
List<Spec> list = List.generate(maps.length, (index) {
return Spec(
id: maps[index]["id"],
type: maps[index]["type"],
category: maps[index]["category"],
method: maps[index]["method"],
contents: maps[index]["contents"],
money: maps[index]["money"],
dateTime: maps[index]["dateTime"],
);
});
return list;
}
rawQuery()
를 쓰면 sql문을 쓸 수 있다.
나머지는 위랑 똑같음
DB insert
Future<void> insert(Spec spec) async {
final db = await database;
spec.id = await db?.insert(tableName, spec.toMap());
}
DB update
Future<void> update(Spec spec) async {
final db = await database;
await db?.update(
tableName,
spec.toMap(),
where: "id = ?",
whereArgs: [spec.id],
);
}
primary key인 id로 접근해서 해당 Spec을 업데이트해 줬다.
DB delete
Future<void> delete(Spec spec) async {
final db = await database;
await db?.delete(
tableName,
where: "id = ?",
whereArgs: [spec.id],
);
}
primary key인 id로 접근해서 해당 Spec을 삭제한다.
굿
처음 Null check 문제로 좀 애먹었는데 지금 보니 간단하고 좋다
댓글남기기