본문 바로가기

Flutter, Moor(SQLite ORM)을 사용한 CRUD 정리 - 1편 Create, Read - Flutter moor example, Dart ORM, Flutter database tool

ironwhale 2021. 9. 26.

 

1. 필요한 패키지 설치 

공식 홈페이지에 있는 Getting started에 있는 패키지를 설치해 줍니다. 

dependencies:
  moor: ^4.5.0
  sqlite3_flutter_libs: ^0.5.0
  path_provider: ^2.0.0
  path: ^1.8.0
  get_it: ^7.2.0 # 공식 문서에 있는 패키지가 아닌 코드 팩토리님 강의에 나온 추가 패키지입니다.

dev_dependencies:
  moor_generator: ^4.5.1
  build_runner: ^2.1.1

2. database.dart 파일 생성

이부분은 sqlite 파일을 생성하고 데이터베이스를 생성하는 부분입니다. 이부분은 거의 공식문서의 코드를 그대로 가지고 왔습니다. 

  part 'database.g.dart'; -> 이부분은 '파일명.g.dart'로 하시면 되고 

이후 flutter packages pub run build_runner build 를 터미널에 입력하시면 'database.g.dart' 파일이 자동으로 생성됩니다.

 

flutter packages pub run build_runner build 명령어는 앞으로 2번 정도 더 사용할 예정이니 잘 기억해두시기 바랍니다. 

// These imports are only needed to open the database
import 'package:moor/ffi.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:moor/moor.dart';
import 'dart:io';

part 'database.g.dart';

LazyDatabase _openConnection() {
  // the LazyDatabase util lets us find the right location for the file async.
  return LazyDatabase(() async {
    // put the database file, called db.sqlite here, into the documents folder
    // for your app.
    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File(p.join(dbFolder.path, 'db.sqlite'));
    return VmDatabase(file);
  });
}

@UseMoor()
class MyDatabase extends _$MyDatabase {
  // we tell the database where to store the data with this constructor
  MyDatabase() : super(_openConnection());

  // you should bump this number whenever you change or add a table definition. Migrations
  // are covered later in this readme.
  @override
  int get schemaVersion => 1;
}

2. DB테이블(User.dart) 파일 생성

회원정보를 저장하는 테이블을 생성하기위해 별도로 User.dart 파일을 생성합니다. 여러 튜토리얼에서는 하나의 파일에 

테이블정보와 DB 관련 정보를 동시에 작성하는데 코드팩토리 님 강의에서는 별도로 작성하셔서 저도 별도로 작성하였습니다. 

#user.dart

import 'package:moor/moor.dart';
import 'database.dart';

part 'user.g.dart';

class User extends Table{
  IntColumn get id => integer().autoIncrement()();
  TextColumn get username => text()();
  TextColumn get userHobby => text()();
  DateTimeColumn get createTime => dateTime().withDefault(Constant(DateTime.now()))();
}

@UseDao(tables:[User])
class UserDao extends DatabaseAccessor<MyDatabase> with _$UserDaoMixin{
  UserDao(MyDatabase db):super(db);
}

database.dart에도 @UseMoor(tables: [User],daos: [UserDao]) 으로 수정합니다.

 

#database.dart

// These imports are only needed to open the database
import 'package:moor/ffi.dart';
import 'package:my_provider_train/moor/user.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:moor/moor.dart';
import 'dart:io';

part 'database.g.dart';

LazyDatabase _openConnection() {
  // the LazyDatabase util lets us find the right location for the file async.
  return LazyDatabase(() async {
    // put the database file, called db.sqlite here, into the documents folder
    // for your app.
    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File(p.join(dbFolder.path, 'db.sqlite'));
    return VmDatabase(file);
  });
}

@UseMoor(tables: [User],daos: [UserDao])  ## 추가된 부분
class MyDatabase extends _$MyDatabase {
  // we tell the database where to store the data with this constructor
  MyDatabase() : super(_openConnection());

  // you should bump this number whenever you change or add a table definition. Migrations
  // are covered later in this readme.
  @override
  int get schemaVersion => 1;
}

위와 같이 하고 다시한번 flutter packages pub run build_runner build 를 해주면 user.g.dart파일이 생성되면서 우리가 moor를 사용하는데 필요한 기능이 있는 파일이 생성됩니다. 

 

3. CRUD(create, read, update, delete) 구현

flutter packages pub run build_runner build을 통해 생성된 파일명.g.dart 파일에는 우리가 CRUD를 구현하는데 필요한 것들이 자동으로 생성되어 있을겁니다. 

특히 id나 createTime은 테이블에 자동으로 입력되는 부분 이기 때문에 UserCompanion이라는 자료형을 사용하여 Create 기능을 구현합니다. 

import 'package:moor/moor.dart';
import 'database.dart';


part 'user.g.dart';


class User extends Table{
  IntColumn get id => integer().autoIncrement()();
  TextColumn get username => text()();
  TextColumn get userHobby => text()();
  DateTimeColumn get createTime => dateTime().withDefault(Constant(DateTime.now()))();
}


@UseDao(tables:[User])
class UserDao extends DatabaseAccessor<MyDatabase> with _$UserDaoMixin{
  UserDao(MyDatabase db):super(db);

   // Create
   Future insertUser(UserCompanion data) => into(user).insert(data);   
  
   // Read
   Stream<List<UserData>> streamUsers() =>
       (select(user)..orderBy([(t)=>OrderingTerm(expression: t.id,mode: OrderingMode.desc)])).watch();
   // 공식문서를 참고하여 id의 내림차순으로 정렬하였습니다. 

   // Update
   Future updateUser(data) => update(user).replace(data);

   // Delete
   Future delUser(data) => delete(user).delete(data);

4. 기본 UI 만들기

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<BottomNavigationBarItem> btmNavItems = [
    BottomNavigationBarItem(icon: Icon(Icons.playlist_add), label: 'Create'),
    BottomNavigationBarItem(icon: Icon(Icons.list), label: 'Read'),
    BottomNavigationBarItem(icon: Icon(Icons.delete), label: 'Update&Detete'),
  ];

  int _selectedIndex = 0;
  
  // 각각의 화면을 만들 부분
  List<Widget> _screens = [
    Container(color: Colors.grey,),
    Container(color: Colors.redAccent,),
    Container(color: Colors.amber,),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        items: btmNavItems,
        selectedItemColor: Colors.black87,
        unselectedItemColor: Colors.grey,
        currentIndex: _selectedIndex,
        onTap: _selectTap,
      ),
      body: IndexedStack(
        children: _screens,
        index: _selectedIndex,
      ),
    );
  }

  void _selectTap(int index){
    setState(() {
      _selectedIndex = index;
    });
  }
}

5. Create 구현

Create UI

 

 

 

아래 부분이 Create를 구현한 부분입니다.

GetIt 패키지를 사용하여 어디서든 UserDao를 불러 올수 있게 하였습니다.-> 이부분도 역시 코드팩토리 님의 유튜브 강의에 있는 내용입니다. 

dao.insertUser 부분을 통해 sqlite로 데이터를 집어 넣습니다. 

 

 

 

 void insertData() async {
    if (formKey.currentState!.validate()) {
      formKey.currentState!.save();
      if (username != null && userHobby != null) {
        final dao = GetIt.instance<UserDao>();
        await dao.insertUser(
          UserCompanion(
            username: Value(username!),
            userHobby: Value(userHobby!),
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:moor/moor.dart' hide Column;
import 'package:my_provider_train/moor/database.dart';

import 'moor/user.dart';

class Create extends StatelessWidget {
  Create({Key? key}) : super(key: key);
  GlobalKey<FormState> formKey = GlobalKey();

  String? username;
  String? userHobby;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Create",style: TextStyle(fontFamily: "BM", fontSize: 30)),
        centerTitle: true,
      ),
      body: Form(
        key: formKey,
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 8.0),
          child: Column(
            children: [
              TextFormField(
                  decoration: InputDecoration(hintText: "Write User Name"),
                  onSaved: (val) => username = val),
              TextFormField(
                  decoration: InputDecoration(hintText: "Write User hobby"),
                  onSaved: (val) => userHobby = val),
              ElevatedButton(onPressed: insertData, child: Text("Save it",
              style: TextStyle(fontFamily: "BM", fontSize: 30)))
            ],
          ),
        ),
      ),
    );
  }

  void insertData() async {
    if (formKey.currentState!.validate()) {
      formKey.currentState!.save();
      if (username != null && userHobby != null) {
        final dao = GetIt.instance<UserDao>();
        await dao.insertUser(
          UserCompanion(
            username: Value(username!),
            userHobby: Value(userHobby!),
          ),
        );
      }
    }
  }
}

5. Read 구현

Read 화면

 

 

 

아래 부분이 Create를 구현한 부분입니다.

StreamBuilder를 이용하여 Read 부분을 구현하였습니다. 역시 GetIt 패키지를 사용하여 UserDao를 사용하고 

dao.streamUsers를 통해 데이터를 뿌려 주었습니다. 

 

 

import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:my_provider_train/moor/database.dart';

import 'moor/user.dart';

class Read extends StatelessWidget {
  Read({Key? key}) : super(key: key);

  final dao = GetIt.instance<UserDao>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Read",style: TextStyle(fontFamily: "BM", fontSize: 30)),
        centerTitle: true,
      ),
      body: StreamBuilder<List<UserData>>(
        stream: dao.streamUsers(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            final data = snapshot.data!;
            return ListView.separated(
                itemBuilder: (_,index){
                  final item = data[index];
                  return Column(
                    children: [
                      Text("User name is ${item.username}",
                      style: TextStyle(fontFamily: "BM", fontSize: 30)),
                      
                      Text("User Hobby is ${item.userHobby}",
                      style: TextStyle(fontFamily: "BM", fontSize: 30)),
                    ],
                  );
                },
                separatorBuilder: (_, index)=>Divider(),
                itemCount: data.length);
          } else {
            return Container();
          }
        },
      ),
    );
  }
}

6. Udate, Detete

 이부분은 너무 길어진것 같아 새로운 포스팅으로 넘어가도록 하겠습니다. 

 

댓글