Flutter에서 SQLite는 내장 데이터베이스로 사용되며, 경량이면서 효율적인 관계형 데이터베이스 시스템입니다. SQLite는 파일 기반의 데이터베이스로, 서버가 아닌 로컬 기기에 저장됩니다. Flutter 개발에서 SQLite를 사용하면 앱 내에서 간단한 로컬 데이터 저장소를 구현할 수 있습니다. 주로 작은 규모의 데이터를 효율적으로 저장하고 관리할 때 사용됩니다. 이번 글은 Flutter SQlite를 사용하여 간단한 ToDo List앱을 만들면서 SQlite 사용법에 대해서 설명드리겠습니다.
목차
Flutter SQLite 사용 방법
의존성 설정
먼저 pubspec.yaml 파일에 종속성을 추가합니다. 이러한 종속성을 추가한 후 터미널에서 flutter pub get 명령을 실행하여 설치합니다.
- sqflite: SQLite 데이터베이스를 위한 Flutter 플러그인입니다.
- path: 파일 및 디렉토리 경로 처리를 위한 패키지입니다.
dependencies: flutter: sdk: flutter path: ^1.8.3 sqflite: ^2.0.0
데이터 모델정의
테이블을 생성하기 전에 저장해야 할 데이터를 정의합니다. id와 content을 포함한 클래스를 만들고 map형태로 변하는 메서드도 만들어준다.
class Todo { int? id; // Todo 항목의 고유 식별자 String content; // Todo의 내용 // 생성자 Todo({this.id, required this.content}); // Map으로 변환하는 메서드 Map<String, dynamic> toMap() { return { 'id': id, // id 필드 'content': content, // content 필드 }; } }
데이터베이스 생성
DatabaseHelper클래스를 만듭니다. 이 클래스는 SQLite 데이터베이스의 초기화 및 쿼리를 처리합니다. 각 메서드에 대한 설명은 다음과 같습니다:
- database: 이 메서드는 SQLite 데이터베이스의 인스턴스를 비동기적으로 반환합니다. 만약 이미 데이터베이스가 초기화되어 있다면, 기존의 인스턴스를 반환하고, 그렇지 않다면 initDatabase 메서드를 호출하여 데이터베이스를 초기화합니다.
- initDatabase: 이 메서드는 SQLite 데이터베이스를 초기화하고, ‘todos’ 테이블이 존재하지 않으면 생성합니다. 데이터베이스 파일 경로는 getDatabasesPath() 함수를 사용하여 얻어오며, 특정 버전 및 생성 콜백을 지정하여 데이터베이스를 엽니다. ‘todos’ 테이블은 메모의 ID 및 내용을 저장하기 위한 구조로 생성됩니다.
mport 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; class DatabaseHelper { static Database? _database; /// 데이터베이스 인스턴스를 가져오거나 새로운 인스턴스를 초기화합니다. Future<Database> get database async { if (_database != null) return _database!; _database = await initDatabase(); return _database!; } /// 데이터베이스를 초기화하고 'todos' 테이블이 없으면 생성합니다. Future<Database> initDatabase() async { // 데이터베이스 파일 경로를 얻어옵니다 ('todo.db'). String path = join(await getDatabasesPath(), 'todo.db'); // 지정된 버전 및 생성 콜백으로 데이터베이스를 엽니다. return await openDatabase( path, version: 1, onCreate: (db, version) async { // 'todos' 테이블을 생성하는 SQL 쿼리를 실행합니다. await db.execute(''' CREATE TABLE todos( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT ) '''); }, ); } }
데이터 삽입
이 메서드는 데이터를 삽입하는 역할을 합니다.
/// 'todos' 테이블에 메모를 삽입합니다. Future<int> insertTodo(Todo todo) async { // 데이터베이스 인스턴스를 가져옵니다. Database db = await database; // 'todos' 테이블에 메모 데이터를 삽입하고 삽입된 ID를 반환합니다. return await db.insert('todos', todos.toMap()); }
데이터 조회
이 메서드는 데이터를 조회하는 역할을 합니다.
/// 'todos' 테이블에서 모든 Todo를 가져옵니다. Future<List<Todo>> getTodos() async { // 데이터베이스 인스턴스를 가져옵니다. Database db = await database; // 'todos' 테이블에서 모든 행을 쿼리합니다. List<Map<String, dynamic>> maps = await db.query('todos'); // 쿼리 결과에서 Todo 객체의 목록을 생성합니다. return List.generate(maps.length, (index) { return Todo( id: maps[index]['id'], content: maps[index]['content'], ); }); }
데이터 업데이트
이 메서드는 데이터를 업데이트하는 역할을 합니다.
/// 'todos' 테이블에서 Todo를 업데이트합니다. Future<void> updateTodo(Todo todo) async { // 데이터베이스 인스턴스를 가져옵니다. Database db = await database; // 'todos' 테이블에서 Todo를 업데이트합니다. await db.update( 'todos', // 테이블 이름 todo.toMap(), // 업데이트할 데이터를 Map 형태로 변환 where: 'id = ?', // 업데이트할 조건 설정 whereArgs: [todo.id], // 조건 값 설정 ); }
데이터 삭제
이 메서드는 데이터를 삭제하는 역할을 합니다.
/// 'todos' 테이블에서 Todo를 삭제합니다. Future<void> deleteTodo(int id) async { // 데이터베이스 인스턴스를 가져옵니다. Database db = await database; // 'todos' 테이블에서 Todo를 삭제합니다. await db.delete( 'todos', // 테이블 이름 where: 'id = ?', // 삭제할 조건 설정 whereArgs: [id], // 조건 값 설정 ); }
플러터 CRUD 앱 만들기
데이터의 Create(생성), Read(읽기), Update(갱신), Delete(삭제)을 하기 위한 화면을 만들고 각각의 기능을 구현하였습니다.
데이터 입력 기능
이 코드는 앱에서 메모를 입력하고 추가하는 부분입니다. TextField와 ElevatedButton 위젯을 사용하였습니다.
// 텍스트를 입력받는 TextField 위젯 TextField( controller: contentController, // 텍스트 필드의 상태를 관리하는 컨트롤러 decoration: InputDecoration(hintText: 'Enter your todo'), // 입력 필드의 외관을 설정, 힌트 텍스트 추가 ), // todo를 추가하는 버튼 ElevatedButton( onPressed: () async { // 텍스트 필드에서 입력된 내용을 가져옴 String content = contentController.text; // 입력된 내용이 비어있지 않은 경우에만 todo를 추가 if (content.isNotEmpty) { // DBHelper를 사용하여 새로운 todo를 데이터베이스에 삽입 await widget.dbHelper.insertTodo(Todo(content: content)); // 텍스트 필드를 초기화하여 새로운 메모를 입력하기 위해 준비 contentController.clear(); // 화면을 다시 그리도록 setState 호출하여 메모 목록을 갱신 setState(() { todos = widget.dbHelper.getTodos(); }); } }, child: Text('Add Todo'), // 버튼에 표시될 텍스트 ),
데이터 조회 기능
이 코드는 FutureBuilder 위젯을 사용하여 비동기적으로 데이터를 빌드하고 화면에 표시합니다.
FutureBuilder( future: todos, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { // 데이터가 로딩 중일 때, CircularProgressIndicator를 표시합니다. return CircularProgressIndicator(); } else if (snapshot.hasError) { // 데이터 로딩 중에 에러가 발생했을 때, 에러 메시지를 표시합니다. return Text('Error: ${snapshot.error}'); } else { // 데이터 로딩이 완료되면 Todo 리스트를 가져옵니다. List<Todo> todoList = snapshot.data as List<Todo>; return Expanded( child: ListView.builder( itemCount: todoList.length, itemBuilder: (context, index) { return ListTile( title: Text(todoList[index].content), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ // 'Edit' 아이콘 클릭 시 _editTodo 메서드를 호출합니다. IconButton( icon: Icon(Icons.edit), onPressed: () { _editTodo(context, todoList[index]); }, ), // 'Delete' 아이콘 클릭 시 _deleteTodo 메서드를 호출합니다. IconButton( icon: Icon(Icons.delete), onPressed: () { _deleteTodo(context, todoList[index]); }, ), ], ), ); }, ), ); } }, )
데이터 업데이트 기능
이 코드는 업데이트를 하기 위한 ElevatedButton 위젯입니다.
ElevatedButton( onPressed: () async { // 텍스트 필드에서 입력된 내용을 가져옵니다. String content = contentController.text; // 입력된 내용이 비어있지 않을 경우에만 실행합니다. if (content.isNotEmpty) { // Todo 객체의 content를 업데이트합니다. todo.content = content; // 데이터베이스에서 해당 Todo를 업데이트합니다. await widget.dbHelper.updateTodo(todo); // 현재 화면을 닫고 이전 화면으로 이동합니다. Navigator.pop(context); // 화면을 갱신하기 위해 setState를 호출하여 Todos를 다시 불러옵니다. setState(() { todos = widget.dbHelper.getTodos(); }); } }, child: Text('Save'), // 버튼에 표시될 텍스트입니다. ),
데이터 삭제 기능
이 코드는 데이터를 삭제하기 위한 ElevatedButton 위젯 입니다.
ElevatedButton( onPressed: () async { // 현재 Todo의 ID를 이용하여 데이터베이스에서 해당 Todo를 삭제합니다. await widget.dbHelper.deleteTodo(todo.id!); // 현재 화면을 닫고 이전 화면으로 이동합니다. Navigator.pop(context); // 화면을 갱신하기 위해 setState를 호출하여 Todos를 다시 불러옵니다. setState(() { todos = widget.dbHelper.getTodos(); }); }, child: Text('Delete'), // 버튼에 표시될 텍스트입니다. ),
전체 코드
전체적인 코드의 링크입니다. 각각의 기능을 확인하고 동작을 확인할 수 있습니다.
마무리
플러터의 SQLite는 낮은 수준의 쿼리를 직접 다루지만 Drift라는 패키지를 이용하여 데이터 모델링을 더 간편하게 만들수 있습니다. 관련된 글도 작성하였으니 필요하신분은 참고하시기 바랍니다. 감사합니다.
훌륭한 예제입니당
감사합니다!