오늘은 Flutter 에서 SQLite를 쓸 일이 있어 글을 남긴다..
공식 홈페이지
https://docs.flutter.dev/cookbook/persistence/sqlite
Persist data with SQLite
How to use SQLite to store and retrieve data.
docs.flutter.dev
여기에 보면 공식 홈페이지 보면 설명이 잘 되어있다 참고 바란다.!
나는 사용하기 전에 공식 사이트 말고 한글 블로그도 참고하면서 활용했다.
https://dalgonakit.tistory.com/116
Flutter Database (SQLite) 사용하기 (1)
프로그램의 꽃은 데이터 저장! 그 저장과 불러오기를 쉽게 도와주는 것이 데이터베이스 인데요 이번 강좌에서는 데이터베이스의 한 종류인 SQLite를 써서 개발하는 방법을 소개해 볼까 합니다.
dalgonakit.tistory.com
1. 일단 개발하기 전 Package 를 추가해야 한다!
dependencies:
flutter:
sdk: flutter
sqflite: ^1.1.6
path_provider: ^1.2.0
path: ^1.6.2
2. DataBase 헬퍼를 구현한다.
widget 코드에서 매번 database를 초기화하고 불러오고 처리하면 메모리적으로 비효율적이다
이유를 모르겠으면 싱글턴 패턴을 공부 바란다.
3. 데이터 베이스 초기화 진행
/** DB를 새로 생성한다 **/
Future initDB() async {
// 어플 내 저장경로에 만든다
// Directory documentsDirectory = await getApplicationDocumentsDirectory();
String dbPath = await ExternalPath.getExternalStoragePublicDirectory(ExternalPath.DIRECTORY_DCIM);
// 해당 경로에 DB가 없으면 새로 만든다
String path = join(dbPath, 'gaugehistory.db');
// UserInfo테이블을 생성한다
String createUserInfoTable = '''
CREATE TABLE $UserInfo(
id INTEGER PRIMARY KEY autoincrement,
project TEXT,
userid TEXT,
password TEXT,
auth TEXT,
username TEXT,
company TEXT
)
''';
// 게이지 Value History를 생성한다
/* 이거 잠시 보류 컬럼이 과거랑 현재랑 안맞음 */
String createGaugeValueHistoryInfo = '''
CREATE TABLE $GaugeValueHistory(
id INTEGER PRIMARY KEY autoincrement,
userLine TEXT,
gaugevalue TEXT,
saveDay TEXT,
gaygeNo TEXT,
value REAL,
st REAL,
ed REAL,
imgPath TEXT,
loginId TEXT,
event TEXT
)
''';
// DB를 생성한다
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute(createUserInfoTable);
await db.execute(createGaugeValueHistoryInfo); // history
// await db.execute(createGaugeBoard);
// 히스토리는 컬럼 문제로 보류한다
},
onUpgrade: (db, oldVersion, newVersion){}
);
}
path_provider에서 지원하는 getApplicationDocumentsDirectory() 함수를 통해서 적당한 위치를 가져오고 경로를 만들어도된다 그렇지만 나는 개발할 때 폰 디렉토리 안에서 가져와야 하기 때문에 저 함수를 쓰면서 DCIM으로 위치를 정했다.
sqflite에서 지원하는 openDatabase() 함수를 이용해서 경로를 불러오고 만약 없다면 onCreate 함수가 실행된다.
migration이 필요하다면 onUpgrade 함수를 구현하면 된다
4. Factory
이걸 하기 전에 Factory가 뭔지 알아야 한다.
Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class
dart 도큐먼트에 나오는 factory에 대한 설명이다. 새로운 인스턴스를 생성하지 않는 생성자를 구현할 때 factory 키워드를 사용하라고 한다.
factory는 새로운 인스턴스를 생성하고 싶지 않을 때 사용하는 생성자이다. 이와 같은 개념은 새로운 게 아니다. 소프트웨어 디자인 패턴 중 '싱글톤 패턴'을 따른 것이다.
싱글톤 패턴(singleton-pattern): 소프트웨어 디자인 패턴에서 싱글톤 패턴을 따르는 클래스는, 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 이와 같은 디자인 유형을 싱글톤 패턴이라고 한다.
이제 코드를 보여주겠다.
class DBHelper {
DBHelper._();
static final DBHelper _db = DBHelper._();
factory DBHelper() => _db;
static Database? _database = null;
Future<Database?> get database async {
if(_database != null)
return _database;
_database = await initDB();
return _database;
}
DBHelper 클래스를 불러오기 편하게 하기 위해서 factory로 가져오게 만들었다.
그리고 Database를 가져오는 get 키워드를 사용해서 없으면 initDB() 함수를 호출하고 있으면 반환하게 만들었다
initDB의 내부 과정에서 Database를 가져올 때, 파일 접근을 하게 되니 비동기로 Future<Database>로 반환하게 된다.
5. 활용(CRUD)
나는 총 3편까지 다룰 예정이다 지금은 1편에는 생성하고, 읽는 거까지 기능을 만들 거다
설명하기에 앞서 나는 assets에서 txt파일기반으로 생성하고 읽는다.
생성하는 거는 3에서 createUserInfoTable 를 보면 된다. 난 로딩하는 시점에 테이블을 생성해야해서 이렇게 했다 !
난 일단 테스트용 임시 데이터를 생성했다.
void insertTesthistoryData() async {
final db = await database;
try {
var res = await db?.rawInsert
('INSERT INTO $GaugeValueHistory '
'('
'userLine, '
'gaugevalue, '
'saveDay, '
'gaygeNo, '
'value, '
'st, '
'ed, '
'imgPath, '
'loginId, '
'event'
') VALUES (?,?,?,?,?,?,?,?,?,?)',
["SN2333",
"Gauge Joo",
"2023-07-12",
"3",
1.15,
1.1,
1.4,
"이미지경로",
"sh1192.joo",
"sqllite이벤트"
],
);
}catch(e){
print("$e");
}
}
문법은 기본적인 SQLite와 같고, 물음표(?)가 들어가는 부분은 뒤에 포함된 파마미터인 배열([ ])의 원소가 치환되는 방식으로 되어 있다.
dart에서는 굳이 물음표로 치환하지 않아도 '$변수' 를 이용하거나 '${ }' 방법으로도 대체할 수 있습니다. (나중에 파라미터 넣을 때)
이제 읽는 부분이다.
Future<List<ModelGaugeValue>> selectGaugeHistroy() async {
final db = await database;
var res = await db?.rawQuery('SELECT * FROM $GaugeValueHistory');
List<ModelGaugeValue> list = [];
try {
list = res!.isNotEmpty ? res.map((c) =>
ModelGaugeValue(
index: c['id'] as int,
userLine: c['userLine'] as String,
gaugevalue: c['gaugevalue'] as String,
saveDay: c['saveDay'] as String,
gaygeNo: c['gaygeNo'] as String,
value: c['value'] as double,
st: c['st'] as double,
ed: c['ed'] as double,
imgPath: c['imgPath'] as String,
loginId: c['loginId'] as String,
event: c['event'] as String,
)).toList() : [];
}catch(e){
print("$e");
}
return list;
}
SQLite가 List로 반환이 된다 그래서 모델을 리스트로 하고 위에서 생성한 테이블을 SELECT 해온다.
그다음 받아온 값 res!.isNotEmpty ? res.map((c) 일때 모델에 데이터 타입을 정해주고 list로 반환한다.
모델은 참고 바란다. 그리고 위에서 Create 할때 sql을 사용한 분이라면 varchar2 이런 게 익숙할 건데 text는 딱봐도 string 이고 real이 궁금할수도 있을 거다 real은 double이다
class modeltest {
final int index;
final String userLine;
final String gaugevalue;
final String saveDay;
final String gaygeNo;
final double? value;
final double? st;
final double? ed;
final String? imgPath;
final String loginId;
final String event;
ModelGaugeValue({
required this.index,
required this.userLine,
required this.gaugevalue,
required this.saveDay,
required this.gaygeNo,
required this.value,
required this.st,
required this.ed,
required this.imgPath,
required this.loginId,
required this.event,
});
6. 화면 구현
Widget cardview(){
gaugeController GaugeController = gaugeController();
dbhelper.DBHelperHistory dbHelperHistory = dbhelper.DBHelperHistory();
// Test용 DB테이블 생성 , 최초로 한개만 생성한다.
dbHelperHistory.selectGaugeHistroy().then((value) {
if(value.length == 0)
dbHelperHistory.insertTesthistoryData();
});
return FutureBuilder(
future: dbHelperHistory.selectGaugeHistroy(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
//해당 부분은 data를 아직 받아 오지 못했을 때 실행되는 부분
if (!snapshot.hasData) {
return CircularProgressIndicator(); // CircularProgressIndicator : 로딩 에니메이션
}
//error가 발생하게 될 경우 반환하게 되는 부분
else if (snapshot.hasError) {
return const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"loading Data",
style: TextStyle(
color: Colors.green,
fontSize: 22,
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w700),
),
);
}
// 데이터를 정상적으로 받아오게 되면 다음 부분을 실행하게 되는 부분
else {
return Column(
children: [
for (int j = snapshot.data.length - 1; j >= 0; j--)
if (UserInfoLogin.userid ==
snapshot.data[j].loginId)
graph ? card(j, snapshot.data) : cardGraph(j),
],
);
}
});
}
난 FutureBuilder를 사용했다. 내 코드를 간략하게 설명을 하자면
나는 .txt file 을 읽어서 사용자한테 보여준다
일단 dbhelper을 생성한다 그리고 select 함수를 불러온다. 성공했을 떄 (.then)
if문을 탄다 가져온 데이터의 길이가 0일 때 데이터를 insert 한다
그 다음 return에서 FutureBuilder 를 탄다 (비동기)
future에서 데이터를 Select하고 데이터는 snapshot에 담긴다
데이터가 없으면 if문을 타서 로딩 애니메이션을 보여준다
있으면 else를 타서 데이터의 길이만큼 그리고 사용자 아이디에 맞는 데이터를 뿌려준다.
끝!!!
오늘은 Create랑 Select를 설명했다.
내가 설명이 좀 부족해 도움이 될지는 모르지만 이런식으로 난 활용했다.
2편에서 봅시당 ~!~!
'Flutter' 카테고리의 다른 글
Flutter Package Sizer (2) | 2023.10.25 |
---|---|
StatefulWidget의 LifeCycle(생명주기) (2) | 2023.09.19 |
생성자와 널 세이프티 이해하기 (0) | 2023.07.22 |
Flutter Setstate ? (0) | 2023.07.18 |
Flutter SQLite(2) (1) | 2023.07.14 |