Flutter Hive 사용이유 + 방법(feat. Base64)

Uno·2023년 9월 4일
0

flutter

목록 보기
8/16
post-thumbnail

기술 선택과정

Flutter 에서는 여러가지 Local Database 라이브러리가 있습니다.

  • Sqlflite
  • Floor
  • Drift
  • Hive
  • Sembast

“shared_preferences” vs LocalDB

현재 제 요구사항은 Android & iOS 디바이스에서 사용하는 Local Database 이면 됩니다. 앱의 현 상황을 봤을 때, 웹으로 접근이 가능한 형태입니다. 비록 현재 요구사항으로 볼 때, 모바일 디바이스만 대응하면 되지만, 추후에 어떤식으로 변경될지 모른다고 판단해서 크로스 플랫폼을 지원해야한다고 생각했습니다.

“크로스 플랫폼” 을 지원하는 DB 를 선택하자.

그 중에서, 제가 LocalDB 를 사용하는 케이스가 관계형을 구축할만큼 복잡하지 않았습니다.

조건)

  1. Base64 를 감당할 수 있는 DB
  2. 사용자와 관련된 설정값은 shared_preference 를 이용

Shared Prefernce 는 Android 의 경우, 1MB 이하 iOS 제한은 없으나 작은 값을 권장한다고 합니다.

cf) “shared_preferences” Github

Android: SharedPreferences 를 사용

iOS: NSUserDefaults

MacOS: NSUserDefaults

Web: LocalStorage

제가 다뤄야하는 값이 Base64 입니다. 값이 어느정도까지 커질지 예측이 불가능합니다.


Base64 간단 설명

  1. 길이를 알 수 없는 Byte 를 24bit 단위로 나눈다.
  2. 나눠진 24 조각의 bit 를 6개씩 묶는다.

출처: https://lhh3520.tistory.com/26

  1. 6개의 숫자는 0~63까지 표현이 가능하므로, 총 64글자이고, 그것이 Base64 가 된다. 각각 Base64 에 대응하는 글자로 맵핑하여 변경한다.
  2. 만약 24 조각으로 딱 나누어 떨어지지 않아서 몇 개가 부족하다면, 부족한 만큼 0 으로 채우고 그 값을 “=” 로 메웁니다. (이 과정에서 용량증가발생)

본론으로 돌아와서, Base64 를 통해서 이미지를 저장하기에는 충분히 용량이 큰 경우도 대응이 가능해야한다고 판단했습니다.

애초에 Shared Preferences 의 이름 뜻처럼 설정값을 입력하라는 이름인 만큼, 데이터를 넣는 것은 부적합하다고 판단했고, 용량에 대한 제한이 여유로운 Hive 를 선택했습니다.

(여러 DB 중 Hive 를 선택한 이유는, Firebaes 와 같은 클라우드 사용은 제한되어 있었습니다. - 프로젝트 업권특성, RDBMS 를 사용할 만큼 다른 테이블이 없으므로, 간단하고 단순한 NoSQL 인 Hive 를 선택했습니다.)


Hive 튜토리얼

설치를 먼저 진행합니다.

pubspec.ymal

dependencies:
  flutter:
    sdk: flutter

  hive: ^2.2.3 
  hive_flutter: ^1.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  hive_generator: ^2.0.1
  build_runner: ^2.4.6

봐야하는 코드는 4 줄 입니다.

  • hive: ^2.2.3 : Hive 설치
  • hive_flutter: ^1.1.0 : Hive 추가기능(Extension)
  • hive_generator: ^2.0.1 : Hive 코드 제너레이터
  • build_runner: ^2.4.6 : 이곳저곳 모두 사용되는 Build Runner

(참고로, 옆에 ^ 뒤에 있는 버전넘버링은 flutter 의 버전과 글을 보는 시점에 따라서 조절이 필요)

위처럼 작성한 이후에 Terminal 에 flutter pub get 을 작성하고 실행해주세요.

이렇게 설치한 상태에서 main.dart 에 다음 코드를 추가합니다.

main.dart

import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart'; // <---

void main() async { // <-- async 추가
  await Hive.initFlutter(); // <-- 코드 호출 (웹인 경우 Hive.init() 을 호출)
  runApp(const MyApp());
}

CRUD 에 대해서 테스트를 위해 예시코드를 작성하겠습니다.

void main() async {
  await Hive.initFlutter();

  // box 조회
  // docs: You may call box('testBox') to get the singleton instance of an already opened box.
  var box = await Hive.openBox('myBox');

  // box 에 특정 값 작성
  box.put('name', 'uno');
  debugPrint(box.get('name')); // uno

  runApp(const MyApp());
}

프로젝트 최초 생성상태에서 MyApp 클래스는 건드리지 않았습니다.

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

MyHomePage 위젯

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // Hive Box 조회
  var box = Hive.box('myBox');

  int _counter = 0;

  int get counter {
    if (_counter < 1) _counter = 0;
    return _counter;
  }

  void addCounter() {
    if (box.values.isEmpty) {
      _counter = 0;
      return;
    }
    // 최초로 저장하는 경우
    _counter++;
  }

  void subCounter() {
    if (box.values.length < counter) {
      _counter = box.values.length - 1;
      return;
    }
    _counter--;
  }

  final List<String> _nameList = [];
  String name = 'default name';
  int currentLength = 0;

  String makeKey(int index) => '$index';

  String makeValue(int index) => 'value: $index';

  // 데이터를 증가시키는 메서드
  void _incrementCounter() {
    // index 변수를 증가시킵니다.
    addCounter(); //

    setState(() {
      // 메모리 저장소에 현재 저장하려는 데이터를 추가합니다.
      _nameList.add(makeKey(counter));

      // 영구 저장소에 현재 저장하려는 데이터를 추가합니다.
      box.put(makeKey(counter), makeValue(counter));
    });
  }

  // 데이터를 감소시키는 메서드
  void _deleteCounter() {

    setState(() {
      // 메모리 저장소에서 데이터를 제거합니다.
      _nameList.remove(counter);

      // 영구 저장소에서 데이터를 제거합니다.
      box.delete(makeKey(counter));
      print('${box.values.toList()}');
    });

    // index 변수를 감소시킵니다.
    subCounter();
  }

  
  Widget build(BuildContext context) {
    var box = Hive.box('myBox');
    return Scaffold(
      appBar: AppBar(
        title: Text(name),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '현재 Box 의 길이는 ${box.length} 입니다.',
            ),
            Text(
              '현재 Index: $counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            // UI를 그리는 부분
            GridView.builder(
              shrinkWrap: true,
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 5,
              ),
              itemCount: 25,
              itemBuilder: (context, index) {
                // Hive에서 데이터가 있는지 확인
                bool isStored = box.get(makeKey(index)) != null;

                // 색칠된 정사각형 또는 흰색 정사각형을 반환
                return Container(
                  margin: EdgeInsets.all(2),
                  color: isStored ? Colors.green : Colors.grey,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: Row(
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: _deleteCounter,
            child: const Icon(Icons.remove),
          ),
          const SizedBox(width: 10),
          FloatingActionButton(
            onPressed: _incrementCounter,
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

추가하고 삭제하는 부분에서 Index 를 움직이고 동작하느냐 혹은 동작하고 Index 를 움직이느냐가 주의할 부분입니다. (여기서 동작은 삭제 혹은 추가 동작 입니다.)


참고자료

profile
iOS & Flutter

0개의 댓글