Programming-[CrossPlatform]/Flutter

Flutter 기본-19. 채팅앱 - Login/out, Stream 및 Cloud firestore 적용

컴퓨터 탐험가 찰리 2023. 2. 9. 15:52
728x90
반응형

 

Youtube 코딩셰프님의 강의를 요약 정리한 글이다. dart 언어나 이론 부분은 자바와 유사하여 대부분 제외하였고, flutter 기초 위주로 정리한다.

https://www.youtube.com/@codingchef

 

코딩셰프

향후 대세가 될 플러터를 단계별로 맛있게 학습하실 수 있습니다!

www.youtube.com

 

 

 

background image reference : https://wallpapercave.com/cartoon-chickens-wallpapers

 


 

1. Signup 테스트

 

chat_screen

앞서 작성했던 char_screen에서 현재 로그인 유저 정보를 알아내는 메서드를 추가한다. 일단은 print()로 email이 콘솔상에서 출력되게만 해준다.

 

//상기 class 및 createState 메서드는 생략

class _ChatScreenState extends State<ChatScreen> {
  final _authentication = FirebaseAuth.instance;
  User? loggedUser;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getCurrentUser();
  }

  void getCurrentUser() {
    try {
      final user = _authentication.currentUser;
      if (user != null) {
        loggedUser = user;
        print(loggedUser!.email);
      }
    }catch (e) {
      print(e);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Chat Screen'),
        ),
        body: Center(
          child: Text('Chat Screen'),
        )
    );
  }

 

이제 회원가입을 해보면 정상적으로 firebase에 등록된다.

 

 

 

 

2. Login/Logout 추가

 

로그인

로그인 기능을 추가한다. 기존 작성했던 부분에 분기문만 추가해주면 된다. Signup과 거의 같다. 메서드만 signInWithEmailAndPassword로 변경해주면 된다.

if(!isSignupScreen) {
  _tryValidation();
  try {
    final newUser =
    await _authentication.signInWithEmailAndPassword(
      email: userEmail, password: userPassword,
    );
    if(newUser.user != null) {
      Navigator.push(context, MaterialPageRoute(builder: (context){
        return ChatScreen();
      }),
      );
    }
  } catch (e){
    print(e);
    ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Please check your email and password'),
          backgroundColor: Colors.blue,
        )
    );
  }
}

 

 

로그아웃

chat_screen 페이지에 _authentication.signOut()을 적용해주면 된다. 그리고 Navigator.pop()을 통해 이전 페이지로 돌아가도록 한다.

appBar: AppBar(
  title: Text('Chat Screen'),
  actions: [
    IconButton(
        icon: Icon(
            Icons.exit_to_app_sharp,
          color: Colors.white
        ),
        onPressed: () {
          _authentication.signOut();
          Navigator.pop(context);
        }
    ),

  ],
),

 

 

3. Stream, Cloud Firestore 조회


StreamBuilder

Future<int>의 복수형이 Stream<int> 이다. 여러 개의 Future 데이터들이 Stream에 들어올 때마다 이를 감지하여 어떤 행동을 할 수 있도록 해주는 객체이다. StreamBuilder<T>는 Stream으로 전달되는 이벤트(데이터)를 감지해준다.

 

 

Cloud Firestore

파이어베이스의 클라우드를 이용하기 위해서 Cloud firestore를 사용한다. 빌드/Cloud Firestore/데이터 베이스 만들기에 들어간다. 여기서 테스트 모드로 시작하기로 해야한다.

 

 

다음 화면에서 나오는 Cloud firestore 위치는 기본값대로 하면된다고 강의에서 나오고 있으나, 기본값인 nam5(United States)로 하면 아래처럼 알 수 없는 에러가 발생했다. 그런데 이상해서 새로고침을 하고 다시 Firestore Database 메뉴에 들어가니 데이터베이스가 잘 생성되어있었다. 정말 일시적인 오류같다.

 

 

컬렉션 시작 버튼을 누르면 마치 일반 데이터베이스에 테이블을 만들듯이 컬렉션을 만들 수 있다. 다만 일반 데이터베이스와 약간 다른 측면이 있어서, 일단 컬렉션을 파일 디렉토리 시스템의 폴더라고 비유적으로 이해하는 것이 좋을 것 같다.

 

chats라는 컬렉션을 만든다. 문서 ID는 자동 ID로 설정한다.

 

 

이런 구조는 채팅앱에서 chats라는 테이블이 있고, 그 안에 자동 ID 값을 갖는 채팅방이 있다고 생각하면 된다. 그 안에 추가로 생성할 컬렉션은 사용자들이 입력하는 채팅 메시지 정보를 보관한다고 생각하면 된다. 

 

chats(DB 테이블) -> 문서: 채팅방 -> 컬렉션: 각 채팅 message(입력한 사람, 채팅 내용, 입력 날짜 등)

 

하위 컬렉션으로 message 컬렉션 및 문서를 만들어준다. 그리고 text 필드를 만든다.

 

 

데이터베이스 데이터 조회

이렇게 만들어진 데이터를 앱에서 조회해본다.

 

인증 규칙 문제

강의에는 후반부에 나오지만, 파이어베이스의 데이터에 일단 접근하기 위해서 데이터베이스 접근 규칙을 바꿔줘야한다. Firestore/규칙 부분에 가서 if 뒷부분이 false로 되어있는 것을 true로 바꿔주어야 한다. 데이터베이스의 read, write 권한을 모두에게 허용하겠다는 것이 되는데, 보안상 문제가 될 수 있으나 일단 테스트용이므로 별 문제가 되지 않고, 이 다음 정리 글에서 보안 규칙을 다시 업데이트할 것이므로 일단은 넘어가도록 한다.

 

 

 

 

 

앞서 만들어놓았던 chat_screen에서 cloud_firebase를 import하고, body 부분에 StreamBuilder를 작성한다. 이 StreamBuilder는 flutter 자체에서 제공하는 것이다.

 

데이터 베이스에 접근하기 위해서 stream 속성에 FirebaseFirestore 를 불러오고 .instance.collection({컬렉션 및 문서 경로명})을 입력해준다. 그리고 뒤에 붙는 snapshots는 Stream을 반환해주는 메서드로, 데이터가 바뀔 때마다 새로운 값을 전달해준다. 이렇게 전달되는 데이터들은 snapshots에 담긴다. snapshot은 QuerySnapshot 타입의 Map 형태로 담긴다. 이것을 snapshot.data.docs 문법을 적용하여 실제 값을 사용할 수 있다.

 

snapshots() 까지 작성 후 StreamBuilder에서 alt + Enter | option + Enter를 누르면 builder argument를 추가하는 옵션이 나온다. 추가해주면 builder 속성값에 context 및 AsyncSnapshot을 자동으로 추가해준다. AsyncSnapshot은 context와 최신의 snapshot을 가져온다.

body: StreamBuilder(
      stream: FirebaseFirestore.instance
          .collection('chats/Vs3Cb5II1GaLXsq2QyUo/message')
      .snapshots(), 
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {  },
    )
);

 

AsyncSnapshot에서 불러오는 snapshot 들을 리스트 형태로 렌더링해줄 수 있는 ListView.builder를 불러온다. itemCount는 몇 개의 항목을 보여줄 것인지를 결정하고, itemBuilder는 각 항목들을 렌더링할 함수를 지정해준다. 상위에서 지정한 docs는 message안에 있는 문서들을 뜻하고, python의 반복문에서의 index값을 불러오듯이 index 값을 지정하여 각 문서의 text 필드값을 docs[index]['text']로 표현해주었다.

 

body: StreamBuilder(
  stream: FirebaseFirestore.instance
      .collection('chats/Vs3Cb5II1GaLXsq2QyUo/message')
      .snapshots(),
  builder: (BuildContext context,
      AsyncSnapshot<QuerySnapshot<Map<String,
          dynamic>>> snapshot) {
    final docs = snapshot.data?.docs;
    if (snapshot.connectionState == ConnectionState.waiting ||
    snapshot.data == null) {
      return Center(
          child: CircularProgressIndicator(),
      );
    } else {
      return ListView.builder(
          itemCount: docs!.length,
          itemBuilder: (context, index) {
            return Container(
              padding: EdgeInsets.all(8.0),
              child: Text(
                docs[index]['text'],
                style: TextStyle(
                    fontSize: 20.0
                ),
              ),
            );
          }
      );
    }
  },
));

 

modal_progress_hud

상기 적용한 CircularProgressIndicator 대신 간편하게 적용할 수 있는 라이브러리이다. 이런 패키지를 적용하면 네트워크에 문제가 생겼거나 데이터를 불러들이는데 시간이 오래 걸리는 경우 등을 처리해줘서 편하게 사용자에게 안내할 수 있다.

 

설치 및 main_screen에 import한다. 그리고 만약 Warning이 뜨는 경우 compileSdkVersion을 32로 업데이트해준다. 지난 글에서 살펴본 내용으로, andriod의 build.gradle에서 바라보는 local.properties를 바꿔주면 된다.

 

 

이후 bool type의 showSpinner = false;로 설정해준다. 이 showSpinner 변수 값에 따라 로딩 중 화면이 표시되도록 할 것이다.

import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';

//중략
class _LoginSignupScreenState extends State<LoginSignupScreen> {
  final _authentication = FirebaseAuth.instance;
  bool isSignupScreen = true;
  bool showSpinner = false;
  
  
//중략

 

기존 Scaffold 아래에 unfocus를 위해 두었던 GestureDetector 위젯 상위에 ModalProgressHUD 위젯을 배치하고 isAsyncCall에 showSpinner 변수를 둔다.

 

그리고 Signin, Signup 전 후로 setState를 불러와서 각 상황에 맞게 showSpinner 변수값을 true, false로 지정해주면 된다.

body: ModalProgressHUD(
    inAsyncCall: showSpinner,
    child: GestureDetector(
          onTap: () async {
            if (isSignupScreen){  //Signup 일때
              _tryValidation();
              try {
                setState(() {
                  showSpinner = true;
                });
                final newUser = await _authentication.createUserWithEmailAndPassword(
                    email: userEmail, password: userPassword
                );
                if(newUser.user != null) {
                                    Navigator.push(context, MaterialPageRoute(builder: (context){
                                      return ChatScreen();
                                    }),
                                    );
                                    // newUser.user를 받아서 push로 이동 후 showSpinner는 false 처리
                                    setState(() {
                                      showSpinner = false;
                                    });
                                  }
                                } catch (e){

 

 

728x90
반응형