오늘한일

플러터 1/4/7/14 앱

db 적용

sqflite, jaguar_orm를 적용하려 했으나, 코드가 더 길어지는 것 같아서 그냥 sembast를 사용하기로 했다.

sembast는 key-value db이므로 데이터 대부분이 json이라면 document db인 objectdb를 사용하는 것을 추천한다.

dart의 in-momory는 대부분 비동기로 동작하는 듯 하다 (심지어 db connection 생성도 비동기다). 내가 선택한 sembast도 그렇다. 그런데, flutter widget의 build 메소드가 동기로 동작해서 reorder 기능을 구현하기가 힘들었다. 이번에도 구글링이 전부 해결해줬다.

class SubjectDatabase {
  static final SubjectDatabase _instance = new SubjectDatabase.internal();
  factory SubjectDatabase() => _instance;

  static Database _db;
  static StoreRef _store;

  Future<SubjectDatabase> get() async {
    if (_db != null) return _instance;

    await initDb();
    return _instance;
  }

  SubjectDatabase.internal();

  initDb() async {
    Directory appDocDirectory = await getApplicationDocumentsDirectory();

    _db = await databaseFactoryIo
        .openDatabase(join(appDocDirectory.path, '.dart_tool', 'sembast', 'example', 'record_demo.db'));

    _store = intMapStoreFactory.store('my_store');
  }

  Future<List<Subject>> findAll() async {
    var snapshots = await _store.find(_db, finder: Finder(
      sortOrders: [SortOrder('pos')]
    ));

    return snapshots.map((snapshot) {
      return Subject.fromRecord(snapshot.key, snapshot.value);
    }).toList();
  }

  updatePosition(int oldIndex, int newIndex) async {
    List<Subject> subjects = await findAll();
    Subject row = subjects.removeAt(oldIndex);
    subjects.insert(newIndex, row);

    for (var i = 0; i < subjects.length; i++) {
      var record = _store.record(subjects[i].key);
      await record.update(_db, {
        'pos': i
      });
    }
  }
}

// 비동기 widget builder
Future<List<Subject>> _subjectsFuture;

FutureBuilder(
  future: _subjectsFuture,
  builder: (context, snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.none:
      case ConnectionState.waiting:
        return new Text('loading...');
      default:
        if (snapshot.hasError)
          return new Text('Error: ${snapshot.error}');
        else {
          List<Subject> data = snapshot.data;

          return ReorderableWrap(
            children: data
                .map((subject) => SubjectSizedCard(subject))
                .toList(),
            onReorder: updateAndRefresh,
            maxMainAxisCount: 2,
          );
        }
    }
}
    
void updateAndRefresh(int oldIndex, int newIndex) async {
  await updatePosition(oldIndex, newIndex);
  // reload
  setState(() {
    _subjectsFuture = findAll();
  });
}

Future<List<Subject>> findAll() async {
  var db = await SubjectDatabase().get();
  return db.findAll();
}

updatePosition(int oldIndex, int newIndex) async {
  var db = await SubjectDatabase().get();
  await db.updatePosition(oldIndex, newIndex);
}

db 커넥션 생성비용 때문에 싱글턴을 적용했다. 아직은 updatePosition이 과목 전체를 업데이트하므로 효율이 떨어진다. 나중에 개선할 수 있으므로 우선은 여기까지...

오늘 느낀점

플러터가 좋기는 좋은데, 실용적인 예제가 별로 없는 것 같다. 예를 들어 비동기로 동작하는 데이터베이스와 CRUD 동작을 하는 StatefulWidget을 같이 사용하는 예제를 찾고 싶은데, 검색결과는 대부분 FutureBuilder와 Future<List>에 대한 내용이다. 결국 원하는 기능을 구현하기 위해서는 'async database example in flutter', 'FutureBuilder with StatefulWidget example' 를 각각 검색해야 한다.

제일 짜증나는 부분은 위젯 사이즈 조절하는데 자꾸 다른 위젯으로 감싸야 하는 부분이다. 자꾸 똑같은 내용을 검색하게되더라...

내일 할일

  • 플러터 1/4/7/14 앱
    • 과목 생성 기능 추가
      • 배경색 선택 가능
    • 과목 목록 표시
      • 배경색의 밝기에 따라 글자색 white or black 표시