티스토리 뷰

재즐보프님의 Flutter강의를 보고 배운 내용을 정리하는 게시물입니다.

www.youtube.com/watch?v=Yt-DjG5b4iA&list=PLnIaYcDMsScxP2Nl8pEbmI__wkF0YVu0a


 저번 포스트에  레이아웃을 설명하는 부분을 모두 뜯어보았으니 이제 텍스트를 저장하고 넘겨주는 방식에 대하여 자세하게 알아봅시다!👏

 

먼저 재즐보프님 강의에서 메모를 저장할 때 SQLite를 이용하시는데 이와 관련된 내용을 먼저 보겠습니다!

 

 SQLite란?

어떤 것에 대한 설명을 찾아볼 때는 이전에도 그랬듯이 공식 문서를 찾아봐야겠죠. 아래 링크를 통해 찾아낸 설명에 대하여 알아봅시다.

www.sqlite.org/index.html

 

SQLite Home Page

SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. SQLite is the most used database engine in the world. SQLite is built into all mobile phones and most computers and comes bu

www.sqlite.org

SQLite is an embedded SQL database engine. Unlike most other SQL databases, SQLite does not have a separate server process. SQLite reads and writes directly to ordinary disk files. A complete SQL database with multiple tables, indices, triggers, and views, is contained in a single disk file. The database file format is cross-platform - you can freely copy a database between 32-bit and 64-bit systems or between big-endian and little-endian architectures. These features make SQLite a popular choice as an Application File Format. SQLite database files are a recommended storage format by the US Library of Congress. Think of SQLite not as a replacement for Oracle but as a replacement for fopen()

 

많은 정보를 찾고 싶다면 SQLite 홈페이지에 직접 들어가서 확인해보시면 좋을 것 같습니다.

내용을 정리해보자면, 응용프로그램에 임베디드되어 동작되는 DBMS이고 따로 서버 프로세스를 가지는 것이 아니라서 사용하기 편리하다는 장점을 가졌습니다.

 

그러면 이것을 Flutter에 어떻게 적용시키는 지 알아봅시다.


 Flutter에서 사용하는 SQLite

기본적으로 Flutter 공식문서를 확인해봅시다.

 

If you are writing an app that needs to persist and query large amounts of data on the local device, consider using a database instead of a local file or key-value store. In general, databases provide faster inserts, updates, and queries compared to other local persistence solutions.

Flutter apps can make use of the SQLite databases via the sqflite plugin available on pub.dev. This recipe demonstrates the basics of using sqflite to insert, read, update, and remove data about various Dogs.

 

sqflite 플러그인을 이용하여서 SQLite databases를 사용하게 됩니다.

 

 

첫 번째, Dependencies를 추가합니다.

dependencies:
  flutter:
    sdk: flutter
  sqflite:
  path:

이 때, 추가되는 dependencies는 sqflite, path로 총 2가지입니다.

sqflite dependencies는 SQLite database에 접근하여 처리할 수 있는 함수와 클래스들을 사용할 수 있게 해줍니다.

path dependencies는 disk에 database를 저장할 수 있는 위치를 정의하는 함수를 사용할 수 있게 해줍니다.

 

그리고 다음과 같이 import해줍니다.

import 'dart:async';

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

async에 대한 내용은 조금 있다가 보도록 합시다!

 

 

두 번째, 우리가 저장해야하는 Data에 해당하는 것들을 정의해줍니다.

class Memo {
  final String id;
  final String title;
  final String text;
  final String createTime;
  final String editTime;

  Memo({this.id, this.title, this.text, this.createTime, this.editTime});

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'title': title,
      'text': text,
      'createTime': createTime,
      'editTime': editTime,
    };
  }
  
  @override
  String toString() {
    return 'Memo{id: $id, title: $title, text: $text, createTime: $createTime, editTime: $editTime}';
  }
}

Memo 클래스를 만들고 기본적인 생성자를 만들어주고 String으로 바꾸고, Map으로 바꿔주는 Method를 정의합니다.

Flutter 공식 문서에는 이 부분이 따로 없지만 재즐보프님 강의에서는 이 부분을 따로 적어주셨습니다.

Map으로 바꾸어 id, title 등을 Database에 넣고 그 정보들을 가져오기 위함입니다.

 

 

세 번째와 네 번째, Database를 열어주고 Table을 만든다.

참고로 SQLite를 Flutter로 여는 과정 중 세 번째부터 마지막까지는 재즐보프님께서 한 Class에 넣어서 구현하셨습니다.

Future<Database> get database async {
    if ( _db != null ) return _db;
    _db = openDatabase(
      join(await getDatabasesPath(), 'memos.db'),
      onCreate: (db, version) {
        return db.execute(
          "CREATE TABLE memos(id TEXT PRIMARY KEY, title TEXT, text TEXT, createTime TEXT, editTime TEXT)",
        );
      },
      version: 1,
    );
    r

Flutter 공식문서에는 다음과 같이 작성되어있습니다.

 

Before reading and writing data to the database, open a connection to the database. This involves two steps:

  1. Define the path to the database file using getDatabasesPath() from the sqflite package, combined with the join function from the path package.
  2. Open the database with the openDatabase() function from sqflite.

1번을 통해 코드에서 getDatabasePath로 Database에 해당하는 path를 정의하고 join function을 이용합니다.

이와 같은 Database를 openDatabase function을 이용하여 열어줍니다.

openDatabase function은 다음과 같이 정의됩니다.

join function은 다음과 같이 정의되니 join을 통해 path를 전달해주는 걸 알 수 있습니다.

또한 Create Function은 db와 version을 전달하는 걸 알 수 있습니다.

재즐보프님은 여기서 db를 return하여 Create Function의 첫 번째 인자로 넣어주고, version 인자도 전달해주셨습니다.

 

그런데 함수에 Future와 async라는 것이 붙어있는 데 이것도 지나치지 말고 알아봅시다!

 

 Future 함수?

이것은 Flutter 공식 문서가 아니라 Dart 공식 문서를 찾아보았습니다.

(출처 : dart.dev/codelabs/async-await)

 

먼저 Future란 비동기식 연산의 결과를 나타낸다고 합니다. 

어떤 시점에 해당 연산이 수행 완료되었을 때 결과를 반환해주면 그 반환하는 값을 가져온다는 의미입니다.

재즐보프님 강의를 토대로 비유하자면, DatabasePath를 요청해놓으면, 그 요청이 나중에 값을 반환할 때 그 값을 받아오는 함수가 되는 것입니다.

 

이와 같이 미래에 반환되는 프로그램을 만들 때에는(비동기식 처리) async를 붙혀주어 표현합니다.

Database를 가져오려고 했는데 그 요청이 얼마나 걸릴 지 모르는 것이니 비동기식 처리로 구현한 셈입니다.

 

await는 단어에서부터 알 수 있듯이 Future 작업이 끝날 때까지 기다렸다가 반환된 값을 받아오는 것입니다.

 

 

다섯 번째, Database에 추가하기

Future<void> insertMemo(Memo memo) async {
    final db = await database;

    await db.insert(
      TableName,
      memo.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

먼저 database를 기다려주고 그 database에 추가할 Table이름과 추가할 memo를 Map으로 변환하여 전달합니다.

이 때, conflictAlgorithm을 이용하여 똑같은 내용의 메모가 추가되면 replace(덮어쓰기)가 될 것입니다.

 

 

여섯 번째, Database에 들어있는 내용 가져오기

 Future<List<Memo>> memos() async {
   final db = await database;
   final List<Map<String, dynamic>> maps = await db.query('memos');

   return List.generate(maps.length, (i) {
     return Memo(
       id: maps[i]['id'],
       title: maps[i]['title'],
       text: maps[i]['text'],
       createTime: maps[i]['createTime'],
       editTime: maps[i]['editTime'],
     );
  });
}

먼저 가져오는 SQLite에 저장되어 있는 메모들이겠죠?그렇기 때문에 Future 형태로 가져오는 Data의 반환형태는 List<Memo>일 것입니다.함수를 보면 database에 있는 Table인 'memos'로부터 내용을 가져오고 이것들을 반환합니다.근데 이 때, 반환하는 방법이 특이하죠?

 

 List.generate란?

당연히 Flutter 공식 문서를 참고했습니다.

Creates a list with length positions and fills it with values created by calling generator for each index in the range 0 .. length - 1 in increasing order.

 

list형태를 만드는 것인데 generator를 불러내어 오름차순으로 List를 만들어 반환하는 함수입니다.

재즐보프님 강의에서 작성해주신 코드를 보면, 리스트들 안에 id, title, text, createTime, editTime을 넣어서 반환하는 것을 알 수 있네요.

 

 

일곱 번째, Database에 들어있는 내용 수정하기

Future<void> updateMemo(Memo memo) async {
  final db = await database;

  await db.update(
    TableName,
    memo.toMap(),
    where: "id = ?",
    whereArgs: [memo.id],
  );
}

너무나도 당연한 방식으로 update를 진행합니다. memo를 Map으로 변환하여 다시 수정하는 것을 확인할 수 있네요.

 

 

여덟 번째, Database에 들어있는 내용 삭제하기

Future<void> deleteMemo(String id) async {
  final db = await database;

  await db.delete(
    TableName,
    where: "id = ?",
    whereArgs: [id],
  );
}

이것도 너무나도 쉽게 삭제할 수 있습니다.

 

 

확실히 Database를 만들 때, 업데이트가 가장 복잡한 것 같습니다. (React로 웹을 만들 때에도 update가 가장 어려웠었는데 말이죠 ㄷㄷ)

 

자, 이렇게 SQLite를 메모 앱과 연결시키는 방법을 재즐보프님 강의 코드를 토대로 살펴보았습니다.

 

생각보다 뭐가 많은 듯 적은 듯 하죠?

 

이제 이 위의 모든 operation을 한 Class에 넣어 정의해줄 겁니다.

final String TableName = 'memos';

class DBHelper {
  var _db;

  Future<Database> get database async {
    if ( _db != null ) return _db;
    _db = openDatabase(
      join(await getDatabasesPath(), 'memos.db'),
      onCreate: (db, version) {
        return db.execute(
          "CREATE TABLE memos(id TEXT PRIMARY KEY, title TEXT, text TEXT, createTime TEXT, editTime TEXT)",
        );
      },
      version: 1,
    );
    return _db;
  }

  Future<void> insertMemo(Memo memo) async {
    final db = await database;

    await db.insert(
      TableName,
      memo.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  Future<List<Memo>> memos() async {
    final db = await database;

    final List<Map<String, dynamic>> maps = await db.query('memos');

    return List.generate(maps.length, (i) {
      return Memo(
        id: maps[i]['id'],
        title: maps[i]['title'],
        text: maps[i]['text'],
        createTime: maps[i]['createTime'],
        editTime: maps[i]['editTime'],
      );
    });
  }

  Future<void> updateMemo(Memo memo) async {
    final db = await database;

    await db.update(
      TableName,
      memo.toMap(),
      where: "id = ?",
      whereArgs: [memo.id],
    );
  }

  Future<void> deleteMemo(String id) async {
    final db = await database;

    await db.delete(
      TableName,
      where: "id = ?",
      whereArgs: [id],
    );
  }
}

그렇다면 edit.dart파일에서 만들었던 레이아웃에 작성된 것들(메모 제목과 내용)을 가져오는 함수를 만들어야겠죠?

저번에 edit.dart의 레이아웃에서 appBar를 넣었던 게 기억나실 겁니다.

appBar에는 IconButton을 두 개 넣었었는데, 그 중 한 개가 저장 버튼의 아이콘을 갖고 있었죠.

 

IconButton도 어쨌거나 이름에서도 알 수 있듯이 Button이기 때문에 onPressed 속성을 갖게 됩니다.

onPressed에 넣어줄 함수를 작성합시다.

Future<void> saveDB() async {
  DBHelper sd = DBHelper();

  var fido = Memo(
    id: Str2sha512(DateTime.now().toString()),
    title: this.title,
    text: this.text,
    createTime: DateTime.now().toString(),
    editTime: DateTime.now().toString(),
  );

  await sd.insertMemo(fido);

  print(await sd.memos());
}
  
String Str2sha512(String text) {
  var bytes = utf8.encode(text);
  var digest = sha512.convert(bytes);
  return digest.toString();
}

앞에서 본 것처럼 DBHelper 클래스를 가져오고 우리가 직접 TextField에 작성한 title과 text를 전달하는 부분입니다.

다르 것들은 다 이해가 되시겠지만 id부분에 특이한 내용이 있지요?

 

SQLite에 Data를 저장할 때, 항상 id를 가변적으로 바꿔주어야 하는데 이를 쉽게 하기 위해 재즐보프님께서 sha512를 소개해주셨습니다. sha512 암호화 방식을 통해 우리가 이제 메모한 것들을 저장할 때마다 프로그램이 알아서 id를 암호화하여 전달해줄 겁니다.


사실 2개의 포스팅으로 모든 내용을 커버할까 생각하다가 제가 Flutter만 공부하고 있는 중이 아닌지라 시간이 부족하여 다음 포스트에 마무리짓도록 하겠습니다! 👏

 

마지막 포스트에는 이제 SQLite에 저장한 것들을 가져와서 Layout 상에 나타내는 부분에서 재즐보프님께서 강의에서 알려주신 정보들과 제가 개인적으로 찾아본 정보들을 합하여 올리고 완성된 메모 앱을 간단하게 영상으로 올려보겠습니다! 🎀

댓글
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Total
Today
Yesterday
최근에 올라온 글