ReactNative) Firebase, Auth, E-Mail 회원가입 및 로그인

2022. 5. 14. 00:20프로그래밍/개인프로젝트

cd ios && pod install

리액트 네이티브에서 서 Firebase 사용하기

1. https://firebase.google.com/ 이동 -> 우측 상단 '콘솔로 이동' 클릭

 

2. 프로젝트추가 

 

3. 설치 https://rnfirebase.io/

npm install --save @react-native-firebase/app

4. 프로젝트 다 만들어지면 Project name작성

-> analytics 비활성화

-> platform 추가(Android & iOS)


4-1 Android  설정

https://rnfirebase.io/#2-android-setup

 

- package name에 ID 입력

    (id는 내 앱/android/app/build.gradle 안에 defaultConfig 내부에 있음 "applicationId")

- 닉네임 작성

- Debug signing certificate SHA-1 입력 

    (앱에서 콘솔 열어주고 cd android && ./gradlew signingReport 입력 -> Task: app:signingReport내부에 SHA1부분 있음)

- Register App 누른 뒤 로딩 기다리면 설정 파일(google-services.json)을 다운로드할 수 있다

- 설정 파일을 앱의 android/app 내부에 넣어준다

- Next버튼 누르면 나오는 코드들을 정해진 위치에 추가해주면 됨

- 다 했으면 Next

 

4-2 iOS 설정:

https://rnfirebase.io/#3-ios-setup

 

- Firebase에서 Add app 클릭 -> iOS 클릭

- iOS Bundle ID 입력

    (XCode를 열어서 general섹션에 bundle Identifier가 있음)

- 닉네임 설정

- 앱스토어 ID는 이미 앱스토어에 등록되어있는 경우에 작성 (나는 비워뒀음)

- Register app 클릭

- 설정 파일(GoogleService-Info.plist) 다운로드

- XCode에서 설정 파일을 프로젝트에 추가

  (project 우클릭 -> Add File to 'project name' -> 설정파일 누르고 Copy items if needed항목 체크, create group 체크)

- Next버튼을 누른 뒤 코드들을 내 프로젝트의 적절한 위치에 추가하기

- 다했으면 터미널에 입력

cd ios && pod install

 

5. 인증을 위한 Firebae Authentication Module 설치

- Firebase의 콘솔에서 Authentication에 들어간 뒤 Get Started클릭, Email/Password 클릭해서 Enable 시켜준 뒤 Save

 

- 모듈설치

https://rnfirebase.io/auth/usage

npm install @react-native-firebase/auth
// ios는 cd ios && pod install도 해줘야함

- 리빌드, 시뮬레이터&에뮬레이터 재실행하고 작업 시작하기

 

- App.js에 다음을 작성

//App.js 인증을 위한 예시
import React, { useState, useEffect } from 'react';
import auth from '@react-native-firebase/auth';

export default function App() {
	useEffect(()=>{
            console.log(auth().currentUser);
        },[])
        return null;
}
// 콘솔에 null이 찍히면 정상

 

6. 이메일/비밀번호를 통한 인증 및 회원가입

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

- 로그인 확인 :

App.js에서는 auth를 확인해서 보여줄 화면을 선택할 수 있다

const[isLoggedIn, setIsLoggedIn] = useState(false);

// ...코드

auth().onAuthStateChanged((user)=>{
  //user값에 따라 isLoggedIn state값을 변경시켜서 로그인스크린을 보여줄지 메뉴스크린을 보여줄지 정한다
  if(user){
    setIsLoggedIn(true)
  }else{
    setIsLoggedIn(false)
  }
});

// ...코드

<NavigationContainer>
    {isLoggedIn ? <1번컴포넌트 /> : <2번컴포넌트 />}
</NavigationContainer>

 

- 회원가입 : 

useState를 통해 email, password값을 담고

auth().createUserWithEmailAndPassword(email, password) 를 통해서 회원가입한다

catch문에서 유효성 체크를 할 수 있다 

 

const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
    
// 회원가입 함수
    const signupEditing = async() => {
        playSound(require("../asset/audio/btnClickSound.mp3"))
        setLoading(true)
        if(loading){
            return;
        }
        try{
            if(email !=="" && password !==""){
                await auth().createUserWithEmailAndPassword(email, password)
            }else{
                setLoading(false)
                setValidation('칸을 채워주세요')
            }
        }catch(e){
            setLoading(false)
            switch(e.code){
                case "auth/email-already-in-use" : {
                    return setValidation('이미 사용중인 이메일입니다.')
                }
                case "auth/invalid-email" : {
                    return setValidation('이메일을 입력해주세요')
                }
                case "auth/weak-password" : {
                    return setValidation('안전하지 않은 비밀번호입니다.\n다른 비밀번호를 사용해 주세요.')
                }
                case "auth/operation-not-allowed" : {
                    return setValidation('operation-not-allowed \n관리자에게 문의하세요 ')
                }
            }
            console.log("error1 = ", e.code)
        }
    }
    
// ...코드

return
<TextArea 
    placeholder="이메일" 
    value={email} 
    returnKeyType="next"
    keyboardType = "email-address" 
    autoCapitalize="none" 
    autoCorrect={false} 
    onChangeText = {(text) => setEmail(text)} 
    onSubmitEditing = {onSubmitEmail}
/>

<TextArea 
    ref={PasswordInput}
    placeholder="비밀번호" 
    value={password}  
    returnKeyType="done"
    secureTextEntry 
    onChangeText = {(text) => setPassword(text)} 
    onSubmitEditing = {btnAlloter()}
/>

// ...코드
<Btn onPress = {signupEditing} style={{backgroundColor : navCheck == "Signup" ? colors.NAVY : "lightgray"}}>
    {loading ? <ActivityIndicator color="white"/> : <BtnText>회원가입</BtnText>}
</Btn>

- 로그인

회원가입과 똑같은 TextArea를 사용하고 실행 함수만 바꿔준다

(auth().signInWithEmailAndPassword(email, password)를 통해 로그인)

// 로그인 함수
    const loginEditing = async() => {
        playSound(require("../asset/audio/btnClickSound.mp3"))
        //ActivityIndicator컴포넌트 출력
        setLoading(true)
        
        // 두번눌리는걸 방지
        if(loading){
            return;
        }
        try{
            if(email !=="" && password !==""){
                // 입력값이 공백이 아니면 로그인
                await auth().signInWithEmailAndPassword(email, password)
            }else{
                // 입력값이 공백이라면 메시지 출력
                setLoading(false)
                setValidation('칸을 채워주세요')
            }
        }catch(e){
            // 에러 발생시 에러이유를 메시지로 출력
            setLoading(false)
            switch(e.code){
                case "auth/invalid-email" : {
                    return setValidation("이메일을 입력해주세요")
                }
                case "auth/user-disabled" : {
                    return setValidation('user-disabled')
                }
                case "auth/user-not-found" : {
                    return setValidation('존재하지 않는 이메일 입니다')
                }
                case "auth/wrong-password" : {
                    return setValidation('비밀번호가 일치하지 않습니다')
                }
                case "auth/operation-not-allowed" : {
                    return setValidation('auth/operation-not-allowed \n관리자에게 문의하세요')
                }
            }
        }
    }

 

전체 코드

 

App.js

//App.js
import { NavigationContainer } from '@react-navigation/native';
import * as Font from "expo-font"
import AppLoading from 'expo-app-loading';
import React, { useState, useEffect } from 'react';
import {View, ActivityIndicator} from "react-native";
import InStack from './app/navigators/InStack';
import OutStack from './app/navigators/OutStack'
import { SafeAreaView } from 'react-native-safe-area-context';

import auth from '@react-native-firebase/auth';
import { NativeBaseProvider } from 'native-base';
import {SSRProvider} from '@react-aria/ssr'; 

export default function App() {
  const [ready, setReady] = useState(false);
  const onFinish = () => setReady(true);
  const[isLoggedIn, setIsLoggedIn] = useState(false);

  useEffect(()=>{
    auth().onAuthStateChanged((user)=>{
      // user를 받아서 user값이 참이면 로그인, false면 로그아웃 -> user값에 따라 isLoggedIn state값을 변경시켜서 로그인스크린을 보여줄지 메뉴스크린을 보여줄지 정한다
      if(user){
        setIsLoggedIn(true)
      }else{
        setIsLoggedIn(false)
      }
    })
  },[])

  const startLoading = async () =>{
    // 로딩하고 싶은 것들을 담는 공간 
    // (ex. API호출 혹은 정보를 받거나 video요소를 미리 받아놓거나, DB를 미리 열거나, 아이콘을 미리준비)
    await Font.loadAsync({
        "SDChild": require("./app/asset/fonts/SDChildfundkorea.otf")
    })
  };


  if(!ready){
    return (
    <AppLoading
      startAsync={startLoading}
      onFinish={onFinish}
      onError={console.log} />
      // ready가 안되어있으면 AppLoading은 splash screen을 비추도록 강제하고 startAsync를 호출,
      // startAsync가 완료되면 AppLoading은 onFinish함수를 호출, 
      // onFinish는 state를 변경시키고 state가 변경되면 조건문 else에 해당하는 부분을 render한다
    );
  }

  return (
    <SSRProvider>
      <NativeBaseProvider>
        <NavigationContainer>
          {ready && (
            <SafeAreaView style={{flex:1}}>
              {isLoggedIn ? <InStack />: <OutStack />}
            </SafeAreaView>
          )}
        </NavigationContainer>
      </NativeBaseProvider>
    </SSRProvider>
  )
}

 

Login.js

//Login.js
import React, {useState, useRef} from "react";
import {ActivityIndicator,Platform} from "react-native";
import styled from "styled-components/native";
import {colors} from "../component/Color"
import { Audio } from 'expo-av';

import auth from '@react-native-firebase/auth';
import AppleLogin from "../component/firebaseComponent/AppleLogin";
import GoogleLogin from "../component/firebaseComponent/GoogleLogin";

import Greeting from "../component/lottieComponent/Greeting";
import Welcome from "../component/lottieComponent/Welcome";
import GreetingNavy from '../component/lottieComponent/GreetingNavy';


const Container = styled.View`
    justify-content: center;
    align-items: center;
    flex: 1;
`
const GreetingShell = styled.View`
    width: 100%;
    height: 25%;
`
const Contents = styled.View`
    flex: 1;
    width: 80%;
    top: 10px;
    border-radius: 15px;
`
const Nav = styled.View`
    flex-direction: row;
    justify-content: center;
    
`
const NavBtn = styled.TouchableOpacity`
    width: 40%;
    height: 40px;
    justify-content: center;
    /* border: 1px solid red; */
`
const NavBtnText = styled.Text`
    text-align: center;
    font-family: "SDChild";
    font-size: 25px;
`
const Main = styled.ScrollView`
    flex: 1;
    top: 0px;
`
const Empty = styled.View`
    width: 100%;
    padding: 0px;
    height: 30%;
    /* border: 1px solid red; */
`
const TextArea = styled.TextInput`
    width: 90%;
    height: 40px;
    margin: 10px 0px;
    padding: 0px 10px;
    border-radius: 15px;
    border: 2px solid lightgray;
    font-size: 23px;
    font-family: "SDChild";
`
const Btn = styled.TouchableOpacity`
    flex-direction: row;
    width: 70%;
    height: 45px;
    margin: 10px 0px;
    border-radius: 15px;
    /* background-color: #EC705E; */
    align-items: center;
    justify-content: center;
`
const BtnText = styled.Text`
    color: white;
    font-size: 23px;
    font-family: "SDChild";
`
const ValidationShell = styled.View`
    width: 70%;
    height: 18px;
    align-items: center;
    justify-content: center;
`
const ValidationText = styled.Text`
    font-size: 18px;
    font-family: "SDChild";
`
// ------------------------------------------------------------------
const Login = ({navigation}) => {
    const PasswordInput = useRef()
    const [navCheck, setNavCheck] = useState("Login")
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [loading, setLoading] = useState(false);
    const [validation, setValidation] = useState("")

    //  이메일입력 후 키보드에 next를 탭하면 password로 포커스를 이동
    const onSubmitEmail = () => {
        PasswordInput.current.focus();
    }

    // 로그인을 진행하는 함수
    const loginEditing = async() => {
        playSound(require("../asset/audio/btnClickSound.mp3"))
        //ActivityIndicator컴포넌트 출력
        setLoading(true)
        // 두번눌리는걸 방지
        if(loading){
            return;
        }
        try{
            if(email !=="" && password !==""){
                // 입력값이 공백이 아니면 로그인
                await auth().signInWithEmailAndPassword(email, password)
            }else{
                // 입력값이 공백이라면 유효성 체크 메시지 출력
                setLoading(false)
                setValidation('칸을 채워주세요')
            }
        }catch(e){
            // 에러 발생시 에러이유를 유효성 체크 메시지로 출력
            setLoading(false)
            switch(e.code){
                case "auth/invalid-email" : {
                    return setValidation("이메일을 입력해주세요")
                }
                case "auth/user-disabled" : {
                    return setValidation('user-disabled')
                }
                case "auth/user-not-found" : {
                    return setValidation('존재하지 않는 이메일 입니다')
                }
                case "auth/wrong-password" : {
                    return setValidation('비밀번호가 일치하지 않습니다')
                }
                case "auth/operation-not-allowed" : {
                    return setValidation('auth/operation-not-allowed \n관리자에게 문의하세요')
                }
            }
        }
    }

// 회원가입 버튼
    const signupEditing = async() => {
        playSound(require("../asset/audio/btnClickSound.mp3"))
        setLoading(true)
        if(loading){
            return;
        }
        try{
            if(email !=="" && password !==""){
                await auth().createUserWithEmailAndPassword(email, password)
            }else{
                setLoading(false)
                setValidation('칸을 채워주세요')
            }
        }catch(e){
            setLoading(false)
            switch(e.code){
                case "auth/email-already-in-use" : {
                     return setValidation('이미 사용중인 이메일입니다.')
                }
                case "auth/invalid-email" : {
                    return setValidation('이메일을 입력해주세요')
                }
                case "auth/weak-password" : {
                     return setValidation('안전하지 않은 비밀번호입니다.\n다른 비밀번호를 사용해 주세요.')
                }
                case "auth/operation-not-allowed" : {
                    return setValidation('operation-not-allowed \n관리자에게 문의하세요 ')
                }
            }
            console.log("error1 = ", e.code)
        }
    }
    //Login인지 Signup인지 확인 후 버튼에 쓰이는 함수를 나눠줌 
    const btnAlloter = () => {
        if(navCheck == "Login"){
            return loginEditing
        }else{
            return signupEditing
        }
    }
    // 오디오출력 관련 함수
    function playSound(sound){
        Audio.Sound.createAsync( sound,{ shouldPlay: true }
        ).then((res)=>{
            res.sound.setOnPlaybackStatusUpdate((status)=>{
                if(!status.didJustFinish) return;
                res.sound.unloadAsync().catch(()=>{});
            });
        }).catch((error)=>{});
    }

    return(
    <Container>
        <GreetingShell style={{transform:[{scale:3}]}}>
            {/* 로그인/회원가입 TextInput 상단 LottieAnimation */}
            {navCheck == "Login" ? (<Greeting />):(<GreetingNavy />)}
        </GreetingShell>
        <Contents>
            <Nav>
                <NavBtn onPress={() => {setNavCheck("Login"), setValidation("")}}>
                    <NavBtnText style={{color : navCheck == "Login" ? "black" : "lightgray"}}>로그인</NavBtnText>
                </NavBtn>
                <NavBtn onPress={() => {setNavCheck("Signup"), setValidation("")}} >
                    <NavBtnText  style={{color : navCheck == "Signup" ? "black" : "lightgray"}}>회원가입</NavBtnText>
                </NavBtn>
            </Nav>

            <Main contentContainerStyle={{alignItems:"center"}}>
                <TextArea 
                    placeholder="이메일" 
                    value={email} 
                    returnKeyType="next"
                    keyboardType = "email-address" 
                    autoCapitalize="none" 
                    autoCorrect={false} 
                    onChangeText = {(text) => setEmail(text)} 
                    onSubmitEditing = {onSubmitEmail}
                />
    
                <TextArea 
                    ref={PasswordInput}
                    placeholder="비밀번호" 
                    value={password}  
                    returnKeyType="done"
                    secureTextEntry 
                    onChangeText = {(text) => setPassword(text)} 
                    onSubmitEditing = {btnAlloter()}
                />

                <ValidationShell>
                    <ValidationText style={{color:colors.DARKGRAY}}>{validation}</ValidationText>
                </ValidationShell>

                {navCheck=="Login" ? (
                    // 로그인 관련 버튼
                <>
                    <Btn onPress = {loginEditing} style={{backgroundColor : navCheck == "Login" ? "#EC705E" : "lightgray"}}>
                        {loading ? <ActivityIndicator color="white"/> : <BtnText>로그인</BtnText>}
                    </Btn>
                    <GoogleLogin />
                    {Platform.OS == "ios" && (
                        <AppleLogin />
                    )}
                </>
                ):(
                    // 회원가입 관련 버튼
                    <Btn onPress = {signupEditing} style={{backgroundColor : navCheck == "Signup" ? colors.NAVY : "lightgray"}}>
                        {loading ? <ActivityIndicator color="white"/> : <BtnText>회원가입</BtnText>}
                    </Btn>
                )}
            </Main>
        </Contents>

        {navCheck == "Signup" ? (
            <Empty style={{transform:[{scale:1}]}}>
                <Welcome />
            </Empty>
        ):(null)}
    </Container>
    )
}
export default Login;