플러터 Flutter SQLite 사용하기 – ToDo list 구현

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(삭제)을 하기 위한 화면을 만들고 각각의 기능을 구현하였습니다.

flutter-sqlite-todolist

데이터 입력 기능

이 코드는 앱에서 메모를 입력하고 추가하는 부분입니다. 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'), // 버튼에 표시될 텍스트입니다.
),
flutter-sqlite-todolist-update

데이터 삭제 기능

이 코드는 데이터를 삭제하기 위한 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'), // 버튼에 표시될 텍스트입니다.
),
flutter-sqlite-todolist-delete

전체 코드

전체적인 코드의 링크입니다. 각각의 기능을 확인하고 동작을 확인할 수 있습니다.

마무리

플러터의 SQLite는 낮은 수준의 쿼리를 직접 다루지만 Drift라는 패키지를 이용하여 데이터 모델링을 더 간편하게 만들수 있습니다. 관련된 글도 작성하였으니 필요하신분은 참고하시기 바랍니다. 감사합니다.

Leave a Comment