[Flutter] 스나이퍼팩토리 Flutter 중급과정 (14)

GONG·2023년 5월 13일
0
post-thumbnail

Firebase 인증

  • Firebase Authentication을 사용하면 인증 시스템을 쉽게 구축할 수 있움

인증 방식

  • 비밀번호 인증
  • 이메일 링크 인증
  • 전화번호
  • sns 인증

비밀번호 인증 방식

  • ID/PW 입력 받음
  • FirebaseAuth에 요청 (자동으로 키값 및 데이터를 저장해줌)
  • 유저 상태가 변하면 페이지 이동할 수 있도록 함

SNS 인증 방식

  • 구글 연동해놓고
  • 버튼 하나 놓기(자동으로 연동 가능)
  • 유저 상태가 변하면 페이지 이동할 수 있도록 함

Firebase Auth

인증상태 바뀔 때마다 함수 실행

  • 코드
    FirebaseAuth
    	.instance                  // 싱글톤 객체 가져오기
    	.authStateChanges()        // 인증이 바뀌는 것을
    	.listen((User? user) {     // 계속 지켜보고 실행하겟
    		if (user == null) {
    			print('User if currently signed out!');
    		} else {
    			print('User is signed in!');
    		}
    	});

이메일/비밀번호 인증

  • 콘솔 > 빌드 > Authentication > 시작하기
  • Sign-in method > 이메일/비밀번호

  • 사용설정 키고 저장

  • 짜잔

회원가입

FirebaseAuth
	.instance
	.createUserWithEmailAndPassword();
  • 테스트 코드
    FirebaseAuth
        .instance
        .createUserWithEmailAndPassword(
            email: 'test@gmail.com', 
            password: '12341234'
        );

로그아웃

FirebaseAuth.instance.signOut();

로그인

FirebaseAuth
	.instance
	.signInWithEmailAndPassword(
	  email: 'test@gmail.com',
	  password: '12341234'
	);

코드 작성

  • main.dart
    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:firebase_core/firebase_core.dart';
    import 'package:flutter/material.dart';
    
    import '../../../../firebase_options.dart';
    import 'login_page.dart';
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();  // 메인 함수에 비동기 함수 있으면 추가해줘야함
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
      runApp(const MyApp());
    }
    
    class MyApp extends StatefulWidget {
      const MyApp({super.key});
    
      
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
    
      
      void initState() {
        super.initState();
        FirebaseAuth.instance.authStateChanges().listen((user) {
          print(FirebaseAuth.instance.currentUser);
          if (user != null) {
            print('회원가입이 됐거나 유저가 들어왔다');
            return;
          }
          print('회원가입이나 로그인이 필요하다');
        });
      }
    
      
      Widget build(BuildContext context) {
        return MaterialApp(
          home: LoginPage(),
        );
      }
    }
  • login_page.dart
    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:flutter/material.dart';
    
    class LoginPage extends StatefulWidget {
      const LoginPage({Key? key}) : super(key: key);
    
      
      State<LoginPage> createState() => _LoginPageState();
    }
    
    class _LoginPageState extends State<LoginPage> {
      _handleLoginButton() async {
        var res = await FirebaseAuth.instance.signInWithEmailAndPassword(
          email: 'test@gmail.com',
          password: '12341234'
        );
        print(res);
      }
    
      _handleSignUpButton() {
        print('회원가입 버튼 눌려짐');
        FirebaseAuth.instance.createUserWithEmailAndPassword(
          email: 'test@gmail.com',
          password: '12341234'
        );
      }
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.white,
          body: Center(
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 40.0),
              child: SingleChildScrollView(
                physics: NeverScrollableScrollPhysics(),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    SizedBox(height: 50),
                    TextField(
                      decoration: InputDecoration(
                        labelText: '아이디',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    TextField(
                      decoration: InputDecoration(
                        labelText: '비밀번호',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    Container(
                      width: double.infinity,
                      child: ElevatedButton(
                        onPressed: _handleLoginButton,
                        child: Text('로그인'),
                      ),
                    ),
                    TextButton(
                      onPressed: _handleSignUpButton,
                      child: Text('회원가입')
                    ),
                    TextButton(
                      onPressed: () {
                        FirebaseAuth.instance.signOut();
                      },
                      child: Text('로그아웃')
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }

구글 인증

Future<UserCredential> signInWithGoogle() async {
    // Trigger the authentication flow
    final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();

    // Obtain the auth details from the request
    final GoogleSignInAuthentication? googleAuth = await googleUser?.authentication;

    // Create a new credential
    final credential = GoogleAuthProvider.credential(
      accessToken: googleAuth?.accessToken,
      idToken: googleAuth?.idToken,
    );

    // Once signed in, return the UserCredential
    return await FirebaseAuth.instance.signInWithCredential(credential);
  }

코드 작성

  • main.dart
    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:firebase_core/firebase_core.dart';
    import 'package:flutter/material.dart';
    
    import '../../../../firebase_options.dart';
    import 'login_page.dart';
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();  // 메인 함수에 비동기 함수 있으면 추가해줘야함
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
      runApp(const MyApp());
    }
    
    class MyApp extends StatefulWidget {
      const MyApp({super.key});
    
      
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
    
      
      void initState() {
        super.initState();
        FirebaseAuth.instance.authStateChanges().listen((user) {
          print(FirebaseAuth.instance.currentUser);
          if (user != null) {
            print('회원가입이 됐거나 유저가 들어왔다');
            print(user.displayName);
            print(user.uid);
            print(user.email);
            return;
          }
          print('회원가입이나 로그인이 필요하다');
        });
      }
    
      
      Widget build(BuildContext context) {
        return MaterialApp(
          home: LoginPage(),
        );
      }
    }
  • login_page.dart
    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:flutter/material.dart';
    import 'package:google_sign_in/google_sign_in.dart';
    
    class LoginPage extends StatefulWidget {
      const LoginPage({Key? key}) : super(key: key);
    
      
      State<LoginPage> createState() => _LoginPageState();
    }
    
    class _LoginPageState extends State<LoginPage> {
      _handleLoginButton() async {
        var res = await FirebaseAuth.instance.signInWithEmailAndPassword(
          email: 'test@gmail.com',
          password: '12341234'
        );
        print(res);
      }
    
      _handleSignUpButton() {
        print('회원가입 버튼 눌려짐');
        FirebaseAuth.instance.createUserWithEmailAndPassword(
          email: 'test@gmail.com',
          password: '12341234'
        );
      }
    
      Future<UserCredential> signInWithGoogle() async {
        // Trigger the authentication flow
        final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
    
        // Obtain the auth details from the request
        final GoogleSignInAuthentication? googleAuth = await googleUser?.authentication;
    
        // Create a new credential
        final credential = GoogleAuthProvider.credential(
          accessToken: googleAuth?.accessToken,
          idToken: googleAuth?.idToken,
        );
    
        // Once signed in, return the UserCredential
        return await FirebaseAuth.instance.signInWithCredential(credential);
      }
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.white,
          body: Center(
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 40.0),
              child: SingleChildScrollView(
                physics: NeverScrollableScrollPhysics(),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    SizedBox(height: 50),
                    TextField(
                      decoration: InputDecoration(
                        labelText: '아이디',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    TextField(
                      decoration: InputDecoration(
                        labelText: '비밀번호',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    Container(
                      width: double.infinity,
                      child: ElevatedButton(
                        onPressed: _handleLoginButton,
                        child: Text('로그인'),
                      ),
                    ),
                    TextButton(
                      onPressed: _handleSignUpButton,
                      child: Text('회원가입')
                    ),
                    TextButton(
                        onPressed: signInWithGoogle,
                        child: Text('구글 로그인')
                    ),
                    TextButton(
                      onPressed: () {
                        FirebaseAuth.instance.signOut();
                      },
                      child: Text('로그아웃')
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }

로그인 시 발생하는 에러

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null, null)

해결

  1. (프로젝트 > app 폴더 하위에 google-service.json 없으면)

    • 프로젝트 > 프로젝트 설정 > 내 앱 > google-service.json 다운로드
    • 플러터 프로젝트 > app 하위에 옮기기
  2. 안드로이드 스튜디오에서 플러터 프로젝트 열고 > 터미널 열기

    • cd android

    • ./gradlew signingReport
      입력

    • SHA-1 키 복사

    • 콘솔에서 디지털 지문 추가 > SHA-1 키 붙여넣

    해결 완.


추가 유저 정보

  • User에 담겨있는 값
    • uId: 유저 고유번호
    • email: 이메일
    • emailVerified: 이메일 인증여부
    • photoURL: 프로필사진
    • displayName: 닉네임
  • Firebse Auth의 User는 커스텀데이터가 없음
  • 추가적인 유저 정보를 관리하려면 Cloud Firestore를 활용하면 됨

    Profile컬렉션 > userId 문서ID > 추가정보 저장

흐름도

  • 저장할 때

    1. 인증방식을 통한 유저정보 획득
    2. 유저정보의 uId 획득
    3. Cloud Firestore에 Profile 컬렉션 생성
    4. Profile 컬렉션에 Document 생성, id를 uId로 설정
  • 불러올 때

    1. 인증방식을 통한 유저정보 획득
    2. 유저정보의 uId 획득
    3. Cloud Firestore에 Profile 컬렉션의 uId에 해당하는 Document 불러오기

유저 정보 추가해보기

  1. profile 컬렉션 만들기 (문서아이디는 uId로)

코드 작성

  • mbti, 직업, 혈액형 수정해보기
  • main.dart
    import 'package:firebase_core/firebase_core.dart';
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../../../../firebase_options.dart';
    import 'controller/auth_controller.dart';
    import 'controller/profile_edit_controller.dart';
    import 'page/login_page.dart';
    import 'page/main_page.dart';
    import 'page/profile_edit_page.dart';
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();  // 메인 함수에 비동기 함수 있으면 추가해줘야함
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return GetMaterialApp(
          initialBinding: BindingsBuilder(() {
            Get.put(AuthController());
            Get.lazyPut(() => ProfileEditController(), fenix: true);
          }),
          getPages: [
            GetPage(name: '/', page: () => const LoginPage()),
            GetPage(name: '/main', page: () => const MainPage()),
            GetPage(name: '/edit/profile', page: () => const ProfileEditPage()),
          ],
          initialRoute: '/',
        );
      }
    }

model

  • profile.dart
    class Profile {
      String mbti;
      String job;
      String bloodtype;
    
      Profile({
        required this.mbti,
        required this.job,
        required this.bloodtype,
      });
    }

controller

  • auth_controller.dart
    import 'package:cloud_firestore/cloud_firestore.dart';
    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:get/get.dart';
    import 'package:google_sign_in/google_sign_in.dart';
    
    import '../model/profile.dart';
    
    class AuthController extends GetxController {
      final Rxn<User> user = Rxn<User>();
      final Rxn<Profile> profile = Rxn<Profile>();
    
      fetchProfile(String uId) async {
        var res = await FirebaseFirestore.instance.collection('profile').doc(uId).get();
        var data = res.data()!;
        profile(Profile(mbti: data['mbti'], job: data['job'], bloodtype: data['bloodtype']));
      }
    
      
      onInit() {
        super.onInit();
        FirebaseAuth.instance.authStateChanges().listen((value) {
          user(value);
          if (value != null) {
            // 유저가 있는 상태
            fetchProfile(value.uid);
            Get.toNamed('/main');
          } else {
            // 유저가 없는 상태
            Get.toNamed('/');
          }
        });
      }
    
      login(id, pw) => FirebaseAuth.instance.signInWithEmailAndPassword(
            email: id,
            password: pw
          );
    
      signup(id, pw) => FirebaseAuth.instance.createUserWithEmailAndPassword(
          email: id,
          password: pw
      );
    
      providerGoogle() async {
        final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
    
        final GoogleSignInAuthentication? googleAuth = await googleUser?.authentication;
    
        final credential = GoogleAuthProvider.credential(
          accessToken: googleAuth?.accessToken,
          idToken: googleAuth?.idToken,
        );
    
        return await FirebaseAuth.instance.signInWithCredential(credential);
      }
    }
  • profile_edit_controller.dart
    import 'package:cloud_firestore/cloud_firestore.dart';
    import 'package:get/get.dart';
    
    import 'auth_controller.dart';
    
    class ProfileEditController extends GetxController {
      final RxnString mbti = RxnString();
      final RxnString job = RxnString();
      final RxnString bloodtype = RxnString();
    
      updateProfile() {
        String uId = Get.find<AuthController>().user.value!.uid;
        FirebaseFirestore.instance.collection('profile').doc(uId).update({
          'mbti': mbti.value,
          'job': job.value,
          'bloodtype': bloodtype.value,
        });
        Get.find<AuthController>().fetchProfile(uId);
      }
    
      
      void onInit() {
        super.onInit();
    
        var profile = Get.find<AuthController>().profile.value;
        if (profile != null) {
          mbti(profile.mbti);
          job(profile.job);
          bloodtype(profile.bloodtype);
        }
      }
    }

page

  • login_page.dart
    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    import 'package:google_sign_in/google_sign_in.dart';
    
    import '../controller/auth_controller.dart';
    
    class LoginPage extends GetView<AuthController> {
      const LoginPage({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.white,
          body: Center(
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 40.0),
              child: SingleChildScrollView(
                physics: NeverScrollableScrollPhysics(),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    SizedBox(height: 50),
                    TextField(
                      decoration: InputDecoration(
                        labelText: '아이디',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    TextField(
                      decoration: InputDecoration(
                        labelText: '비밀번호',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    Container(
                      width: double.infinity,
                      child: ElevatedButton(
                        onPressed: (){},
                        child: Text('로그인'),
                      ),
                    ),
                    TextButton(
                        onPressed: (){},
                        child: Text('회원가입')
                    ),
                    TextButton(
                        onPressed: controller.providerGoogle,
                        child: Text('구글 로그인')
                    ),
                    TextButton(
                        onPressed: () {
                          FirebaseAuth.instance.signOut();
                        },
                        child: Text('로그아웃')
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
  • main_page.dart
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../controller/auth_controller.dart';
    
    class MainPage extends GetView<AuthController> {
      const MainPage({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Obx(() => Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(controller.user.value!.displayName!),
                  Text(controller.profile.value?.bloodtype ?? 'null'),
                  Text(controller.profile.value?.job ?? 'null'),
                  Text(controller.profile.value?.mbti ?? 'null'),
                ],
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () => Get.toNamed('edit/profile'),
            child: Icon(Icons.edit),
          ),
        );
      }
    }
  • profile_edit_page.dart
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../controller/profile_edit_controller.dart';
    
    class ProfileEditPage extends GetView<ProfileEditController> {
      const ProfileEditPage({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Obx(() => Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Text('프로필 수정 및 추가'),
                  DropdownButton(
                    value: controller.job.value,
                    items: ['개발자', '디자이너', '기획자', '마케터'].map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(),
                    onChanged: controller.job,
                    hint: Text('직업을 선택하세요'),
                  ),
                  DropdownButton(
                    value: controller.bloodtype.value,
                    items: ['A', 'B', 'AB', 'O'].map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(),
                    onChanged: controller.bloodtype,
                    hint: Text('혈액형을 선택하세요'),
                  ),
                  DropdownButton(
                    value: controller.mbti.value,
                    items: ['ISTJ', 'ISFJ', 'ISTP', 'INTP'].map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(),
                    onChanged: controller.mbti,
                    hint: Text('MBTI를 선택하세요'),
                  ),
                  TextButton(
                    onPressed: controller.updateProfile,
                    child: Text('적용하기'),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }


35일차 끝!

profile
우와재밋다

0개의 댓글