본문 바로가기
Flutter

[Flutter] 5분 만에 Sqflite 사용하기

by 아마도개발자 2024. 2. 17.

 

개인 프로젝트 중 Sqflite를 사용을 위해 pub.dev의 sqflite 공식 example을 참고하였는데, 필요 없는 내용이 너무 많아 한 눈에 보기가 힘들었고, 실질적으로 Sqflite를 연동하는 부분을 찾기가 힘들었다. 

 

Sqlite를 사용해 본 적이 있고, 빠르게 Sqflite를 프로젝트에 적용하기를 원하는 사람들을 위해 정말 빠르고 간단하게 Sqflite로 CRUD를 구현하는 방법을 소개하겠다.

 

 

우선 sqflitepath 패키지를 추가해준다.

  • sqflite: SQLite 데이터 베이스를 사용하기 위한 패키지
  • path: 파일 및 디렉토리 경로를 조작하기 위한 유틸리티 패키지

 

패키지 추가 이후에는 사용할 데이터의 클래스를 정의해 준다.

class Script {
  final int id;
  final String title;
  final String content;

  const Script({required this.id, required this.title, required this.content});

  // #1
  factory Script.fromJson(Map<String, dynamic> json) =>
      Script(id: json['id'], title: json['title'], content: json['content']);
  // #2
  Map<String, dynamic> toJson() =>
      {'id': id, 'title': title, 'content': content};
}

 

 

#1은 팩토리 메서드를 사용하여 JSON형식의 데이터를 받아 Script 클래스의 인스턴스를 생성하는 역할을 한다.

즉, JSON => 객체

 

#2는 반대로 Script 객체를 JSON형식으로 변환한다.

 

Class를 생성하였다면 이제 sqflite데이터베이스를 생성하고 사용해보자.

 

import 'dart:async';

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

import '../data/script_data.dart';

class SqliteService {
  static late Database _database;
  Future<Database> get database async {
    if (_database != null) {
      return _database;
    }

    _database = await initializeDB();
    return _database;
  }

  Future<Database> initializeDB() async {
    return await openDatabase(
      join(await getDatabasesPath(), 'database.db'),
      onCreate: (database, version) async {
        await database.execute(
          "CREATE TABLE Script(id INTEGER PRIMARY KEY AUTOINCREMENT, title NOT NULL, content NOT NULL)",
        );
      },
      version: 1,
    );
  }

  Future<int> addScript(Script script) async {
    final db = await database;
    return await db.insert("Script", script.toJson(),
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

  Future<List<Script>?> getAllScript() async {
    final db = await database;
    final List<Map<String, dynamic>> mapData = await db.query("table");
    if (mapData.isEmpty) return null;

    return List.generate(
        mapData.length, (index) => Script.fromJson(mapData[index]));
  }

  Future<int> updateScript(Script script) async {
    final db = await database;
    return await db.update(
      "Script",
      script.toJson(),
      where: 'id = ?',
      whereArgs: [script.id],
    );
  }

  Future<int> deleteScript(Script script) async {
    final db = await database;
    return await db.delete("Script", where: "id= = ?", whereArgs: [script.id]);
  }
}

 

기본적으로 클래스는 싱글톤 패턴을 사용하여 구현한다. 앱 전체에서 오직 하나의 Database인스턴스만을 사용하기 위함이다.

 

만약 싱글톤 패턴에 대한 이해가 없다면

 

import 'dart:async';

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

import '../data/script_data.dart';

class SqliteService {
  static late Database _database;
  Future<Database> get database async {
    if (_database != null) {
      return _database;
    }

    _database = await initializeDB();
    return _database;
  }

  Future<Database> initializeDB() async {
    return await openDatabase(
      join(await getDatabasesPath(), 'database.db'),
      onCreate: (database, version) async {
        await database.execute(
          "CREATE TABLE Script(id INTEGER PRIMARY KEY AUTOINCREMENT, title NOT NULL, content NOT NULL)",
        );
      },
      version: 1,
    );
  }
}

 

이 부분이 Database를 생성하는 부분이라고만 이해하고 있어도 사용에 무방하다. 'database.db'가 데이터를 저장할 db파일의 이름이고, `CREATE ~ NOT NULL`이 테이블의 스키마를 정의하는 쿼리이다.

 

이 정도의 코드만 작성하여도, 다른 클래스 혹은 위젯에서 SqliteService의 인스턴스를 호출하여 SqliteService클래스 내부의 함수들을 사용할 수 있게 된다.(addScript, updateScript 등)

 

getAllScript를 예로 들어보면

 

class DetailScreenContent extends StatefulWidget {
  const DetailScreenContent({
    super.key,
  });
  @override
  State<DetailScreenContent> createState() => _DetailScreenContentState();
}

class _DetailScreenContentState extends State<DetailScreenContent> {
  SqliteService sqliteService = SqliteService();

  @override
  Widget build(BuildContext context) {
    return SliverFillRemaining(
      child: Container(
        padding: EdgeInsets.all(defaultPadding),
        child: Column(
          children: [
            // ... 중략 ...
            FutureBuilder(
              future: sqliteService.getAllScript(),
              builder: (context, snapshot) {
                return ListView.builder(
                  itemBuilder: (context, index) {
                    return Text(snapshot.data![index].title);
                  },
                );
              },
            )
          ],
        ),
      ),
    );
  }
}

 

이런 형태로 sqflite에 저장된 데이터를 읽어 올 수 있다. 이 형태를 기본 템플릿으로 하여 좀 더 복잡한 로직의 구성이 가능하다.