[Flutter] 우당탕탕 개발 일지7 - 파이어스토어 연동하기

Leona·2023년 11월 7일
0
post-thumbnail

기깔나는 Todo 앱을 만들기 위해 Firestore에 DB를 만들어 저장하기로 했다. 쉽고 간편하게 만들 수 있어서 잘 모르는 사람도 뚝딱 DB를 만들 수 있다.

DB 만들기

  1. 데이터베이스 만들기 선택
  2. 프로덕션 모드로 시작(보안 설정, 권한 설정 알아서 해줌)
  3. asia-northeast3 (Seoul) 선택
  4. DB 완성!

데이터 추가

  • 컬렉션은 폴더, 도큐먼트는 폴더 안에 파일이라고 생각하면 된다.
  • todos - todo Id를 직접 지정하거나 자동으로 생성할 수도 있다.

플러터에서 데이터 조회하기

  1. 터미널에서 flutter pub add cloud_firestore 명령어로 플러그인 추가

    • 선택 사항이지만 docs에서 iOS 빌드 시간을 개선할 수 있도록 아래 코드를 Podfile - target 'Runner' do 블록 안에 입력하라고 한다.(사전 컴파일된 버전이라고 함)
      // docs에는 8버전으로 되어있는데 작성 시점에서는 pod install 에러나서, 10.15.0으로 올리래서 올림
      pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '10.15.0'
    • gem —version 명령어로 CocoaPods를 1.9.1 이상으로 업그레이드했는지 확인
  2. Streambuilder로 실시간 정보 받아오기

    • FirebaseFirestore.instance에서 collection의 snapshot을 받아온다. orderBy 등 조건을 걸 수도 있다.

    • snapshot은 Map<String, dynamic> 형식이기 때문에 내가 정의한 타입으로 형변환이 필요했다.

      import 'package:cloud_firestore/cloud_firestore.dart';
       import 'package:intl/intl.dart';
      
       class Todo {
         String title;
         String content;
         String date;
         String updateDate;
         bool isPlanned;
         bool isDoing;
         bool isDone;
      
         Todo({
           required this.title,
           required this.content,
           required this.date,
           required this.updateDate,
           required this.isPlanned,
           required this.isDoing,
           required this.isDone,
         });
      
          // firestore로부터 조회한 데이터 형변환
         factory Todo.fromFirestore(DocumentSnapshot<Map<String, dynamic>> snapshot) {
           final data = snapshot.data();
      
           return Todo(
               title: data?['title'],
               content: data?['content'],
               date:
                   DateFormat.yMMMMd('ko_KR').add_jm().format(data?['date'].toDate()),
               updateDate: DateFormat.yMMMMd('ko_KR')
                   .add_jm()
                   .format(data?['updateDate'].toDate()),
               isPlanned: data?['isPlanned'],
               isDoing: data?['isDoing'],
               isDone: data?['isDone']);
         }
      
          // firestore로 전달할 데이터 형변환
         Map<String, dynamic> toFirestore() {
           return {
             'title': title,
             'content': content,
             'date': date,
             'updateDate': updateDate,
             'isPlanned': isPlanned,
             'isDoing': isDoing,
             'isDone': isDone,
           };
         }
       }
      }
    • timestamp 형식은 intl 플러그인을 이용해서 변환했다. 사용하려면 main()에서 초기화 해주어야 한다.

    • firebase에서 직접 데이터를 추가하면 실시간으로 rebuild 된다.

      void main() async {
         // 위젯 먼저 초기화
         WidgetsFlutterBinding.ensureInitialized();
      
         // intl 초기화
         await initializeDateFormatting('ko_KR', null);
         // firebase 초기화
         await Firebase.initializeApp(
           options: DefaultFirebaseOptions.currentPlatform,
         );
      
         runApp(const MyApp());
       }
      
      ...
      
      class _MyHomePageState extends State<MyHomePage> {
       
       Widget build(BuildContext context) {
             ...
             body: StreamBuilder(
                     stream: FirebaseFirestore.instance
                             .collection('/todos')
                             .orderBy('updateDate', descending: true)
                             .snapshots(),
                     builder: (BuildContext context,
                             AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
                           if (snapshot.connectionState == ConnectionState.waiting) {
                             return const Center(child: Text('Loading...'));
                           }
      
                           final docs = snapshot.data!.docs;
                           List<Todo> newDocs = docs
                               .map((doc) => Todo(
                                   title: doc['title'],
                                   content: doc['content'],
                                   date: DateFormat.yMMMMd('ko_KR')
                                       .add_jm()
                                       .format(doc['date'].toDate()),
                                   updateDate: DateFormat.yMMMMd('ko_KR')
                                       .add_jm()
                                       .format(doc['updateDate'].toDate()),
                                   isPlanned: doc['isPlanned'],
                                   isDoing: doc['isDoing'],
                                   isDone: doc['isDone']))
                               .toList();
      
                           return ListView.builder(
                             itemCount: newDocs.length,
                             itemBuilder: (BuildContext context, int index) {
                               Todo item = newDocs[index];
      
                               return Padding(
                                 padding: const EdgeInsets.symmetric(
                                     horizontal: 20, vertical: 10),
                                 child: Container(
                                   width: MediaQuery.of(context).size.width,
                                   decoration: BoxDecoration(
                                     color: Colors.white,
                                     borderRadius: BorderRadius.circular(8),
                                     boxShadow: [
                                       BoxShadow(
                                         color: Color.fromARGB(255, 46, 87, 55)
                                             .withOpacity(0.5),
                                         spreadRadius: 1,
                                         blurRadius: 10,
                                         offset: const Offset(0, 8),
                                       )
                                     ],
                                   ),
                                   child: Padding(
                                     padding: const EdgeInsets.symmetric(
                                         horizontal: 15, vertical: 10),
                                     child: Column(
                                       mainAxisAlignment: MainAxisAlignment.center,
                                       crossAxisAlignment: CrossAxisAlignment.start,
                                       children: [
                                         Text(item.title),
                                         Text(item.content),
                                         Text(item.updateDate),
                                       ],
                                     ),
                                   ),
                                 ),
                               );
                             },
                           );
                         }),
  3. 실행(실제 핸드폰에서 돌려서 사진이 크다)

profile
레오나의 기묘한 개발 일지

0개의 댓글