Skip to content

delaying/ReactNative-Chat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

30 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

์ฑ„ํŒ… ์•ฑ

๊ฒฐ๊ณผ ํ™”๋ฉด

ios๋Š” ์‹ค๋ฌผ ๊ธฐ๊ธฐ๋กœ, android๋Š” ์—๋ฎฌ๋ ˆ์ดํ„ฐ๋กœ ๋…นํ™”ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

ํšŒ์›๊ฐ€์ž… ๋ฐ ๋กœ๊ทธ์ธ

์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ๋ฐ push์•Œ๋ฆผ

  • background, quit, foreground์ƒํƒœ์— ๋”ฐ๋ฅธ push ์•Œ๋ฆผ

  • ์ด๋ฏธ์ง€์™€ ์Œ์„ฑ๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ณ  ํ™•์ธ. ํ™•์ธ์‹œ 1 ์‚ฌ๋ผ์ง

ios

android

์‚ฌ์ง„ ์ž์„ธํžˆ๋ณด๊ธฐ, ๋กœ๊ทธ์•„์›ƒ

์ •๋ฆฌ


๋ชฉ์ฐจ


๊ตฌํ˜„๊ธฐ๋Šฅ

  • ํ…์ŠคํŠธ,์ด๋ฏธ์ง€ ๋ฐ ์Œ์„ฑ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•  ์ˆ˜ ์žˆ๋Š” 1:1 ์ฑ„ํŒ…์•ฑ

  • ๊ธฐ๋Šฅ

    • ํšŒ์›๊ฐ€์ž…,๋กœ๊ทธ์ธ
    • ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๋“ฑ๋ก
    • ์‚ฌ์šฉ์ž ๋ฆฌ์ŠคํŠธ
    • ์ฑ„ํŒ…๋ฐฉ
    • ํ…์ŠคํŠธ, ์ด๋ฏธ์ง€, ์˜ค๋””์˜ค ๋ฉ”์‹œ์ง€ ์ „์†ก
    • ๋ฉ”์‹œ์ง€ ์ฝ์Œ ํ‘œ์‹œ
    • ํ‘ธ์‹œ ์•Œ๋ฆผ
  • ๋ชฉํ‘œ

    • Typescript ๊ธฐ๋ฐ˜ React Naitve ํ”„๋กœ์ ํŠธ๋ฅผ CLI๋ฅผ ์ด์šฉํ•ด์„œ ์ดˆ๊ธฐํ™”
    • ๋ฉ€ํ‹ฐ๋ฏธ๋””์–ด ๋‹ค๋ฃจ๊ธฐ (์ด๋ฏธ์ง€, ์˜ค๋””์˜ค ๋…น์Œ ๋ฐ ์žฌ์ƒ)
    • Firebase๋ฅผ ์ด์šฉํ•˜์—ฌ serverless ํ™˜๊ฒฝ์—์„œ ์•ฑ ๊ฐœ๋ฐœ ( Authentication, Firestore, Storage, Cloud Functions)
    • Firebase Cloud Messaging์„ ์ด์šฉํ•œ ํ‘ธ์‹œ ๋…ธํ‹ฐํ”ผ์ผ€์ด์…˜ ์ „์†ก

firebase

  • react native firebase ์„ค์ •
  • rnfirebase
  • npm install --save @react-native-firebase/app @react-native-firebase/auth @react-native-firebase/firestore

react-navigation ํŒจํ‚ค์ง€ ์„ค์น˜

  • npm install --save @react-navigation/native-stack @react-navigation/native react-native-screens react-native-safe-area-context

ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€ ๊ตฌํ˜„

  • ํ˜•์‹ ํ™•์ธํ•˜๊ธฐ . validator ์‚ฌ์šฉ

    • npm install validator
    • npm install @types/validator --save
    if (!validator.isEmail(email)) {
      return " ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค.";
    }
  • SignupScreen

    • name,email,password ํ™•์ธ ๋ฐ ๊ฒฝ๊ณ ๋ฌธ
    <TextInput
                value={password}
                style={styles.input}
                secureTextEntry //๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ ์‹œ ๊ฐ€๋ ค์ฃผ๋Š”์—ญํ• 
                onChangeText={onChangePasswordText}
              />
    
  • firestore ์„ค์ •

    • authentication, firestore database ์„ค์ •

์ฑ„ํŒ…๋ฐฉ

  • npm i lodash

  • npm i @types/lodash --save

  • await firestore().collection(Collections.CHATS)

    • .add๋Š” ๋ฌธ์„œ ์ƒ๊ด€์—†์ด ์ถ”๊ฐ€
    • .doc๋Š” ๋ฌธ์„œ๋ช… ๋”ฐ๋กœ ์„ค์ • ๊ฐ€๋Šฅ
  • useChat hook ์ž‘์„ฑ

    import { useCallback, useEffect, useState } from 'react';
    import firestore from '@react-native-firebase/firestore';
    import _ from 'lodash';
    
    import { Chat, Collections, User } from '../types';
    
    // userIds๋ฅผ ๋ฐ›์•„์„œ ๊ทœ์น™์— ๋”ฐ๋ผ lodash๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ •๋ ฌํ•ด์ฃผ๋Š” ํ•จ์ˆ˜
    const getChatKey = (userIds: string[]) => {
      // userId๊ฐ’์„ ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜๋Š” ๊ฒƒ
      return _.orderBy(userIds, userId => userId, 'asc');
    };
    
    // ์‚ฌ์šฉ์ž๊ฐ€ ํฌํ•จ๋œ ์ฑ„ํŒ…๋ฐฉ์ด ์žˆ๋‹ค๋ฉด ๋ถˆ๋Ÿฌ์˜ค๊ณ , ์—†๋‹ค๋ฉด ์ƒˆ๋กœ์ƒ์„ฑ
    const useChat = (userIds: string[]) => {
      const [chat, setChat] = useState<Chat | null>(null);
      const [loadingChat, setLoadingChat] = useState(false);
    
      const loadChat = useCallback(async () => {
        try {
          setLoadingChat(true);
          // userIds๋ž‘ ์šฐ๋ฆฌ๊ฐ€ ์ค€ userIds๊ฐ€ ๊ฐ™์€ ์ฑ„ํŒ…๋ฐฉ์ด ์ƒ์„ฑ๋˜์–ด์žˆ๋Š”์ง€ ์ฒดํฌ
          const chatSnapshot = await firestore()
            .collection(Collections.CHATS)
            .where('userIds', '==', getChatKey(userIds))
            .get();
    
          if (chatSnapshot.docs.length > 0) {
            const doc = chatSnapshot.docs[0];
            setChat({
              id: doc.id,
              userIds: doc.data().userIds as string[],
              users: doc.data().users as User[],
            });
            return;
          }
    
          // userId์— userIds๊ฐ€ ํฌํ•จ๋œ ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ ธ์˜ค๊ฒŒ๋จ.
          const usersSnapshot = await firestore()
            .collection(Collections.USERS)
            .where('userId', 'in', userIds)
            .get();
          const users = usersSnapshot.docs.map(doc => doc.data() as User);
          const data = {
            userIds: getChatKey(userIds),
            users,
          };
    
          const doc = await firestore().collection(Collections.CHATS).add(data);
          setChat({
            id: doc.id,
            ...data,
          });
        } finally {
          setLoadingChat(false);
        }
      }, [userIds]);
    
      useEffect(() => {
        loadChat();
      }, [loadChat]);
    
      return {
        chat,
        loadingChat,
      };
    };
    
    export default useChat;

๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ ๋ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ๊ธฐ๋Šฅ

  • firestore์˜ subcollection์„ ์ด์šฉํ•ด์„œ ๋ฉ”์‹œ์ง€ ์ „์†ก ๊ธฐ๋Šฅ ๊ตฌํ˜„

  • subcollection

    • ๋ฉ”์‹œ์ง€ chats ์ปฌ๋ ‰์…˜์˜ ์œ ์ €๋ชฉ๋ก๊ณผ ๋ถ„๋ฆฌํ•˜์—ฌ์•ผํ•จ
    • chat๋‹คํ๋จผํŠธ collection์•ˆ์— subcollection์„ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
    // ์„œ๋ธŒ์ปฌ๋ ‰์…˜ ์—ด์–ด์ฃผ์–ด ์ €์žฅํ•˜๊ธฐ
    const doc = await firestore()
      .collection(Collections.CHATS)
      .doc(chat.id)
      .collection(Collections.MESSAGES)
      .add(data);
    
    // ์ด์ „ ๋ฉ”์‹œ์ง€ + ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€ ์—…๋ฐ์ดํŠธ
    setMessages((prevMessages) =>
      [
        {
          id: doc.id,
          ...data,
        },
      ].concat(prevMessages)
    );
  • ๋ฉ”์‹œ์ง€ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

    const loadMessages = useCallback(async (chatId: string) => {
      try {
        setLoadingMessages(true);
        const messagesSnapshot = await firestore()
          .collection(Collections.CHATS)
          .doc(chatId)
          .collection(Collections.MESSAGES)
          .orderBy("createdAt", "desc")
          .get();
    
        const ms = messagesSnapshot.docs.map<Message>((doc) => {
          const data = doc.data();
          return {
            id: doc.id,
            user: data.user,
            text: data.text,
            createdAt: data.createdAt.toDate(), //db์˜ date๋ฅผ dateํƒ€์ž…์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ๊ธฐ ์œ„ํ•ด toDate()์‚ฌ์šฉ
          };
        });
        setMessages(ms);
      } finally {
        setLoadingMessages(false);
      }
    }, []);
    
    useEffect(() => {
      if (chat?.id != null) {
        loadMessages(chat.id);
      }
    }, [chat?.id, loadMessages]);

์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ๊ตฌํ˜„

  • inverted FlatList
    • inverted ์˜ต์…˜์„ ์ฃผ๋ฉด ์Šคํฌ๋กค์„ ์œ—๋ฐฉํ–ฅ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์Œ
      <FlatList
                inverted
                style={styles.messageList}
                data={messages}
                renderItem={({ item: message }) => {
                  return (
                    <View>
                      <Text>{message.user.name}</Text>
                      <Text>{message.text}</Text>
                      <Text>{message.createdAt.toISOString()}</Text>
                    </View>
                  );
                }}
              />
      

์‹œ๊ฐ„ ์ถœ๋ ฅ

  • npm i moment
  • ํฌ๋งท ํ˜•์‹ ์ž‘์„ฑํ•ด์ฃผ๋ฉด๋จ
<Text style={styles.timeText}>{moment(createdAt).format("HH:mm")}</Text>

์‹ค์‹œ๊ฐ„ ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€ ๋ฐ›๊ธฐ ๊ตฌํ˜„

  • firestore ์‚ฌ์ดํŠธ ์ฐธ๊ณ 

  • onSnapshot() ๋ฉ”์†Œ๋“œ ์‚ฌ์šฉํ•˜๊ธฐ

    firestore()
      .collection(Collections.CHATS)
      .doc(chat.id)
      .collection(Collections.MESSAGES)
      .orderBy("createdAt", "desc")
      .onSnapshot((snapshot) => {
        // ๋ฉ”์‹œ์ง€๊ฐ€ ์ถ”๊ฐ€๋ ๋•Œ๋งŒ ๋‚ด์šฉ๋ณ€๊ฒฝ
        const newMessages = snapshot
          .docChanges()
          .filter(({ type }) => type === "added")
          .map((docChange) => {
            const { doc } = docChange;
            const docData = doc.data();
            const newMessage: Message = {
              id: doc.id,
              text: docData.text,
              user: docData.user,
              createdAt: docData.createdAt.toDate(),
            };
            return newMessage;
          });
        addNewMessage(newMessages);
      });
  • lodash โ†’ uniqBy

    • ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€๊ฐ€ ์ค‘๋ณต๋˜๋”๋ผ๋„ ๊ฐ™์€๋ฉ”์‹œ์ง€๊ฐ€ ์žˆ์œผ๋ฉด lodash uniqBy๋กœ ์ œ๊ฑฐ

      const addNewMessage = useCallback((newMessages: Message[]) => {
        setMessages((prevMessages) => {
          return _.uniqBy(newMessages.concat(prevMessages), (m) => m.id);
        });
      }, []);

ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๋“ฑ๋ก

  • ์ด๋ฏธ์ง€ ํฌ๋กญ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ด์šฉ
    • react-native-image-crop-picker
    • ์œ„ ์‚ฌ์ดํŠธ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ios์™€ android ์…‹ํŒ…ํ•˜๊ธฐ
    const onPressProfile = useCallback(async () => {
      const image = await ImageCropPicker.openPicker({
        cropping: true,
        cropperCircleOverlay: true, //์›๋ชจ์–‘์œผ๋กœ ์‚ฌ์ง„์ด ์ž˜๋ฆผ
      });
    }, []);
  • firebase storage์— ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
    • firebase storage ์‹œ์ž‘ ๋ฐ ํŒจํ‚ค์ง€ ์„ค์น˜
    • npm i @react-native-firebase/storage
    • firebase ๊ด€๋ จ ํŒจํ‚ค์ง€ ๋ฒ„์ „์„ ๋˜‘๊ฐ™์ด ๋งž์ถฐ์ฃผ์–ด์•ผํ•จ
      	"@react-native-firebase/app": "^17.4.2",
          "@react-native-firebase/auth": "^17.4.2",
          "@react-native-firebase/firestore": "^17.4.2",
          "@react-native-firebase/storage": "^17.4.2",
  • ํ”„๋กœํ•„ ๋ณด์—ฌ์ฃผ๊ธฐ ๋ฐ ํ™•๋Œ€๊ธฐ๋Šฅ
    • react-native-image-viewing

๋ฉ”์‹œ์ง€ ์ฝ์Œ ํ‘œ์‹œ ๊ตฌํ˜„

  • firestore server timestamp์ด์šฉ
    • ๋งˆ์ง€๋ง‰์œผ๋กœ ์ฑ„ํŒ…๋ฐฉ์— ๋“ค์–ด์˜จ ์‹œ๊ฐ„ chat db์— ๊ธฐ๋ก
    • ์‹œ๊ฐ„์„ ๋น„๊ต
      • ๋ฉ”์‹œ์ง€ ์ „์†ก์‹œ๊ฐ„ > ์ฑ„ํŒ…๋ฐฉ์— ๋“ค์–ด์˜จ ์‹œ๊ฐ„ : ์•ˆ์ฝ์Œ
      • ๋ฉ”์‹œ์ง€ ์ „์†ก์‹œ๊ฐ„ โ‰ค ์ฑ„ํŒ…๋ฐฉ์— ๋“ค์–ด์˜จ ์‹œ๊ฐ„ : ์ฝ์Œ
  • firestore ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ๋ฅผ ์ด์šฉํ•ด์„œ ๋ฉ”์‹œ์ง€ ์ฝ์€ ์‚ฌ์šฉ์ž ์ •๋ณด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ
  • ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์ง€ ์•Š์€ ์‚ฌ๋žŒ ์ˆ˜ ํ‘œ์‹œ

์Œ์„ฑ๋ฉ”์‹œ์ง€ ์ „์†ก

react-native-audio-recorder-player

import React, { useCallback, useRef, useState } from "react";
import { PermissionsAndroid, Platform, StyleSheet } from "react-native";
import AudioRecorderPlayer, {
  AVEncodingOption,
  AudioEncoderAndroidType,
} from "react-native-audio-recorder-player";

const MicButton = () => {
  const [recording, setRecording] = useState(false);
  const audioRecorderPlayerRef = useRef(new AudioRecorderPlayer());
  const startRecord = useCallback(async () => {
    // android permission
    if (Platform.OS === "android") {
      const grants = await PermissionsAndroid.requestMultiple([
        PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
        PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
        PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
      ]);

      const granted =
        grants[PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE] ===
          PermissionsAndroid.RESULTS.GRANTED &&
        grants[PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE] ===
          PermissionsAndroid.RESULTS.GRANTED &&
        grants[PermissionsAndroid.PERMISSIONS.RECORD_AUDIO] ===
          PermissionsAndroid.RESULTS.GRANTED;

      if (!granted) {
        return;
      }
    }
    //๋…น์Œ์‹คํ–‰
    await audioRecorderPlayerRef.current.startRecorder(undefined, {
      //   android์™€ ios ์˜ค๋””์˜คํƒ€์ž… ํ†ต์ผ
      AudioEncoderAndroid: AudioEncoderAndroidType.AAC,
      AVFormatIDKeyIOS: AVEncodingOption.aac,
    });
    audioRecorderPlayerRef.current.addRecordBackListener(() => {});
    setRecording(true);
  }, []);

  const stopRecord = useCallback(async () => {
    const uri = await audioRecorderPlayerRef.current.stopRecorder();
    audioRecorderPlayerRef.current.removeRecordBackListener();
    setRecording(false);
  }, []);
};

export default MicButton;
  • ๋งˆ์ดํฌ ๊ถŒํ•œ ์š”์ฒญ
  • ์Œ์„ฑ ๋…น์Œ ๊ธฐ๋Šฅ ๊ตฌํ˜„
    • android๋Š” npm run android๋กœ ์‹คํ–‰์‹œ ๋…น์Œ์ด ์•ˆ๋˜๋ฏ€๋กœ android studio์—์„œ ์—ด์–ด์•ผํ•จ.
    • open์œผ๋กœ ํ”„๋กœ์ ํŠธ์˜ androidํด๋”๋ฅผ open โ†’ (tools)device manager โ†’ ์žฌ์ƒ
    • emulator์˜ ์ ์„ธ๊ฐœ(extended controls) ํด๋ฆญ โ†’ microphone โ†’ virtual microphone uses host audio input ํ™œ์„ฑํ™”
  • firebase storage์— ์Œ์„ฑ ์—…๋กœ๋“œํ•˜๊ธฐ
  • ์Œ์„ฑ ๋ฉ”์‹œ์ง€ ์ „์†กํ•˜๊ธฐ
  • ์Œ์„ฑ ๋ฉ”์‹œ์ง€ ์žฌ์ƒ ๋ฐ ๋ฉˆ์ถค ๊ธฐ๋Šฅ ๊ตฌํ˜„
  • ๋‚จ์€ ์žฌ์ƒ์‹œ๊ฐ„ ํ‘œ์‹œ

react-native-firebase-@messaging

  • ios

    • ์‹ค์ œ ๋””๋ฐ”์ด์Šค, apple ๊ฐœ๋ฐœ์ž ๋“ฑ๋ก์ด ํ•„์š”ํ•จ
  • android

    • android ์—๋ฎฌ๋ ˆ์ดํ„ฐ๋‚˜ ์‹ค์ œ ๋””๋ฐ”์ด
  • FCM(Firebase Cloud Messaging)

    • ๋น„์šฉ์—†์ด Server-to-Device, Device-to-Device๋กœ ์•Œ๋ฆผ์„ ์ „์†กํ•  ์ˆ˜ ์žˆ๋Š” ํ”Œ๋žซํผ
  • ์•Œ๋ฆผ ์ˆ˜์‹  ์‹œ ์•ฑ ์ƒํƒœ

    • Foreground : ์•ฑ์ด ์‹คํ–‰๋˜๊ณ  ํ˜„์žฌ ๋ณด์—ฌ์ง€๋Š” ์ƒํƒœ
    • Background : ์•ฑ์€ ์‹คํ–‰๋˜์—ˆ์œผ๋‚˜ ์ตœ์†Œํ™” ๋˜์–ด์žˆ๋Š” ์ƒํƒœ
    • Quit : ์•ฑ์ด ์™„์ „ํžˆ ์ข…๋ฃŒ๋œ ์ƒํƒœ
  • ๋ฉ”์‹œ์ง€ ํƒ€์ž…์— ๋”ฐ๋ฅธ ์•Œ๋ฆผํ‘œ์‹œ

    • Foreground : ์•Œ๋ฆผํ‘œ์‹œ ๋ถˆ๊ฐ€๋Šฅ
    • Background: Notification, Notification + Data ์•Œ๋ฆผํ‘œ์‹œ๊ฐ€๋Šฅ
    • Quit : Notification, Notification + Data ์•Œ๋ฆผํ‘œ์‹œ๊ฐ€๋Šฅ
  • ํ•ธ๋“œํฐ์—์„œ ๋ณ€๋™์‚ฌํ•ญ์ด firebase๋กœ ๋“ค์–ด๊ฐ โ†’ cloud functions์—์„œ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ „์†ก

  • https://rnfirebase.io/messaging/usage

    • yarn add @react-native-firebase/messaging
    • ๋‹ค๋ฅธ firestore ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค๊ณผ ๋ฒ„์ „ ๋˜‘๊ฐ™์ด ๋งž์ถ”์–ด์ฃผ๊ธฐ โ†’ yarn
    • ios > rm -rf Pods ๋กœ ์ง€์›Œ์ค€ ํ›„, pod install
  • ์•Œ๋ฆผ๊ถŒํ•œ ์š”์ฒญ (ios, android 13+ : 13๋ฒ„์ „์ด์ „์€ ์ด๋ฏธ๊ถŒํ•œ์ด ๋˜์–ด์žˆ์Œ)

  • Push Notification ์ „์†ก์„ ์œ„ํ•ด fcmํ† ํฐ ๋“ฑ๋กํ•˜๊ธฐ

๋ฉ”์‹œ์ง€ ๋„์ฐฉ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ

  • ๋ฐฑ์—”๋“œ

    • firebase cloud functions ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐํ™” https://firebase.google.com/docs/functions?hl=ko

    • https://firebase.google.com/docs/functions/firestore-events?hl=ko

      cloud functions์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” firebase๋ฌด๋ฃŒ(spark)์š”๊ธˆ์ œ๋กœ๋Š” ์‚ฌ์šฉ ๋ถˆ๊ฐ€๋Šฅ โ†’ ์‚ฌ์šฉํ• ์ˆ˜๋ก ์š”๊ธˆ์ด ๋ถ€๊ณผ๋˜๋Š” Blaze์š”๊ธˆ์ œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•จ

      • firestore trigger์ด์šฉ์‹œ ๋ฌธ์„œ๊ฐ€ ์—…๋ฐ์ดํŠธ,์ƒ์„ฑ,์‚ญ์ œ ๋ ๋•Œ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ ๊ตฌํ˜„๊ฐ€๋Šฅ
    • ์‚ฌ์šฉ npm install -g firebase-tools https://firebase.google.com/docs/functions/get-started?hl=ko ๋งํฌ ์ฐธ๊ณ ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ setting

      • login โ†’ functions๊ธฐ๋Šฅ์„ ํƒ โ†’ ๊ธฐ์กด ํ”„๋กœ์ ํŠธ ์„ ํƒ โ†’ ์‚ฌ์šฉ์–ธ์–ด์„ ํƒ
      • ํŒจํ‚ค์ง€ ์„ค์น˜๋‚˜ ์‹คํ–‰์‹œ ChatAppServer > functions ํด๋” ๋‚ด์—์„œ ์ž‘์—…
    • npm i eslint-config-prettier -s

      • eslint์™€ prettier๊ฐ€ ์ถฉ๋Œํ•  ๋•Œ (4์นธ๋„์šธ์ง€ 2์นธ๋„์šธ์ง€ ๋“ฑ) ํ•œ๊ฐ€์ง€๋ฅผ ์„ ํƒํ•˜๋Š” ํŒจํ‚ค์ง€
      • .eslint.rcํŒŒ์ผ์—์„œ ๋‹ค์Œ๋‚ด์šฉ์ถ”๊ฐ€
        extends: [
            "prettier",
          ],
    • npm i firebase-admin

      • function์—์„œ ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ์œ„ํ•ด ์‚ฌ์šฉ
    • ์„œ๋ฒ„์—์„œ firestore ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ ์ˆ˜์‹ ํ•˜๊ธฐ

    • ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ ์‹œ Push Notification ์ „์†ก

      • npm run deploy
        • ์„œ๋ฒ„์— ๋ฐฐํฌ๊ฐ€ ๋จ. ์•ฝ 3๋ถ„์†Œ์š”
        • firebase โ†’ ํ”„๋กœ์ ํŠธ โ†’ functions โ†’ ํ•จ์ˆ˜์˜ ์ 3๊ฐœ โ†’ ๋กœ๊ทธ๋ณด๊ธฐ ์—์„œ ์ž˜๋ณด๋‚ด์ง€๋Š”์ง€ ํ™•์ธ๊ฐ€๋Šฅ
  • ์•ฑ

    • ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์‹œ Notification ๋ณด์—ฌ์ฃผ๊ธฐ
    • Notification ํ„ฐ์น˜ํ•˜๋ฉด ์ฑ„ํŒ… ์Šคํฌ๋ฆฐ์œผ๋กœ ์ด๋™
      • background์— ์žˆ๋Š”๊ฒฝ์šฐ์™€ quit์ƒํƒœ์— ์žˆ๋Š”๊ฒฝ์šฐ ๋‹ค๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•ด์ฃผ์–ด์•ผํ•จ.
    • foreground์—์„œ ์•Œ๋ฆผ๋„์šฐ๊ธฐ
      • messaging์˜ onMessage์‚ฌ์šฉ
      • react-native-toast-message ํŒจํ‚ค์ง€ ์‚ฌ์šฉํ•˜์—ฌ ์•ฑ ๋‚ด์—์„œ ์•Œ๋ฆผ๋„์šฐ๊ธฐ
    • ios APNs(Apple Push Notifications service)์„ค์ •
      • ios Messaging setup
      • https://rnfirebase.io/messaging/usage/ios-setup
        • capabilities ์ถ”๊ฐ€
        • APNs ํ‚ค ๋“ฑ๋ก
          • developer.apple.com
          • ํ”„๋กœ๊ทธ๋žจ ๋ฆฌ์†Œ์Šค โ†’ ํ‚ค ๋“ฑ๋ก โ†’ ๋‹ค์šด๋กœ๋“œ(์žฌ๋‹ค์šด๋กœ๋“œ๋ถˆ๊ฐ€๋Šฅ)
          • firebase ํ”„๋กœ์ ํŠธ ์„ค์ • โ†’ ํด๋ผ์šฐ๋“œ ๋ฉ”์‹œ์ง• โ†’ APN์ธ์ฆ ํ‚ค ๋“ฑ๋ก (ํ‚คid, ํŒ€id-์ด๋ฆ„์˜† ์ฝ”๋“œ), ํ‚ค ํŒŒ์ผ ๋“ฑ๋ก

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published