Sqlcool은 데이터베이스 변경에 대한 반응형 스트림, 내장된 선택 블록 패턴, 사용자 정의 데이터베이스 모델 지원을 제공함으로써 SQLite 데이터베이스 작업을 간소화하는 Flutter/Dart 패키지입니다. 최소한의 반복 코드로 실시간 데이터 동기화와 효율적인 쿼리가 가능합니다. Flutter 앱에서 로컬 지속성 데이터 저장을 위한 가볍고 직관적인 솔루션을 찾는 개발자를 위해 설계되었으며, 반응형 프로그래밍 패러다임과 원활하게 통합됩니다. 복잡한 데이터베이스 작업에 대한 유연성을 유지하면서도 사용의 용이성을 강조합니다.
사용 사례
오프라인 우선 앱에서의 실시간 데이터 동기화
데이터베이스 변경에 기반한 반응형 UI 업데이트
구조화된 데이터베이스 모델을 사용한 로컬 상태 관리
동적 데이터 로딩을 사용한 폼 만들기
SQLite를 사용한 캐시 레이어 구현
사용자 정의 모델을 사용한 CRUD 작업 간소화
주요 기능
데이터베이스 변경 알림을 위한 반응형 스트림
상태 관리를 위한 내장 선택 블록 패턴
사용자 정의 데이터베이스 모델 정의 지원
쿼리 및 변환을 위한 간단한 API
자동 타입 안전 모델 매핑
경량이며 최소한의 종속성
적합한 대상
오프라인 기능을 갖춘 플러터 앱을 개발하는 개발자
반응형 상태 관리 사용 팀
실시간 데이터베이스 업데이트가 필요한 프로젝트
구조화된 로컬 데이터 지속성을 필요로 하는 앱
플러터에서 SQLite를 배우는 초보자
자주 로컬 데이터 상호작용을 하는 앱
고려 사항
SQLite만 지원됨(다른 데이터베이스는 불가능)
내장된 마이그레이션 처리 없음(수동 또는 외부 도구 필요)
복잡한 스키마 변경을 위해 추가 설정이 필요할 수 있습니다
대규모 서버 측 데이터베이스 작업에 적합하지 않습니다
모바일 및 데스크톱 사용 사례에 가장 적합
반응형 프로그래밍 개념을 이해해야 합니다
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
Sqlcool
A database helper library for Sqflite. Forget about implementation details and focus on the business logic.
A stream of database change events is available. Inspired by Rethinkdb
import'dart:async';
import'package:sqlcool/sqlcool.dart';
StreamSubscription changefeed;
changefeed = db.changefeed.listen((change) {
print("Change in the database:");
print("Query: ${change.query}");
if (change.type == DatabaseChange.update) {
print("${change.value} items updated");
}
});
// Dispose the changefeed when finished using it
changefeed.cancel();
Reactive select bloc
The bloc will rebuild itself on any database change because of the reactive
parameter set to true:
import'package:flutter/material.dart';
import'package:sqlcool/sqlcool.dart';
class_PageSelectBlocStateextendsState<PageSelectBloc> {
SelectBloc bloc;
@overridevoidinitState() {
super.initState();
this.bloc = SelectBloc(
table: "items", orderBy: "name", reactive: true);
}
@overridevoiddispose() {
bloc.dispose();
super.dispose();
}
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar: AppBar(title: Text("My app")),
body: StreamBuilder<List<Map>>(
stream: bloc.items,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
// the select query has not found anythingif (snapshot.data.length == 0) {
returnCenter(child: constText("No data"));
}
// the select query has resultsreturnListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
var item = snapshot.data[index];
returnListTile(
title: GestureDetector(
child: Text(item["name"]),
onTap: () => someFunction()),
);
});
} else {
// the select query is still runningreturnCircularProgressIndicator();
}
}),
);
}
}
classPageSelectBlocextendsStatefulWidget{
@override_PageSelectBlocStatecreateState() => _PageSelectBlocState();
}
Database models
New in 4.0.0: define models that have database methods. The main
advantage of this is to use only typed model data and avoid the
type conversions from maps for every query. It directly plugs custom
models into the database. Example:
In schema.dart:
final carTable = DbTable("car")
..varchar("name")
..integer("max_speed")
..real("price")
..integer("year")
..boolean("is_4wd", defaultValue: false)
..foreignKey("manufacturer", onDelete: OnDelete.cascade);
final manufacturerTable = DbTable("manufacturer")..varchar("name");
In car_model.dart:
import'package:sqlcool/sqlcool.dart';
// the database schemaimport'schema.dart';
// another modelimport'manufacturer_model.dart';
classCarwithDbModel{
Car(
{this.id,
this.name,
this.maxSpeed,
this.price,
this.year,
this.is4wd,
this.manufacturer});
/// define some class propertiesfinalString name;
finalint maxSpeed;
finaldouble price;
finalDateTime year;
finalbool is4wd;
// this is a foreign key to another modelManufacturer manufacturer;
/// [DbModel] required overrides@overrideint id;
/// the [Db] used/// pass it your main db@overrideDbget db => db;
/// the table schema representation/// check example/pages/dbmodels/schema.dart@overrideDbTableget table => carTable;
/// serialize a row to the database@overrideMap<String, dynamic> toDb() {
// we want the foreign key to be recordedassert(manufacturer?.id != null);
final row = <String, dynamic>{
"name": name,
"max_speed": maxSpeed,
"price": price,
"year": year.millisecondsSinceEpoch,
"is_4wd": is4wd,
"manufacturer": manufacturer.id
};
return row;
}
/// deserialize a row from database@overrideCarfromDb(Map<String, dynamic> map) {
final car = Car(
id: map["id"] asint,
name: map["name"].toString(),
maxSpeed: map["max_speed"] asint,
price: map["price"] asdouble,
year: DateTime.fromMillisecondsSinceEpoch(map["year"] asint),
is4wd: (map["is_4wd"].toString() == "true"),
);
// the key will be present only with join queries// in a simple select this data is not presentif (map.containsKey("manufacturer")) {
car.manufacturer =
Manufacturer().fromDb(map["manufacturer"] asMap<String, dynamic>);
}
return car;
}
/// Create a static join method for conveniencestaticFuture<List<Car>> selectRelated({String where, int limit}) async {
final cars = List<Car>.from(
awaitCar().sqlJoin(where: where, limit: limit, verbose: true));
return cars;
}
}
Then use the models:
/// car is an instance of [Car]await car.sqlInsert();
await car.sqlUpdate();
await car.sqlUpsert();
await car.sqlDelete();
final cars = Car.selectRelated(where: "speed>200");
// foreign keys are retrieved as model instancesprint(cars[0].manufacturer.name);