๐Ÿ‹ Flutter

[Flutter] Firebase๋กœ ์ฑ„ํŒ…์•ฑ ๋งŒ๋“ค๊ธฐ - 3 (๋ฐ์ดํ„ฐ ์ €์žฅํ•˜๊ธฐ)

Dogfoot_JW 2022. 5. 19. 19:56

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š”

์ €๋ฒˆ ํฌ์ŠคํŒ…์— ์ด์–ด ๋ฉ”์‹œ์ง€ ์ €์žฅํ•˜๊ธฐ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณผ ์˜ˆ์ •์ด๋‹ค.

์šฐ์„  ๋ชจ๋ธ ํด๋ž˜์Šค๋ฅผ ์•ฝ๊ฐ„ ๋” ์ˆ˜์ •ํ•˜์˜€๋‹ค.

 

message_model.dart

import 'package:cloud_firestore/cloud_firestore.dart';

class MessageModel {
  final String id; //ํ•ด๋‹น ๋„ํ๋จผํŠธ์˜ ID๋ฅผ ๋‹ด๊ธฐ์œ„ํ•จ.
  final String content;
  final Timestamp sendDate;

  MessageModel({
    this.id = '',
    this.content = '',
    Timestamp? sendDate,
  }):sendDate = sendDate??Timestamp(0, 0);

  //์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ mapํ˜•ํƒœ์˜ ์ž๋ฃŒ๋ฅผ MessageModelํ˜•ํƒœ์˜ ์ž๋ฃŒ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•จ.
  factory MessageModel.fromMap({required String id,required Map<String,dynamic> map}){
    return MessageModel(
      id: id,
      content: map['content']??'',
      sendDate: map['sendDate']??Timestamp(0, 0)
    );
  }

  Map<String,dynamic> toMap(){
    Map<String,dynamic> data = {};
    data['content']=content;
    data['sendDate']=sendDate;
    return data;
  }

}

์ด๋ฒˆ์—” timestamp๋ผ๋Š” ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋˜์—ˆ๋Š”๋ฐ

์กฐํšŒ๋œ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ์ „์†ก ์ผ์‹œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ƒํƒœ์ด๋‹ค.

 

๋‹ค์Œ์€ ๋ชฉ๋ก ํด๋ž˜์Šค์— ๋Œ€ํ•œ ์ˆ˜์ •์„ ์ง„ํ–‰ํ–ˆ๋‹ค.

 

message_list_screen.dart ( onPressedSendButton ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€)

  void _onPressedSendButton(){
    try{
      //์„œ๋ฒ„๋กœ ๋ณด๋‚ผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋ธํด๋ž˜์Šค์— ๋‹ด์•„๋‘”๋‹ค.
      //์—ฌ๊ธฐ์„œ sendDate์— Timestamp.now()๊ฐ€ ๋“ค์–ด๊ฐ€๋Š”๋ฐ ์ด๋Š” ๋””๋ฐ”์ด์Šค์˜ ์‹œ๊ฐ„์„ ๋‚˜ํƒ€๋‚ด๋ฏ€๋กœ ๋‚˜์ค‘์—๋Š” ์„œ๋ฒ„์˜ ์‹œ๊ฐ„์„ ๋„ฃ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋„๋ก ํ•˜์ž.
      MessageModel messageModel = MessageModel(content: controller.text,sendDate: Timestamp.now());
      
      //Firestore ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
      FirebaseFirestore firestore = FirebaseFirestore.instance;
      //์›ํ•˜๋Š” collection ์ฃผ์†Œ์— ์ƒˆ๋กœ์šด document๋ฅผ Map์˜ ํ˜•ํƒœ๋กœ ์ถ”๊ฐ€ํ•˜๋Š” ๋ชจ์Šต.
      firestore.collection('chatrooms/YLCoRBj59XRsDdav2YV1/messages').add(messageModel.toMap());

    }catch(ex){
      log('error)',error: ex.toString(),stackTrace: StackTrace.current);
    }
  }

ํ•ด๋‹น ์ฝ”๋“œ๋Š” "์ „์†ก" ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ๋™์ž‘ํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.

ํŒŒ์ด์–ด์Šคํ† ์–ด์˜ db๋Š” RDBMS์™€๋Š” ๋‹ค๋ฅด๊ฒŒ ๋ฐ์ดํ„ฐ๋“ค์ด ์ •ํ˜•ํ™”๋œ ์ƒํƒœ๋กœ ์ž…๋ ฅ๋˜์•ผ๋˜๋Š” ๊ฒŒ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—

์–ธ์ œ๋“ ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ์—†๋˜ ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ๋„ฃ๊ฑฐ๋‚˜ ์—†๋˜ ์ปฌ๋ ‰์…˜์ด ์ถ”๊ฐ€๋œ ์ƒํƒœ๋กœ ๋„ฃ๊ฑฐ๋‚˜ ํ•˜๋Š” ๊ฒŒ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

message_list_screen.dart ( getInputWidget ๋ฉ”์†Œ๋“œ ์ถ”๊ฐ€)

  Widget getInputWidget() {
    return Container(
      height: 60,
      width: double.infinity,
      decoration: BoxDecoration(boxShadow: const [
        BoxShadow(color: Colors.black12, offset: Offset(0, -2), blurRadius: 3)
      ], color: Theme.of(context).bottomAppBarColor),
      child: Padding(
        padding: EdgeInsets.symmetric(horizontal: 15,vertical: 8),
        child: Row(
          mainAxisSize: MainAxisSize.max,
          children: [
            Expanded(
              child: TextField(
                controller: controller,
                decoration: InputDecoration(
                  labelStyle: TextStyle(fontSize: 15),
                  labelText: "๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”..",
                  fillColor: Colors.white,
                  focusedBorder: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(25.0),
                    borderSide: BorderSide(
                      color: Colors.blue,
                    ),
                  ),
                  enabledBorder: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(25.0),
                    borderSide: BorderSide(
                      color: Colors.black26,
                      width: 1.0,
                    ),
                  ),
                ),
              ),
            ),
            SizedBox(width: 10,),
            RawMaterialButton(
              onPressed: _onPressedSendButton, //์ „์†ก๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ๋•Œ ๋™์ž‘์‹œํ‚ฌ ๋ฉ”์†Œ๋“œ
              constraints: BoxConstraints(
                minWidth: 0,
                minHeight: 0
              ),
              elevation: 2,
              fillColor: Theme.of(context).colorScheme.primary,
              shape: CircleBorder(),
              child: Padding(
                padding: EdgeInsets.all(10),
                child: Icon(Icons.send),
              ),
            )
          ],
        ),
      ),
    );
  }

ํ•ด๋‹น ์ฝ”๋“œ๋Š” ํ•˜๋‹จ์— ๋„ฃ์„ ์ž…๋ ฅ ์นธ์— ๋Œ€ํ•œ UI ๊ตฌ์„ฑ์ด๋‹ค 

 

 

์ด์ œ MessageListScreenํด๋ž˜์Šค์— ๋Œ€ํ•œ ์ „์ฒด ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋„๋ก ํ•˜์ž.

message_list_screen.dart (์ „์ฒด ์ฝ”๋“œ)

import 'dart:developer';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test_chatapp/model/message_model.dart';

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

  @override
  State<MessageListScreen> createState() => _MessageListScreenState();
}

class _MessageListScreenState extends State<MessageListScreen> {
  TextEditingController controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('๋ฉ”์„ธ์ง€ ๋ชฉ๋ก')),
      body: StreamBuilder<List<MessageModel>>(
        stream: streamMessages(), //์ค‘๊ณ„ํ•˜๊ณ  ์‹ถ์€ Stream์„ ๋„ฃ๋Š”๋‹ค.
        builder: (context, asyncSnapshot) {
          if (!asyncSnapshot.hasData) {
            //๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ ๋กœ๋”ฉ์œ„์ ฏ์„ ํ‘œ์‹œํ•œ๋‹ค.
            return const Center(child: CircularProgressIndicator());
          } else if (asyncSnapshot.hasError) {
            return const Center(
              child: Text('์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.'),
            );
          } else {
            List<MessageModel> messages = asyncSnapshot.data!; //๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•  ๊ฒฝ์šฐ ๋ฆฌ์ŠคํŠธ๋ทฐ ํ‘œ์‹œ
            return Column(
              mainAxisSize: MainAxisSize.max,
              children: [
                Expanded(
                    child: ListView.builder(
                        // ์ƒ์œ„ ์œ„์ ฏ์˜ ํฌ๊ธฐ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์žก๋Š”๊ฒŒ ์•„๋‹Œ ์ž์‹์œ„์ ฏ์˜ ํฌ๊ธฐ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์žก์Œ
                        itemCount: messages.length,
                        itemBuilder: (context, index) {
                          return ListTile(
                            title: Text(messages[index].content),
                            subtitle: Text(messages[index].sendDate.toDate().toLocal().toString().substring(5,16)),
                          );
                        })),
                getInputWidget()
              ],
            );
          }
        },
      ),
    );
  }

  Widget getInputWidget() {
    return Container(
      height: 60,
      width: double.infinity,
      decoration: BoxDecoration(boxShadow: const [
        BoxShadow(color: Colors.black12, offset: Offset(0, -2), blurRadius: 3)
      ], color: Theme.of(context).bottomAppBarColor),
      child: Padding(
        padding: EdgeInsets.symmetric(horizontal: 15,vertical: 8),
        child: Row(
          mainAxisSize: MainAxisSize.max,
          children: [
            Expanded(
              child: TextField(
                controller: controller,
                decoration: InputDecoration(
                  labelStyle: TextStyle(fontSize: 15),
                  labelText: "๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”..",
                  fillColor: Colors.white,
                  focusedBorder: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(25.0),
                    borderSide: BorderSide(
                      color: Colors.blue,
                    ),
                  ),
                  enabledBorder: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(25.0),
                    borderSide: BorderSide(
                      color: Colors.black26,
                      width: 1.0,
                    ),
                  ),
                ),
              ),
            ),
            SizedBox(width: 10,),
            RawMaterialButton(
              onPressed: _onPressedSendButton, //์ „์†ก๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ๋•Œ ๋™์ž‘์‹œํ‚ฌ ๋ฉ”์†Œ๋“œ
              constraints: BoxConstraints(
                minWidth: 0,
                minHeight: 0
              ),
              elevation: 2,
              fillColor: Theme.of(context).colorScheme.primary,
              shape: CircleBorder(),
              child: Padding(
                padding: EdgeInsets.all(10),
                child: Icon(Icons.send),
              ),
            )
          ],
        ),
      ),
    );
  }

  void _onPressedSendButton(){
    try{
      //์„œ๋ฒ„๋กœ ๋ณด๋‚ผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋ธํด๋ž˜์Šค์— ๋‹ด์•„๋‘”๋‹ค.
      //์—ฌ๊ธฐ์„œ sendDate์— Timestamp.now()๊ฐ€ ๋“ค์–ด๊ฐ€๋Š”๋ฐ ์ด๋Š” ๋””๋ฐ”์ด์Šค์˜ ์‹œ๊ฐ„์„ ๋‚˜ํƒ€๋‚ด๋ฏ€๋กœ ๋‚˜์ค‘์—๋Š” ์„œ๋ฒ„์˜ ์‹œ๊ฐ„์„ ๋„ฃ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋„๋ก ํ•˜์ž.
      MessageModel messageModel = MessageModel(content: controller.text,sendDate: Timestamp.now());

      //Firestore ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
      FirebaseFirestore firestore = FirebaseFirestore.instance;
      //์›ํ•˜๋Š” collection ์ฃผ์†Œ์— ์ƒˆ๋กœ์šด document๋ฅผ Map์˜ ํ˜•ํƒœ๋กœ ์ถ”๊ฐ€ํ•˜๋Š” ๋ชจ์Šต.
      firestore.collection('chatrooms/YLCoRBj59XRsDdav2YV1/messages').add(messageModel.toMap());

    }catch(ex){
      log('error)',error: ex.toString(),stackTrace: StackTrace.current);
    }
  }

  Stream<List<MessageModel>> streamMessages(){
    try{
      //์ฐพ๊ณ ์ž ํ•˜๋Š” ์ปฌ๋ ‰์…˜์˜ ์Šค๋ƒ…์ƒท(Stream)์„ ๊ฐ€์ ธ์˜จ๋‹ค.
      final Stream<QuerySnapshot> snapshots = FirebaseFirestore.instance.collection('chatrooms/YLCoRBj59XRsDdav2YV1/messages').orderBy('sendDate').snapshots();

      //์ƒˆ๋‚ญ ์Šค๋ƒ…์ƒท(Stream)๋‚ด๋ถ€์˜ ์ž๋ฃŒ๋“ค์„ List<MessageModel> ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด map์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•œ๋‹ค.
      //์ฐธ๊ณ ๋กœ List.map()๋„ List ์•ˆ์˜ element๋“ค์„ ์›ํ•˜๋Š” ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ƒˆ๋กœ์šด List๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค
      return snapshots.map((querySnapshot){
        List<MessageModel> messages = [];//querySnapshot์„ message๋กœ ์˜ฎ๊ธฐ๊ธฐ ์œ„ํ•ด List<MessageModel> ์„ ์–ธ
        querySnapshot.docs.forEach((element) { //ํ•ด๋‹น ์ปฌ๋ ‰์…˜์— ์กด์žฌํ•˜๋Š” ๋ชจ๋“  docs๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ messages ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.
          messages.add(
              MessageModel.fromMap(
                  id:element.id,
                  map:element.data() as Map<String, dynamic>
              )
          );
        });
        return messages; //QuerySnapshot์—์„œ List<MessageModel> ๋กœ ๋ณ€๊ฒฝ์ด ๋์œผ๋‹ˆ ๋ฐ˜ํ™˜
      }); //Stream<QuerySnapshot> ์—์„œ Stream<List<MessageModel>>๋กœ ๋ณ€๊ฒฝ๋˜์–ด ๋ฐ˜ํ™˜๋จ

    }catch(ex){//์˜ค๋ฅ˜ ๋ฐœ์ƒ ์ฒ˜๋ฆฌ
      log('error)',error: ex.toString(),stackTrace: StackTrace.current);
      return Stream.error(ex.toString());
    }
  }
}

 

 

 

๋งŒ์•ฝ ๋ฉ”์‹œ์ง€ ์ „์†ก ํ›„ ๋ชฉ๋ก์ด ๋’ค์ฃฝ๋ฐ•์ฃฝ ์„ž์—ฌ์„œ ๋‚˜์˜จ๋‹ค๋ฉด

๋ฉ”์‹œ์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ถ€๋ถ„์—์„œ ์ •๋ ฌ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

๋‚˜์˜ ๊ฒฝ์šฐ๋Š” ์•„๋ž˜์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ

final Stream<QuerySnapshot> snapshots = FirebaseFirestore.instance.collection('chatrooms/YLCoRBj59XRsDdav2YV1/messages').orderBy('sendDate').snapshots();

order by ๋ฉ”์†Œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ sendDate๊ธฐ์ค€ ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜๊ฒŒ๋” ํ–ˆ๋‹ค.

 

 

์›น์—์„œ๋„ ์ž˜ ๋™์ž‘ํ•˜๋Š” ๋ชจ์Šต์ด๋‹ค XD

 

์—ฌ๊ธฐ๊นŒ์ง€ ์ง„ํ–‰ํ•ด์˜จ ์ „์ฒด ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์˜ฌ๋ ค๋‘๋„๋ก ํ•˜๊ฒ ๋‹ค.

ํฌ์ŠคํŒ…๋œ ๋‚ด์šฉ๊ณผ ์•ฝ๊ฐ„์˜ ์ฐจ์ด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ

์ฐธ๊ณ ์šฉ์œผ๋กœ๋งŒ ํ™•์ธํ•˜๋„๋ก ํ•˜๊ณ  ์ฝ”๋”ฉ์€ ๋ณต๋ถ™์ด ์•„๋‹ˆ๋ผ ์ง์ ‘ ์†์œผ๋กœ ์ž…๋ ฅํ•ด๋ณด๋ฉฐ ์ตœ๋Œ€ํ•œ ์ดํ•ด๋ฅผ ํ•ด๊ฐ€๋ฉฐ 

๊ณต๋ถ€ํ•ด๊ฐ€๊ธธ ๋ฐ”๋ž€๋‹ค.

 

https://github.com/jwk9022648/flutter_test_chatapp

 

GitHub - jwk9022648/flutter_test_chatapp

Contribute to jwk9022648/flutter_test_chatapp development by creating an account on GitHub.

github.com