React Native/React Native_study

[리액트 네이티브를 다루는 기술 #17] 8장 Firebase로 사진 공유 앱 만들기1 (p .445 ~ 452)

bocoder
728x90
반응형

@ List

** 8.2.5 Firebase로 회원 인증하기

** 8.2.6 오류 예외 처리하기

** 8.2.7 사용자 프로필 Firebase에 담기

 

@ Note

1. React Native Firebase 회원 인증

 - Firebase에서 제공하는 함수들을 컴포넌트에 바로 사용하지 않고 임의의 함수를 따로 만들어서 호출함

 - 함수를 감싸는 작업을 한 번 하면 추후 firebase를 사용하지 않고 다른 방식으로 인증할 경우에도 코드를 쉽게 전환 가능 

  > reference : https://rnfirebase.io/auth/usage 

 

2. loading 을 통한 상태 관리

 - async/await 문법을 사용한 비동기 처리 시, loading & setLoading 을 적절히 사용

 

3. 오류 처리

 - e 객체의 code 를 통해 어떤 에러인지 판별 가능

  > 로그인 : https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#signinwithemailandpassword

  > 회원가입 : https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#createuserwithemailandpassword

 

4. AND 연산자, OR 연산자

 - AND 연산자 : A && B
  > ex) return { flag && "안녕하세요"} ;

           A, B 모두 true 일 때 B를 반환
           A, B 중 하나라도 false 인 경우 아무것도 반환하지 않음
 - OR 연산자 : A || B
  > ex) return { "flag" || "오랜만이네요"} ;

           A가 true 인 경우 A 를 반환 
           A가 false, B가 true인 경우 B를 반환
           A, B가 false인 경우 아무것도 반환하지 않음

 

5. Firebase Authentication 

 - Firebase 콘솔을 설정하고 App에서 회원 가입 시, 정상적으로 Authentication 페이지의 Users 탭에 등록됨

 

6. Firestore  

- Firebase에서 제공하는 NoSQL 베이스로 아래와 같이 생성함

 

@ Result

iOS / android - loading 동작 테스트

 

01234
iOS / android - 오류 출력 테스트

** 오류 출력 관련 추가된 내용 : https://bocoder.tistory.com/88

 

@ Git

 - https://github.com/eunbok-bocoder/PublicGalleryBocoder/commits/main

 

# Source tree

* AppDelegate.m 수정 이유 : https://bocoder.tistory.com/77

 

# components > SignButton.js

import React from 'react';
import {ActivityIndicator, StyleSheet, View} from 'react-native';
import CustomButton from '../components/CustomButton';
import {useNavigation} from '@react-navigation/native';

function SignButtons({isSignUp, onSubmit, loading}) {
  const navigation = useNavigation();

  const primaryTitle = isSignUp ? '회원가입' : '로그인';
  const secondaryTitle = isSignUp ? '로그인' : '회원가입';

  const onSecondaryButtonPress = () => {
    if (isSignUp) {
      navigation.goBack();
    } else {
      navigation.push('SignIn', {isSignUp: true});
    }
  };

  if (loading) {
    return (
      <View style={styles.spinnerWrapper}>
        <ActivityIndicator size={32} color="#6200ee" />
      </View>
    );
  }

  return (
    <View style={styles.buttons}>
      <CustomButton title={primaryTitle} hasMarginBottom onPress={onSubmit} />
      <CustomButton
        title={secondaryTitle}
        theme="secondary"
        onPress={onSecondaryButtonPress}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  spinnerWrapper: {
    maginTop: 64,
    height: 104,
    justifyContent: 'center',
    alignItems: 'center',
  },
  buttons: {
    marginTop: 64,
  },
});

export default SignButtons;

 

# lib > auth.js

import auth from '@react-native-firebase/auth';

// 로그인
export function signIn({email, password}) {
  return auth().signInWithEmailAndPassword(email, password);
}

// 회원가입
export function signUp({email, password}) {
  return auth().createUserWithEmailAndPassword(email, password);
}

// 앱 가동 or 로그인 상태 변경 시, 현재 사용자의 정보를 파라미터로 받아오는 콜백함수
export function subscribeAuth(callback) {
  return auth().onAuthStateChanged(callback);
}

// 로그아웃
export function signOut() {
  return auth().signOut();
}

 

# lib > users.js

import firestore from '@react-native-firebase/firestore';

export const usersCollection = firestore().collection('users');

export function createUser({id, displayName, photoURL}) {
  return usersCollection.doc(id).set({
    id,
    displayName,
    photoURL,
  });
}

export async function getUser(id) {
  const doc = await usersCollection.doc(id).get();
  return doc.data(); // 다음 블로그 글인 #18 단계에서 추가함, gibhub 이용 시 참고  
}

 

# screens > SignInScreen.js

import React, {useState} from 'react';
import {
  Alert,
  Keyboard,
  KeyboardAvoidingView,
  Platform,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context';
import SignButtons from '../components/SignButton';
import SignForm from '../components/SignForm';
import {signIn, signUp} from '../lib/auth';

function SignInScreen({navigation, route}) {
  const {isSignUp} = route.params ?? {}; // null 병합 연산자
  const [form, setForm] = useState({
    email: '',
    password: '',
    confirmPassword: '',
  });

  const [loading, setLoading] = useState();

  const createChangeTextHandler = name => value => {
    setForm({...form, [name]: value});
  };

  const onSubmit = async () => {
    Keyboard.dismiss();
    console.log('[Log] form : ', form);

    const {email, password, confirmPassword} = form;

    if (isSignUp && password !== confirmPassword) {
      Alert.alert('실패', '비밀번호가 일치하지 않습니다.');
      return;
    }

    setLoading(true);
    const info = {email, password};

    try {
      const {user} = isSignUp ? await signUp(info) : await signIn(info);
      console.log('[LOG] user : ', user);
    } catch (e) {
      const message = {
        'auth/email-already-in-use': '이미 가입된 이메일입니다.',
        'auth/wrong-password': '잘못된 비밀번호입니다.',
        'auth/user-not-found': '존재하지 않는 계정입니다.',
        'auth/invalid-email': '유효하지 않은 이메일 주소입니다.',
      };

      const msg = message[e.code] || `${isSignUp ? '가입' : '로그인'} 실패`;
      Alert.alert('실패', msg);
      console.log('[Log] error :', e);
    } finally {
      setLoading(false);
    }
  };

  return (
    <KeyboardAvoidingView
      style={styles.KeyboardAvoidingView}
      behavior={Platform.select({ios: 'padding'})}>
      <SafeAreaView style={styles.fullscreen}>
        <Text style={styles.text}>PublicGallery</Text>
        <View style={styles.form}>
          <SignForm
            isSignUp={isSignUp}
            onSubmit={onSubmit}
            form={form}
            createChangeTextHandler={createChangeTextHandler}
          />
          <SignButtons
            isSignUp={isSignUp}
            onSubmit={onSubmit}
            loading={loading}
          />
        </View>
      </SafeAreaView>
    </KeyboardAvoidingView>
  );
}

const styles = StyleSheet.create({
  KeyboardAvoidingView: {
    flex: 1,
  },
  fullscreen: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 32,
    fontWeight: 'bold',
  },
  form: {
    marginTop: 64,
    width: '100%',
    paddingHorizontal: 16,
  },
  //   buttons: {
  //     marginTop: 64,
  //   },
});

export default SignInScreen;
728x90
반응형