본문 바로가기

앱개발

앱 개발 - flutter (4) 파이어베이스 서버 이용한 채팅 앱 2

지난번에 ui 구성을 완성했으니 이제 서버 연동을 위해서 firebase를 이용해볼 차례이다.

firebase 홈페이지에 들어가면

이렇게 뜨는데 우측 상단에 go to console을 눌러서 구글 계정으로 로그인을 하고 프로젝트를 만들면 된다.

 

먼저 앱에 firebase를 등록해야 한다.

여기서 안드로이드를 선택해주고 들어가면

이렇게 뜬다.

패키지 이름은 android > app > build.gradle 파일 > defaultConfig 부분에서 찾을 수 있다.

나머지는 입력할 필요 없고 앱 등록을 누르고 나오는 google-service.json 파일을 받아서 android > app 폴더에 넣어주면 된다.

이후에는 그냥 시키는대로 하면 되는데 우선 app 말고 android 폴더 내의 build.gradle 파일에

plugins 코드를 복붙해준다.

그리고 다시 app 수준의 gradle 파일로 가서 

시키는 대로 dependencies와 plugins 에 코드를 복붙해준다.

 

이제 인터넷에 firebase flutter firestore를 검색해서 

cloud_firestore로 가서 installing에 들어간다.

dependencies를 복사해서 pubspec.yaml 파일에 붙여넣어 준다.

이번에는 flutter firebase core를 검색하고 나온 사이트에서 마찬가지고 installing에 들어가서 dependencies를 yaml 파일에 넣어준다. 마찬가지로 firebase auth 패키지도 install 해준다.

하다 보면 에러가 진짜 미친듯이 많이 나오는데 에러메시지를 잘 검색해서 해결하길 바란다.

내가 고쳤던 에러 중에 생각나는거 몇개를 말하자면 일단 plugins 코드는 해당 파일의 맨 위에다 위치시켜야 한다. 안 그러면 에러가 뜬다.

그리고 app의 build.gradle 파일에 가면 defaultConfig에서 minSdk가 있는데 flutter.어쩌고로 됐있을텐데 그거 버전이 낮아서 안된 경우도 있어서 23으로 바꿔놨다.

이외에도 에러들이 많이 생길텐데 포기하지 말고 끝까지 해보길 바란다.

 

이제 오랜만에 main.dart 파일로 가서 main 메소드에 runApp 코드 위에 

WidgetsFlutterBinding.ensureInitialized();

Firebase.initializeApp();

코드를 넣어준다.

flutter에서 firebase를 사용하려면 메인 메소드에서 비동기 방식으로 initialized 메소드를 불러와야 하기 때문이다.

메인 메소드는 async 방식으로 바꿔줬다.

동기는 어떤 작업을 할 때 그 작업이 끝나야 다음 작업을 진행하는 것이고, 비동기는 안 끝나도 다음 작업이 진행되는 것이다.

async는 함수가 비동기 방식임을 지정해주고, await는 반대로 동기 방식임을 지정해준다.

 

원래라면 여기서 성공을 했어야 했다..

하지만 여기서부터 어마어마하게 많은 에러들이 발생하고 이를 다 해결할 때까지 몇시간이 걸렸는지 모르겠다.

하도 많은 에러를 고치다 보니 기억이 잘 안나는 부분도 있는데 생각나는 대로 해결법을 적어보겠다.

당장은 에러가 안 나더라도 이를 고치지 않으면 아래에서 결국 firebase를 연동하는 기능을 사용하려고 할 때 에러가 발생할 것이다.

(그때 다시 여기로 와서 에러를 고쳐도 된다. 당장 에러가 나지 않으면 어쩔 수 없으니 계속 안 나길 기도하면서 넘어가자.)

대충 [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(null-error, Host platform returned null value for non-null return value., null, null) 이런 에러가 발생할 텐데, 해결하기 위해서는 

일단 터미널에서

dart pub global activate flutterfire_cli

dart pub global activate flutterfire_cli

이 명령어들을 이용해서 cli를 설치해야 한다.

그리고 나서 flutterfire configure 명령어를 치면 된다는데 여기서 flutterfire은 없는 배치 파일 어쩌구 하면서 에러가 생긴다.

애초에 firebase가 설치가 안돼있었기 때문인데 일단 해결하기 위해서 nodejs에서 firebase를 설치해야 한다.

나는 nodejs가 이미 깔려있었어서 그나마 다행이었다.

npm install -g firebase-tools

명령어를 터미널에서 돌리면 되는데 npm이 또 없다고 뜬다.

해결하기 위해서 환경변수를 조작해야 하는데 시스템 환경 변수에서 path 부분을 편집하면 된다.

밑에서 2번째도 새로 추가된 것인데 이건 중간에 에러 메시지 중 하나에 나오는 export path를 환경변수에 추가한 것이다.

하다가 중간에 export path 어쩌구 하는 에러 메시지가 뜬다면 저걸 환경변수에 추가해보는걸 추천한다.

아무튼 nodejs의 path를 환경변수에 추가해주면 npm 문제는 해결된다.

설치를 했으면 이제 firebase login 이라는 명령어를 친다.

터미널 창에 나오는 링크로 이동해서 구글 로그인을 해주고 나면 해결된다.

이제 다시 flutterfire configure 명령어를 치면 app이랑 os 등을 선택해주고 app id를 입력하라고 한다.

위에서 firebase 홈페이지에서 입력했던 앱 id (gradle 파일에서 defaultconfig 부분에 있었던) 를 입력한다.

그러면 firebase_options.dart 파일이 생성될 것이고,

await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

이렇게 initailize 메소드 안에 options를 추가시켜주면 된다.

import 'firebase_options.dart';

이것도 추가해줘야 한다.

드디어 끝났나 싶었는데 에러가 또 생긴다.

이렇게 생기길래 프로젝트의 gradle 파일에 들어가서

구글 서비스 버전을 에러 메시지에 적힌대로 바꿔줬다.

드디어 flutter run이 에러 없이 실행되고 앱이 켜졌다.

중간중간에 에러가 더 많이 생겼었던거 같지만 몇시간을 헤매느라 다는 기억이 안나고 생각나는 해결 방법만 적었다.

그래도 핵심은 다 적었으니 이 정도면 웬만한 에러는 다 해결할 수 있을 것이다.

 

 

이제 로그인, 회원가입 기능을 firebase와 연동해볼 차례이다.

main_screen.dart 파일에서 저번에 작업했던 TextFormField 위젯으로 가서

onSaved 메소드 밑에 onChanged 메소드를 만들고 똑같이 코드를 작성한다.

5개의 텍스트폼필드에 대해 반복한다.

그리고 password를 입력할 때 보이지 않게 하기 위해 간단하게 TextFormField위젯에서 obscureText: true 코드를 추가해준다.

 

이제 firebase auth 패키지를 import해준다.

import 'package:firebase_auth/firebase_auth.dart';

그리고 class 안에 FirebaseAuth의 인스턴스를 만들어준다.

변수명 앞에 _를 붙이는 건 private으로 만들기 위해서이다.

public은 외부에서 참조하거나 가져올 수 있는 변수이고 private은 반대로 외부에서 건드릴 수 없는 변수이다.

 

이제 전송버튼의 AnimatedPositioned 위젯의 onTap 메소드로 가서 signup부터 구현해볼 것이다.

onTap: () async {
    if (isSignupScreen) {
    _tryValidation();
    try {
      final newUser = await _authentication
          .createUserWithEmailAndPassword(
              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,
        ),
      );
    }
   }
 },

일단 signup 화면일 때를 if문으로 검사하고, validation을 진행한 후에 authentication을 진행할 건데

에러가 생길 수도 있으니 try exception을 이용한다.

newUser라는 변수에 authentication 정보를 담고, 이메일과 패스워드를 보내서 회원가입을 진행한다.

그리고 newUser 변수가 null이 아닐 때 회원가입이 성공적으로 됐다고 판단하고, 창을 바꿔준다.

Navigator는 창 전환에 쓰이는 flutter 라이브러리이다.

새로운 창은 ChatScreen으로 새로운 dart파일에 만들어 두었다.

그리고 에러가 생겼을 때는 사용자에게도 알려주기 위해서 SnackBar로 이메일과 패스워드를 체크하라고 띄운다.

 

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';

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

  @override
  _ChatScreenState createState() => _ChatScreenState();
}

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

  @override
  void 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'),
        ));
  }
}

이 부분이 회원가입을 성공했을 때 이동되는 페이지이다.

chat_screen.dart 파일에 만들었다.

firebase로 가서 build에 Authentication으로 가서 로그인 사용 설정을 활성화한다.

이제 앱을 실행해서 회원가입 창에서 입력을 하고 보내면, 

이렇게 다음 페이지로 넘어오고,

firebase에서도 사용자에 제대로 등록이 된다.

 

회원가입을 성공했으니 이제 로그인 기능도 구현해야 한다.

다시 전송버튼의 onTap 메소드로 가서 이번에는 signupScreen이 아닐 경우(로그인 창일 경우)에 대해 기능을 작성한다.

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,
        ),
      );
    }
  }

signup 코드랑 함수 빼고 다 똑같다.

함수 부분만 signInwith로 바꿔주면 된다.

이렇게 로그인을 하면 chatScreen으로 잘 넘어간다.

(저 이메일은 내 실제 이메일이 아니니까 뭔가를 보내지 말자)

이제 로그아웃 버튼을 만들어 볼 것이다.

로그인을 해서 들어간 chat_screen에서 로그아웃 버튼을 만들어야 한다.

Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Chat Screen'),
          actions: [
            IconButton(
              icon: Icon(
                Icons.exit_to_app_sharp,
                color: Colors.blue,
              ),
              onPressed: () {
                _authentication.signOut();
                Navigator.pop(context);
              },
            )
          ],
        ),
        body: Center(
          child: Text('Chat Screen'),
        ));
  }

이렇게 actions 부분에 iconbutton을 추가해서 만들었다.

onPressed에서 signOut 함수로 로그아웃을 하고, Navigator.pop으로 페이지를 나와서 다시 로그인, 회원가입 창으로 이동하게 했다.

여기 우측 상단에 보이는 파란 버튼을 누르면 로그아웃이 되고 로그인, 회원가입 창으로 이동한다.