Flutter

[Firebase+provider]Chp5. Chat App 채팅 기능 구현하기 II

모리선생 2023. 5. 14. 17:02
728x90

목표

provider과 Firebase를 기반으로 일회용 chat이 가능한 Application을 만들어보고 활용법을 알아본다.


해당 내용은 도그풋님의 #2 플러터 + 파이어베이스 채팅앱 만들기를 기초로 study를 진행하였으며, 일부 부분과 다르게 UI 및 기능적인 측면에서 추가를 하였습니다. 또한 Firebase와 Firecloud 등이 업데이트 됨에 따라 firebase 코드 관련 부분등은 수정된 부분이 있으니 참고하여 주시기 바랍니다.

 

또한 해당내용에 있어서 공부를 진행하는 부분이 있으므로 코드상에 문의가 있거나, 수정사항이 발견이 되면 알려주시면 감사하겠습니다.


해당 코드의 github

https://github.com/riris01/flutter_firebase_chat_provider

 

원본 코드의 github

https://github.com/wownsdl13/flutter_firebase_chatting_example


이제 본격적으로 어떤 데이터들을 받아올지 그리고 어떤 기능들을 추가할지 생각을 해보자.

 

아 그나저나, 원래는 어플을 제작을 할 당시는 '나의 입장'에서는 다음과 같은 순서로 뼈대를 세우고 어플을 제작을 한다.

서비스 기획 (제품 기획) - Group Interview - Focused Group Interview - User 구체화 - 기능요구서 작성 - 스켈레톤 생성 (흐름도 생성) - 페이지별 시나리오 생성 - 툴을 이용한 프로토타이핑 진행 - 추가 기능 요구서 작성 혹은 수정 - UI 단순 배치 - 개발 진행 - 개발 & 디자인 & 기획 간의 무한 회의 - VIP 생성 그리고 A/B 테스트 진행 - 시제품 생성 - 시장 테스트 (1달) - 제품 출고

 

이러다 보니 어떤 데이터를 어떻게 받아올지 이런 것들이 많이 변경되는 경우가 많다. 그래서 기본적인 데이터 등을 미리 설정은 해두되, 나중에 추가 데이터를 처리해야할 부분은 조금 염두해서 느슨하게 개발을 하고자 하는 편이다. 물론 이 방식이 맞는 것도 아니고 저자보다 더 훌륭한 사람들이 만드는 방식은 이와 정 반대일 수도 있다. 그렇지만 자신의 방식 (순서)를 확실하게 세워두고 개발 혹은 디자인을 하는 습관을 지닌다면, 어떠한 형태의 개발 의뢰 및 개발 단계가 진행을 하더라도 자신의 순서에 맞게 차근 차근 해나갈 수 있다.

 

(그걸 못해서 본인은 좀 고생하는 편이 없지 않아 있긴 하지만... ㅠㅠ)

 

여튼 회사 생활 적인 부분은 각설하고, 이렇게 채팅을 함에 있어서 '유저', '채팅', '시간' 등을 받아 올 수 있는 ChattingModel을 만들자.

 

@ChattingModel.dart

우리가 이번 채팅앱을 만들면서 필요한 데이터는 다음과 같다. 

  • pk (primary key): 각각의 레코드를 식별할 수 있는 고유 번호
  • name: 입력된 이름
  • text: 채팅 내용
  • uploadTime: 채팅이 입력된 시간

이외에도 다른 기능들을 추가하려면 더 넣을 수도 있다. 우리에게 name 이라는 것이 ID와 같은 느낌이라면 데이터 내에서도 각각의 정보들을 구별할 수 있는 랜덤하지만 시스템 상으로는 이해할 수 있는 고유 번호가 필요하다. 이를 primary key라고 한다.

 

일단 ChattingModel 클래스를 생성하여 어떤 데이터들을 앞으로 다룰지 선언을 해주자.

class ChattingModel {
  ChattingModel(this.pk, this.name, this.text, this.uploadTime);
  final String pk;
  final String name;
  final String text;
  final int uploadTime;
  
  (...)

 

그다음은 정적 팩토리 메서드를 사용하여 인스턴스를 생성하는데 이는 클래스의 생성자 대신 사용을 하였다. 이 메서드는 Json을 파싱하여 ChattingModel 인스턴스를 반환한다.

 

(...)

 factory ChattingModel.fromJson(Map<String, dynamic> json) {
    return ChattingModel(
        json['pk'], json['name'], json['text'], json['uploadTime']);
  }
  
  (...)

 

그 다음으로는 Map 함수를 사용하여 key와 value 값을 매칭해주면 어떤 데이터를 얻고, 어떻게 생성자를 만들 것이며, 매칭은 어떻게 할 것인지 끝이 나게 된다.

 

(...)

Map<String, dynamic> toJson() {
    return <String, dynamic>{
      'pk': pk,
      'name': name,
      'text': text,
      'uploadTime': uploadTime,
    };
  }
}

 

이렇게 하면 @ChattingModel.dart에서의 작업은 끝

 

전체코드

class ChattingModel {
  ChattingModel(this.pk, this.name, this.text, this.uploadTime);
  final String pk;
  final String name;
  final String text;
  final int uploadTime;

  factory ChattingModel.fromJson(Map<String, dynamic> json) {
    return ChattingModel(
        json['pk'], json['name'], json['text'], json['uploadTime']);
  }

  Map<String, dynamic> toJson() {
    return <String, dynamic>{
      'pk': pk,
      'name': name,
      'text': text,
      'uploadTime': uploadTime,
    };
  }
}

 

이제 ChattingProvider를 만들어서 어떤식으로 자료를 불러올지 그리고 다른 기능들은 무엇을 추가할지 작성해주자.

 

@ChattingProvider.dart

screens/chatting_page/local_utils/ChattingProvider.dart

해당 위치에 ChattingProvider.dart를 만들어 주고 난다음에, 우리가 해야할 것은 (import들을 하고 난 다음에), ChattingProvider 클래스를 만들고 ChangeNotifier로 extends하여 계속해서 Provider가 상태를 업데이트 받을 수 있도록 한다음에 Firebase의 Firestore에서 어떤 식으로 자료를 받아올지 설명해주는 것이다.

 

자 여기서는 f 라는 변수를 선언하여서 FirebaseFirestore에서 instance들을 받아오는데

.collection을 통해 CHATTING_ROOM 컬렉션을 참조하도록 하고 limit(1)을 호출하여 최대 1개의 문서를 가지고 오게 한다음에 orderBy를 통해 내림차순으로 정렬을 하도록 하였다. 그리고 snapshots을 호출하여 쿼리의 결과 변경시 스트림을 업데이트 하도록 하였다.

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_firebase_chat/models/ChattingModel.dart';
import 'package:flutter/material.dart';

class ChattingProvider extends ChangeNotifier {
  static const String CHATTING_ROOM = 'CHATTING_ROOM';

  ChattingProvider(this.pk, this.name);
  final String pk;
  final String name;

  var chattingList = <ChattingModel>[];

  Stream<QuerySnapshot> getSnapshot() {
    final f = FirebaseFirestore.instance;
    return f
        .collection(CHATTING_ROOM)
        .limit(1)
        .orderBy('uploadTime', descending: true)
        .snapshots();
  }
  
  (...)

자 그럼 데이터를 어떤식으로 업데이트를 해서 무엇을 불러 올지 그리고 불러올때, 비동기 형식이니 이를 알려주고 또 새롭게 추가하려고 할땐 어떻게 해야하는지 알려줘야한다 (인간의 자연어를 인간은 그 내용으로 추론하여서 추가적으로 어떤 행위가 일어나야할지 예측하는게 장점이라면, 개발에서 사용하는 코드들은 매우 솔직하게 반응을 하나 그에 따른 기능들에 대해서는 상세하게 구체화를 해줘야한다는것이 다른 점이다. 즉, 개발은 '잘~ 알려줘야한다')

 

addOne 메서드를 통해서 ChattingModel 객체들을 chattingList의 가장 앞에 추가를 해주고 이를 notifyListeners()를 통해 UI에 변경하도록 알린다.

 

load 메서드를 통해서 uploadTime에 근거하여 ChattingModel 객체를 변환한다음 chattingList에 추가하는 역할을 하는데, 현재 시간보다 큰 UploadTime을 가져와서 ChattingModel 객체로 변환하는 것이 큰 특징이다. 이후 get()을 호출하여 데이터를 불러온다.

 

send 메서드는 ChattingModel 객체 추가시 set()을 호출하여 doc 메서드를 통해 Firestore 문서의 ID의 현재 시간을 설정해주는 역할을 한다.

 

(...)

 void addOne(ChattingModel model) {
    chattingList.insert(0, model);
    notifyListeners();
  }

  Future load() async {
    var now = DateTime.now().millisecondsSinceEpoch;
    final f = FirebaseFirestore.instance;
    var result = await f
        .collection(CHATTING_ROOM)
        .where('uploadTime', isGreaterThan: now)
        .orderBy('uploadTime', descending: true)
        .get();
    var l = result.docs.map((e) => ChattingModel.fromJson(e.data())).toList();
    chattingList.addAll(l);
    notifyListeners();
  }

  Future send(String text) async {
    var now = DateTime.now().millisecondsSinceEpoch;
    final f = FirebaseFirestore.instance;
    await f
        .collection(CHATTING_ROOM)
        .doc(now.toString())
        .set(ChattingModel(pk, name, text, now).toJson());
  }
}

 

자 이제 완성이다. 이를 통해

 

  1. 채팅방 UI 만들기
  2. 채팅 Nickname 만들기
  3. 채팅방 구성
  4. Firebase기반 채팅 어플 생성

 

등의 내용을 모두 공부해보았다.

 

이는 매우 기본이 되는 어플리케이션의 형태이다 보니, 채팅방을 나가면 모든 내용이 삭제가 되버리게 된다. 즉 이말인 즉슨 "로그인" 기능을 구현하여, "대화내용이 계속해서 Firebase에 저장"되어 있도록 하면서, 필요시에는 "알림"기능 까지 줄 수 있는 어플을 만들 기본적인 골격만을 제공한 셈이다.

 

여기에 다양한 기능을 붙여서 실제 자신만의 채팅방을 만들어보기를 바란다.

 

자 이제 또 뭘 만들어보고 '코드를 읽어볼까?'

728x90