Flutter

[Toy] 혈압 기록 노트(2) - Firebase Cloud FireStore 소개 및 사용

Dean83 2023. 5. 18. 08:49

정보를 로컬에 저장하는것은 좋은 방법이 아니다. 클라우드에 저장시 여러 이점이 존재한다.

그중 Firebase는 접근하기가 쉽고 간단하며, 무료로 이용 가능한 점이 좋았다. (물론 limit은 있다)

 

프로젝트 설정은, 공식 문서를 참조하는것이 가장 좋다. 

https://firebase.google.com/docs/flutter/setup?hl=ko&platform=web 

 

Flutter 앱에 Firebase 추가

의견 보내기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Flutter 앱에 Firebase 추가 plat_ios plat_android plat_web iOS+ Android 웹 기본 요건 아직 Flutter 앱이 없다면

firebase.google.com


위의 작업을 한 뒤, Firebase console 에서 모든제품 -> Cloud Firestore 를 추가해주면 된다.

1. 소개

    - 구글에서 제공하는지라 Flutter 에 대한 연동이 쉽다

    - NOSQL 로서, json 형식으로 저장을 한다. 
    - 파일로 저장, 불러오기가 가능하나 "유료" 사용자만 가능하다. 무료사용자는 안된다.

    - 정렬 기능을 사용하기 위해서는 따로 "설정" 을 해주어야 한다

 

2. 구조

    - 컬렉션 -> document -> 컬렉션 형태로 구성이 되어 있다. 
    - 1 : N : 1 관계이다. 

    - root에 있는 컬렉션, document 에는 각각 key 값이 필요하다. 자동생성도 가능하고, 임의로 생성도 가능하다. 

    - root 컬렉션 : 각 분류별 저장할 정보의 root 카테고리

    - document : 실제 저장될 항목들 개수 만큼 생성. 저장 항목과 1:1 관계이다. 

    - 최종 컬렉션 : 실제 저장되는 정보들의 모음. 필드 추가를 통해 다양한 값을 저장할 수 있다.

 

3. 프로젝트에서 사용하는 구성

    - 이 프로젝트에서 각 구성은 다음과 같다. 

        - root 컬렉션 : history (혈압 기록 노트 정보), member (회원정보) 로 구성

 

3.1. member
        - member 의 document : 회원 수 만큼 생성

        - member -> document -> collection : id 필드가 있고, 회원 아이디가 기록된다. (로그인을 위해선 pwd 정보도 추가 필요하다)

 

3.2. history

        - history 의 document : 혈압 기록 데이터 수 만큼 생성. 키값은 자동생성 한다. 

        - history -> document -> collection : createtime, date, high, low, member 의 필드가 존재한다. 

           - createtime : 문자열. 정렬을 위해 사용되는 날짜를 숫자형태로 변형

           - date : 문자열. 화면에 표시할, 년월일 시분 형태의 기록 일시

           - high : number, 수축기 혈압값

           - low : number, 이완기 혈압값

           - member : 회원정보에 기록된 아이디. 이 정보의 주체를 구분하기 위해 사용

 

4. 정렬을 위한 설정 (이것을 하지 않으면 정렬시 오류가 발생한다)

     - Firebase console 에서, Cloud Firestore -> 색인 항목 선택

     - 색인 추가 클릭 후 정렬에 사용할 필드 이름 추가 (내림차순, 올림차순 별로 각각 설정해야 한다)

     - 반영되는데 까지 시간이 약간 소요된다.

     - __name__ 이라는 항목이 자동으로 하나 더 추가된다. 

 

 

5. Flutter 코드에서 연동

5.1. 초기화
        - 메인에서 초기화를 해주어야 한다.

        - future 를 이용하 초기화 하고 변수에 담는다. 이유는, 비동기로 초기화 하고 앱을 구동하기 위함이다.         

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

  final Future<FirebaseApp> _initialization =
      Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

          

5.2. app 의 build 함수

        - 위에서 firebase initialize 변수를 futurebuilder 에 배정한다

        - 초기화가 되기 전에는 화면에 Loading... 을 보여주고, 초기화가 완료되면, 실제 화면을 보여준다. 

@override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _initialization,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          return MaterialApp(
            title: 'Blood Pressure',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            home: MyHomePage(title: 'Blood Pressure History'),
          );
        }
        return MaterialApp(
          title: 'Blood Pressure',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const Text('Loading...'),
        );
      },
    );
  }
}

      

5.3. FireStore 에서 정보 가져오기

        - 비동기를 이용한다

        - root 컬렉션을 먼저  가져온다. 

        - 가져온 root 컬렉션에서, 조건값에 부합하는 documents 를 비동기로 가져온다.

            - 검색은 .where 를 통해 수행 할 수 있다.

            - 정렬은 .orderby 로 수행할 수 있고, 검색조건 맨 마지막에 위치해야 한다.

        - 화면에 표시, 다른화면과 연계를 위해 별도의 List변수에 해당 데이터를 담고, setstate로 화면 갱신을 한다.            

* 데이터를 가져오는 방식은 1회성 방식과 real time 방식이 있다. 나는 1회성 방식으로 사용하였다. 

Future<void> _getDataFromCloudFireStore() async {
    CollectionReference collections =
        FirebaseFirestore.instance.collection('root 컬랙션 명');

    var histories = await collections
        .where("member", isEqualTo: "회원아이디명")
        .where("createtime", isNull: false)
        .orderBy("createtime")
        .get();

    if (histories == null ||
        histories.docs == null ||
        histories.docs.length <= 0)
      print("data null");
    else {
      histories.docs.forEach((item) {
        PressureClass temp = PressureClass(
            item["date"], item["high"], item["low"], item["createtime"]);
        setState(() {
          _listItems.add(temp);
        });
      });
    }

 

5.4. List 변수를 통한 Widget 생성

        - FireStore 에서 가져온 컬렉션을 ListView 에 보여줘야 한다. 이를 위해서 반복문 (foreach, for)을 통해 각 아이템별

           UI를 생성해 준다. 

        - 각 아이템별 수정, 삭제 버튼을 만들어주고 onclick 연계를 한다. 

 

List<Widget> getList() {
    List<Widget> items = List.empty(growable: true);
    PressureClass? resultItem = null;

    if (_listItems.isNotEmpty) {
      var width = MediaQuery.of(context).size.width / 100.0;
      _listItems.forEach((item) {
        items.insert(
          0,
          .....
          IconButton(
                      onPressed: () => {
                        setState(() {
                          _listItems.remove(item);

                          _updateDeleteFireStoreData(item, false);
                        })
                      },
                      icon: const Icon(Icons.delete),
                    ),
        ....

 

5.5. 데이터 수정 및 삭제  

        - 기본적으로 where 를 통한 collection 검색은 똑같다.  조건이 좀 더 추가될뿐이다.

        - update 함수를 통해 내용을 업데이트하고, delete함수를 통해 삭제를 한다. 

Future<void> _updateDeleteFireStoreData(
      PressureClass item, bool isModify) async {
    if (item == null) {
      return;
    }

    CollectionReference collections =
        FirebaseFirestore.instance.collection('history');

    var histories = await collections
        .where("member", isEqualTo: "회원아이디명")
        .where("createtime", isEqualTo: item.createtime)
        .get();
    if (histories == null ||
        histories.docs == null ||
        histories.docs.length <= 0) {
      return;
    }

    if (isModify) {
      collections
          .doc(histories.docs.first.id)
          .update({"high": item.high, "low": item.low});
    } else {
      collections.doc(histories.docs.first.id).delete();
    }

  }

 

 5.6. 데이터 추가

         - 데이터 추가시 document 가 생성되는데, key값을 넣을수도 있고 자동 생성할 수도있다. 여기서는 자동생성 하였다. 

  void _addDataToFireStore(PressureClass item) {
    if (item == null) {
      return;
    }

    FirebaseFirestore.instance.collection('루트컬렉션명').add({
      "createtime": item.createtime,
      "date": item.time,
      "high": item.high,
      "low": item.low,
      "member": "회원아이디명"
    });

  }