이전 글 - 메모 입력 화면 만들기

메모 작성 위젯에서 메모를 입력하고, 저장 버튼을 누르면 이전 화면으로 메모 내용을 전달하는 것 까지 구현했다.

작성한 메모 내용을 유지하기 위해서는 로컬 또는 서버에 메모 내용을 저장해야 한다.

현재 만들고 있는 앱의 경우에는 이용자를 식별할 수 있는 값 (ex. 가입 ID )이 없으므로 로컬에 데이터를 저장하도록 구상했다.

 

1. 패키지 설치

텍스트 형식의 데이터만 저장할 예정이므로, 규모가 큰 데이터베이스가 요구되지 않기 때문에 SQLite 를 사용해서 메모 데이터를 저장해보려고 한다.

먼저, 작성 중인 플러터 앱 > pubspec.yaml 파일에 아래 패키지를 추가한다.

dependencies:
  flutter:
    sdk: flutter
  sqflite: ^2.2.0
  path: ^1.8.2

 

2. 데이터 베이스 초기화

아래 코드는 데이터 베이스의 todoList 라는 이름의 테이블을 검색한 후

데이터가 있으면 db를 반환하고, 데이터가 없는 경우에는 todoList 라는 이름의 테이블을 만들어서 반환하는 역할을 한다.

테이블의 구조는 id, date(메모를 저장할 날짜), content(메모 내용), status(완료 여부) 로 구성되어 있다.

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

class DatabaseHelper {
  static final DatabaseHelper instance = DatabaseHelper._init();

  static Database? _database;

  DatabaseHelper._init();

  Future<Database> get database async {
    if (_database != null) return _database!;

    _database = await _initDB('');
    return _database!;
  }

  Future<Database> _initDB(String filePath) async {
    final dbPath = await getDatabasesPath();
    final path = join(dbPath, filePath);

    return await openDatabase(path, version: 1, onCreate: _createDB);
  }

  Future _createDB(Database db, int version) async {
    const id = 'INTEGER PRIMARY KEY AUTOINCREMENT';
    const date = 'TEXT NOT NULL';
    const content = 'TEXT NOT NULL';
    const status = 'BOOL NOT NULL';

    await db.execute('''
    CREATE TABLE todoList (
      id $id,
      date $date,
      content $content,
      status $status,
    )
    ''');
  }

  Future close() async {
    final db = await instance.database;
    db.close();
  }
}

 

3. CRUD 정의하기

2번 과정이 완료되었다면, 데이터 베이스 읽기, 쓰기, 수정, 삭제를 위한 코드를 작성한다.

// 데이터 베이스 동작 (2번) 코드 생략

  Future<int> createTodo(String date, String content, int status) async {
    final db = await instance.database;

    final data = {'date': date, 'content': content, 'status': 0};
    return await db.insert('todoList', data); // return : auto-increment value
  }

  Future<List<Map<String, dynamic>>> readAllTodo() async {
    final db = await instance.database;
    return await db.query('todoList');
  }

  Future<int> updateTodo(
      int id, String date, String content, int status) async {
    final db = await instance.database;

    final data = {'date': date, 'content': content, 'status': status};
    return await db.update('todoList', data, where: 'id = ?', whereArgs: [id]);
  }

  Future<int> deleteTodo(int id) async {
    final db = await instance.database;

    return await db.delete('todoList', where: 'id = ?', whereArgs: [id]);
  }

데이터베이스 쓰기 (createTodo) 는 날짜를 문자형으로 받고 완료 여부 (status)를 0 (false) 로 한 후 저장한다.

데이터베이스 읽기 (readAllTodo) 는 todoList 에 있는 모든 데이터를 반환한다.

데이터베이스 수정 (updateTodo)은 데이터 id, 날짜, 내용, 완료 여부를 받고 해당하는 데이터 id에 내용을 업데이트 한다.

데잍처베이스 삭제(deleteDto) 는 데이터 id를 받아 해당 데이터를 삭제한다.

 

CRUD 로직 구현이 완료되었으면 달력 위젯으로 돌아가서 메모한 내용과 날짜를 DB에 넣어보고 제대로 출력이 되는지 확인하자

그 전에, 하나 수정해야 할 부분이 있다.

database.dart 파일을 lib > sqlite 폴더 아래에 생성했는데, 이 경우 getDatabasePath() 에서 코드 동작이 멈춰버린다.

내 경우에는 database.dart 파일을 lib 폴더 하위에 위치시켜 해결했으나, 다른 폴더로 넣어둬야 한다면 파일 경로 관련 문제를

먼저 해결해야 할 것이다.

 

4. DB 에 메모 데이터 쓰기, 읽기

floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final result = await Navigator.push(context,
              MaterialPageRoute(builder: (context) => const NewWidget()));
          if (result != null) {
            _memoDay = _selectedDay.toString().split(' ')[0];
            
            // db 입력
            print('db 입력 시작 : $_memoDay $result');
            await DatabaseHelper.instance.createTodo(_memoDay, result);
            print('db 입력 완료');

            setState(() {});
          }
        },

이전에 구현한 기능을 일부 수정한다.

result 변수를 출력하는 대신, 데이터베이스 쓰기 함수 변수에 생성 날짜와 내용을 넣어주면 데이터베이스 입력이 완료된다.

DB에 쓰여진 데이터를 보기 위해서는 아래와 같이 DB에서 데이터를 불러오는 코드를 작성한다.

 

class _MyHomePageState extends State<MyHomePage> {
  late List<Map<String, dynamic>> _todoList;
  
  @override
  void initState() {
    super.initState();
    _requestDateTodoList();
  }
  
  Future<void> _requestDateTodoList() async {
 	print('데이터 불러오는 중');
    final data = await DatabaseHelper.instance.readAllTodo();
    print('데이터 불러오기 완료 $data');
    setState(() {
      _todoList = data;
    });
  }

 

DB에 입력된 데이터 확인 결과 화면

정상적으로 데이터를 가져오는 것을 확인했으므로 해당 데이터들을 화면에 표기해야 한다.

이를 위해서 메모 데이터를 불러오는 함수를 일부 수정한 후, 아래와 같이 FutureBuilder 를 사용하여 데이터를 표현한다.

// 수정 전
Future<void> _requestDateTodoList() async {
    final data = await DatabaseHelper.instance.readAllTodo();
	
    setState(() {
    	_todoList = data;
    })
  }


// 수정 후
Future<List<Map<String, dynamic>>> _requestDateTodoList() async {
    final data = await DatabaseHelper.instance.readAllTodo();
    return data;
  }
  
  
 // 호출한 데이터를 UI에 표기 하는 코드
 FutureBuilder(
    future: _requestDateTodoList(),
    builder: (context, snapshot) {
      if (snapshot.connectionState == ConnectionState.waiting) {
        return const CircularProgressIndicator();
      } else {
        final List<Map<String, dynamic>> items = snapshot.data!;

        return Expanded(
          child: ListView.builder(
              itemCount: items.length,
              itemBuilder: (context, index) {
                final item = items[index];
                return Text(
                  item['content'],
                  style: const TextStyle(fontSize: 24),
                );
              }),
        );
      }
    })

화면에 데이터가 표현된 모습

스타일이 마음에 들지는 않지만 일단 표현에 성공했다. 

다음으로는 메모를 작성하는 화면을 수정할 예정인데, 반복 버튼을 누르면 특정 UI 가 등장하는 방식을 해 볼 예정이다.