Flutter 에서는 여러가지 Local Database 라이브러리가 있습니다.
현재 제 요구사항은 Android & iOS 디바이스에서 사용하는 Local Database 이면 됩니다. 앱의 현 상황을 봤을 때, 웹으로 접근이 가능한 형태입니다. 비록 현재 요구사항으로 볼 때, 모바일 디바이스만 대응하면 되지만, 추후에 어떤식으로 변경될지 모른다고 판단해서 크로스 플랫폼을 지원해야한다고 생각했습니다.
“크로스 플랫폼” 을 지원하는 DB 를 선택하자.
그 중에서, 제가 LocalDB 를 사용하는 케이스가 관계형을 구축할만큼 복잡하지 않았습니다.
조건)
shared_preference
를 이용Shared Prefernce 는 Android 의 경우, 1MB 이하 iOS 제한은 없으나 작은 값을 권장한다고 합니다.
cf) “shared_preferences” Github
Android: SharedPreferences
를 사용
iOS: NSUserDefaults
MacOS: NSUserDefaults
Web: LocalStorage
…
제가 다뤄야하는 값이 Base64 입니다. 값이 어느정도까지 커질지 예측이 불가능합니다.
출처: https://lhh3520.tistory.com/26
본론으로 돌아와서, Base64 를 통해서 이미지를 저장하기에는 충분히 용량이 큰 경우도 대응이 가능해야한다고 판단했습니다.
애초에 Shared Preferences 의 이름 뜻처럼 설정값을 입력하라는 이름인 만큼, 데이터를 넣는 것은 부적합하다고 판단했고, 용량에 대한 제한이 여유로운 Hive 를 선택했습니다.
(여러 DB 중 Hive 를 선택한 이유는, Firebaes 와 같은 클라우드 사용은 제한되어 있었습니다. - 프로젝트 업권특성, RDBMS 를 사용할 만큼 다른 테이블이 없으므로, 간단하고 단순한 NoSQL 인 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 를 움직이느냐가 주의할 부분입니다. (여기서 동작은 삭제 혹은 추가 동작 입니다.)