https://github.com/ssi02014/react-native-chat
๐ฌ React Native๋ก ๋ง๋ Chatting App
https://github.com/ssi02014/react-native-chat
firebase react react-native
Last synced: 3 months ago
JSON representation
๐ฌ React Native๋ก ๋ง๋ Chatting App
- Host: GitHub
- URL: https://github.com/ssi02014/react-native-chat
- Owner: ssi02014
- Created: 2021-04-08T14:28:43.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2021-04-19T07:23:02.000Z (over 4 years ago)
- Last Synced: 2025-05-29T16:17:25.328Z (4 months ago)
- Topics: firebase, react, react-native
- Language: JavaScript
- Homepage:
- Size: 1.38 MB
- Stars: 1
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ๐ป React-Native-Chat
### React-Native-Chat ์ ์ฅ์
## ๐ฅ App View
## ๐ ๊ธฐ์ ์คํ ๋ฐ ์ฃผ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
1. React-Native
2. Styled-Components: Styling
3. Google Material Design: Icon
4. React-Navigation(Stack, Tab)
5. Context API: ์ํ ๊ด๋ฆฌ
6. Firebase: ์๋น์ค์ ํ์ํ ์๋ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ง์ ๊ตฌ์ถํ์ง ์๊ณ ๊ฐ๋ฐ์ด ๊ฐ๋ฅํ ๊ฐ๋ฐ ํ๋ซํผ
6. expo-image-picker: ๊ธฐ๊ธฐ์ ์ฌ์ง์ด๋ ์์์ ๊ฐ์ ธ์ฌ ์ ์๋๋ก ์์คํ UI์ ์ ๊ทผํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณต
7. moment: ์๊ฐ์ ๋ค์ํ ํํ๋ก ๋ณ๊ฒฝํ๋ ๋ฑ ์๊ฐ๊ณผ ๊ด๋ จ๋ ๋ง์ ๊ธฐ๋ฅ์ ์ ๊ณต
8. react-native-keyboard-aware-scroll-view: ํค๋ณด๋๊ฐ ํ๋ฉด์ ๊ฐ๋ฆฌ๋ฉฐ์ ์๊ธฐ๋ ๋ถํธํ ์ ์ ํด๊ฒฐํ ์ ์๋ ๊ธฐ๋ฅ ์ ๊ณต
9. react-native-gifted-chat: ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ์ฑํ ํ๋ฉด์ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋๋ก ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
## ๐ App File Structure
- components: ์ปดํฌ๋ํธ ํ์ผ ๊ด๋ฆฌ
- contexts: Context API ํ์ผ ๊ด๋ฆฌ
- navigations: ๋ด๋น๊ฒ์ด์ ํ์ผ ๊ด๋ฆฌ
- screens: ํ๋ฉด ํ์ผ ๊ด๋ฆฌ
- utils: ํ๋ก์ ํธ์์ ์ด์ฉํ ๊ธฐํ ๊ธฐ๋ฅ ๊ด๋ฆฌ
## ๐จ๐ปโ๐ป Firebase
๐ **https://console.firebase.google.com/**
- Firebase๋ ์ธ์ฆ(Authentication), ๋ฐ์ดํฐ๋ฒ ์ด์ค(Database) ๋ฑ์ ๋ค์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๊ฐ๋ฐ ํ๋ซํผ์ด๋ค.
- Firebase๊ฐ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ์ด์ฉํ๋ฉด ๋๋ถ๋ถ์ ์๋น์ค์์ ํ์ํ ์๋ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ง์ ๊ตฌ์ถํ์ง ์์๋ ๊ฐ๋ฐ์ด ๊ฐ๋ฅํ๋ค.```javascript
//Firebase Setting
1. ํ๋ก์ ํธ ์ค์ > ์ผ๋ฐ > ๋ด ์ฑ์์ '์น'์ ์ ํํ๊ณ ์ฑ์ ์ถ๊ฐ
2. ํ๋ก์ ํธ ์ค์ > ์ผ๋ฐ > ๋ด ์ฑ์์ 'Firebase SDK snippet'์์ Firebase ์ค์ ๊ฐ์ ํ์ธํ๋ค.3. ํ๋ก์ ํธ ๋ฃจํธ ๋๋ ํฐ๋ฆฌ์ firebase.json ํ์ผ์ ์์ฑ ํ 2๋ฒ์์ ํ์ธํ ์ฝ๋๋ฅผ ๋ฃ๋๋ค.
- firebase.json์ ์ค์ํ ํ์ผ์ด๊ธฐ ๋๋ฌธ์ .gitignore์ ์ถ๊ฐํ๋ค.//firebase.json
{
"apiKey": "...",
"authDomain": "...",
"projectId": "...",
"storageBucket": "...",
"messagingSenderId": "...",
"appId": "...",
"measurementId": "..."
}4. ์ธ์ฆ, ๋ฐ์ดํฐ๋ฒ ์ด์ค, ์คํ ๋ฆฌ์ง ์ค์ ํ๋ค.
5. expo install firebase ๋ฅผ ํตํด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ๋ค.
6. firebase.js ํ์ผ์ ์์ฑํ๋ค.
//src/utils/firebase.js
import * as firebase from "firebase";
import config from "../../firebase.json";const app = firebase.initializeApp(config);
```
## ๐จ๐ปโ๐ป ์ฑ ์์ด์ฝ๊ณผ ๋ก๋ฉ ํ๋ฉด
- ํ๋ก์ ํธ์์ ์ฌ์ฉํ ์ด๋ฏธ์ง์ ํฐํธ๋ฅผ ๋ฏธ๋ฆฌ ๋ถ๋ฌ์์ ์ฌ์ฉํ ์ ์๋๋ก cacheImages, cacheFonts ํจ์๋ฅผ ์์ฑํ๊ณ ์ด๋ฅผ _loadAssets ํจ์๋ฅผ ๊ตฌ์ฑํ๋ค.
- ์ด๋ฏธ์ง๋ ํฐํธ๋ฅผ ๋ฏธ๋ฆฌ ๋ถ๋ฌ์ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์ฉํ๋ ํ๊ฒฝ์ ๋ฐ๋ผ ์ด๋ฏธ์ง๋ ํฐํธ๊ฐ ๋๋ฆฌ๊ฒ ์ ์ฉ๋๋ ๋ฌธ์ ๋ฅผ ๊ฐ์ ํ ์ ์๋ค.
- ์ ํ๋ฆฌ์ผ์ ์ ๋ฏธ๋ฆฌ ๋ถ๋ฌ์์ผ ํ๋ ํญ๋ชฉ๋ค์ ๋ชจ๋ ๋ถ๋ฌ์ค๊ณ ํ๋ฉด์ด ๋ ๋๋ง ๋๋๋ก AppLoading ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ค.```javascript
const cacheImages = (images) => {
return images.map((image) => {
if (typeof image === "string") {
return Image.prefetch(image);
} else {
return Asset.fromModule(image).downloadAsync();
}
});
};const cacheFonts = (fonts) => {
return fonts.map((font) => Font.loadAsync(font));
};const App = () => {
(...)const _loadAssets = async () => {
const imageAssets = cacheImages([
require("../assets/splash.png"),
]);const fontAssets = cacheFonts([]);
await Promise.all([...imageAssets, ...fontAssets]);
};return isReady ? (
(...)
) : (
setIsReady(true)}
onError={console.error}
/>
);
};
```
## ๐จ๐ปโ๐ป ๋ก๊ณ ์ ์ฉํ๊ธฐ
- ์ด๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ก๊ณ ๋ฅผ Firebase ์คํ ๋ฆฌ์ง์ ์ ๋ก๋ํ๊ณ ๋ก๊ทธ์ธ ํ๋ฉด์์ ์ฌ์ฉํ๋๋ก ๋ง๋ค์์ต๋๋ค.
- ์คํ ๋ฆฌ์ง์ ํ์ผ์ ์ ๋ก๋ํ๊ณ ํ์ผ ์ ๋ณด์์ ์ด๋ฆ์ ํด๋ฆญํ๋ฉด ํด๋น ํ์ผ์ url์ ์ป์ ์ ์์ต๋๋ค.```javascript
//1. src/utils/images.js ์์ฑconst prefix =
"https://firebasestorage.googleapis.com/v0/b/react-native-chat-65246.appspot.com/o";export const images = {
logo: `${prefix}/logo.png?alt=media`,
};//2. src/App.js (_loadAssets ๋ฉ์๋ ์์ )
const _loadAssets = async () => {
const imageAssets = cacheImages([
require("../assets/splash.png"),
...Object.values(images),
]);const fontAssets = cacheFonts([]);
await Promise.all([...imageAssets, ...fontAssets]);
};//3. Firebase ์คํ ๋ฆฌ์ง Rules ์์
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /logo.png {
allow read;
}
}
}
```
## ๐จ๐ปโ๐ป useRef, forwardRef
- useRef๋ฅผ ์ด์ฉํ์ฌ passwordRef๋ฅผ ๋ง๋ค๊ณ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ๋ Input ์ปดํฌ๋ํธ์ ref๋ก ์ง์ ํ์ต๋๋ค.
- ์ด๋ฉ์ผ์ ์ ๋ ฅํ๋ Input ์ปดํฌ๋ํธ์ onSubmitEditing ํจ์๋ฅผ passwordRef ๋ฅผ ์ด์ฉํด์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ๋ Input ์ปดํฌ๋ํธ๋ก ํฌ์ปค์ค๊ฐ ์ด๋๋๋๋ก ์์ฑํฉ๋๋ค.
- ref๋ key์ฒ๋ผ ๋ฆฌ์กํธ์์ ์์ ์ปดํฌ๋ํธ์ props๋ก ์ ๋ฌ๋์ง ์์ต๋๋ค. ์ด๋, forwardRef ํจ์๋ฅผ ์ด์ฉํ๋ฉด ref๋ฅผ ์ ๋ฌ๋ฐ์ ์ ์์ต๋๋ค.```javascript
const Input = forwardRef(
(
{
(...)
},
ref
) => {
return (
(...)
setIsFocused(true)}
onBlur={() => {
setIsFocused(false);
onBlur();
}} //input์ ํฌ์ปค์ค๊ฐ ํ๋ฆด๋ ํธ์ถ๋๋ ์ฝ๋ฐฑ
placeholder={placeholder}
secureTextEntry={isPassword} //๋ฌธ์๋ฅผ ๊ฐ์ถ๋ ๊ธฐ๋ฅ
returnKeyType={returnKeyType} //๋ฆฌํด ํค๋ฅผ ๋ ์ด๋ธ๋ก ์ค์
maxLength={maxLength} //์ ๋ ฅ ํ ์์๋ ์ต๋ ๋ฌธ์ ์๋ฅผ ์ ํ
autoCapitalize="none" //์๋ ๋๋ฌธ์ ๋ณํ
autoCorrect={false} //์๋ ์์
textContentType="none" //iOS
underlineColorAndroid="transparent" //Android TextInput ๋ฐ์ค ์ ์์
/>
);
}
);
```
## ๐จ๐ปโ๐ป ํค๋ณด๋ ๊ฐ์ถ๊ธฐ
- TextInput์ ์ ๋ ฅ ๋์ค ๋ค๋ฅธ ๊ณณ์ ํฐ์นํ๋ฉด ํค๋ณด๋๊ฐ ์ฌ๋ผ์ง๋๋ฐ, ์ด๋ ์ฌ์ฉ์ ํธ์๋ฅผ ์ํ ์ผ๋ฐ์ ์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋์ฅ ๋ฐฉ์์ ๋๋ค.
- ๋ฆฌ์กํธ ๋ค์ดํฐ๋ธ์์ **TouchableWithoutFeedback** ์ปดํฌ๋ํธ์ **Keyboard API**๋ฅผ ์ฌ์ฉํด์ ์ ๋์ฅ ๋ฐฉ์์ ๊ตฌํํ ์ ์์ต๋๋ค.
- ์์ ๋ ์ปดํฌ๋ํธ๋ ๋์ , ์์น์ ๋ฐ๋ผ ํค๋ณด๋๊ฐ Input ์ปดํฌ๋ํธ๋ฅผ ๊ฐ๋ฆฌ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ง๋ ๋ชปํฉ๋๋ค.
- **react-native-keyboard-aware-scroll-view** ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ๋ฉด ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค. ๋ฟ๋ง ์๋๋ผ focus๊ฐ ์๋ TextInput ์ปดํฌ๋ํธ์ ์์น๋ก ์๋ ์คํฌ๋กค๋๋ ๊ธฐ๋ฅ ๋ฑ Input ์ปดํฌ๋ํธ์ ํ์ํ ๊ธฐ๋ฅ๋ค์ ์ ๊ณตํฉ๋๋ค.```javascript
//import
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
(...)
```
## ๐จ๐ปโ๐ป ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ
```javascript
//์ฌ๋ฐ๋ฅธ ์ด๋ฉ์ผ ํ์ ๊ฒ์ฌ
export const validateEmail = (email) => {
const regex = /^[0-9?A-z0-9?]+(\.)?[0-9?A-z0-9?]+@[0-9?A-z]+\.[A-z]{2}.?[A-z]{0,3}$/;return regex.test(email);
};//๊ณต๋ฐฑ ์ ๊ฑฐ
export const removeWhitespace = (text) => {
const regex = /\s/g;
return text.replace(regex, "");
};
```## ๐จ๐ปโ๐ป Button ์ปดํฌ๋ํธ
- **TouchableOpacity**๋ ํฐ์น ์ด๋ฒคํธ(onPress)๋ฅผ ์ฌ์ฉํ ์ ์๋ View```javascript
const Container = styled.TouchableOpacity`
(...)
`;
```
## ๐จ๐ปโ๐ป ๋ ธ์น ๋์์ธ
- react-native-safe-area-context ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ๊ณตํ๋ useSafeAreaInsets Hook ํจ์๋ฅผ ์ด์ฉํ๋ฉด ๋ ธ์น๋์์ธ์ ํด๊ฒฐํ ์ ์๋ค.
- useSafeAreaInsets์ ์ฅ์ ์ iOS๋ฟ๋ง์๋๋ผ ์๋๋ก์ด๋์์๋ ์ ์ฉ ๊ฐ๋ฅํ padding ๊ฐ์ ์ ๋ฌํ๋ค.```javascript
//import
import { useSafeAreaInsets } from "react-native-safe-area-context";//padding top๊ณผ bottom์ ๊ฐ์ useSafeAreaInsets ํจ์๊ฐ ์๋ ค์ฃผ๋ ๊ฐ๋งํผ ์ค์ ํ๋ค.
const Container = styled.View`
(...)
padding: 0 20px;
padding-top: ${({ insets: { top } }) => top}px;
padding-bottom: ${({ insets: { bottom } }) => bottom}px;
`;
```
## ๐จ๐ปโ๐ป ๊ถํ ์์ฒญ, ์ฌ์ง์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
### ๐๊ถํ ์์ฒญ(iOS)
- expo-image-picker ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด์ ๊ธฐ๊ธฐ์ ์ฌ์ง์ฒฉ์ ์ ๊ทผํด์ ์ ํ๋ ์ฌ์ง์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.
- iOS์์๋ ์ฌ์ง์ฒฉ์ ์ ๊ทผํ๊ธฐ ์ํด ์ฌ์ฉ์์๊ฒ ๊ถํ์ ์์ฒญํ๋ ๊ณผ์ ์ด ํ์ํ๋ฏ๋ก, ๊ถํ์ ์์ฒญํ๋ ๋ถ๋ถ์ ์ถ๊ฐํด์ผ ํ๋ค. ์๋๋ก๋์์๋ ํน๋ณํ ์ค์ ์์ด ์ฌ์ง์ ์ ๊ทผํ ์ ์๋ค.```javascript
//install
expo install expo-image-picker//import
import * as ImagePicker from "expo-image-picker";
import * as Permissions from "expo-permissions";//iOS ๊ถํ ์์ฒญ
useEffect(() => {
async () => {
try {
if (Platform.OS === "ios") {
const { status } = await Permissions.askAsync(
Permissions.CAMERA_ROLL
);
if (status !== "granted") {
Alert.alert(
"Photo Permission",
"Please turn on the camera roll permissions"
);
}
}
} catch (e) {
Alert.alert("Photo Permission Error", e.message);
}
};
}, []);
```### ๐์ฌ์ง ์ ๋ ฅ๋ฐ๊ธฐ
- ์ฌ์ง ๋ณ๊ฒฝ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํธ์ถ๋๋ ํจ์์์ ๊ธฐ๊ธฐ์ ์ฌ์ง์ ์ ๊ทผํ๊ธฐ ์ํด ํธ์ถ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํจ์๋ ๋ค์๊ณผ ๊ฐ์ ๊ฐ๋ค์ ํฌํจํ ๊ฐ์ฒด๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋ฐ๋๋ค.
1. mediaTypes: ์กฐํํ๋ ์๋ฃ์ ํ์
2. allowsEditing: ์ด๋ฏธ์ง ์ ํ ํ ํธ์ง ๋จ๊ณ ์งํ ์ฌ๋ถ
3. aspect: ์๋๋ก์ด๋ ์ ์ฉ ์ต์ ์ผ๋ก ์ด๋ฏธ์ง ํธ์ง์ ์ฌ๊ฐํ์ ๋น์จ([x, y])
4. quality: 0 ~ 1 ์ฌ์ด์ ๊ฐ์ ๋ฐ์ผ๋ฉฐ ์์ถ ํ์ง์ ์๋ฏธ (1: ์ต๋ ํ์ง)```javascript
const _handelEditButton = async () => {
try {
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
if (!result.cancelled) {
onChangeImage(result.uri);
}
} catch (e) {
Alert.alert("Photo Error", e.message);
}
};
```- ๊ธฐ๊ธฐ์ ์ฌ์ง์ ์ ๊ทผํ๋ ํจ์๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋๋ฐ, cancelled ๊ฐ์ ํตํด ์ ํ ์ฌ๋ถ๋ฅผ ํ์ธํ ์ ์๋ค. ๋ง์ฝ ์ฌ์ฉ์๊ฐ ์ฌ์ง์ ์ ํํ๋ค๋ฉด ๋ฐํ๋ ๊ฒฐ๊ณผ์ uri๋ฅผ ํตํด ์ ํ๋ ์ฌ์ง์ ์ฃผ์๋ฅผ ์ ์ ์๋ค.
```json
//์๋จ์ result์ ๋ฐํ ๊ฐ
{
"cancelled": true,
}{
"cancelled": false,
"height": 000,
"type": "image",
"uri": "file:../...jpg",
"width": 000,
}
```
## ๐จ๐ปโ๐ป ๋ก๊ทธ์ธ, ํ์๊ฐ์ ๊ธฐ๋ฅ ๊ตฌํ
### ๐ ๋ก๊ทธ์ธ
- ํ์ด์ด๋ฒ ์ด์ค๋ฅผ ์ด์ฉํ ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ด์ฉํด ์ธ์ฆ๋ฐ๋ ํจ์๋ **signInWithEmailAndPassword** ์ ๋๋ค.```javascript
//utils/firebase.js
export const login = async ({ email, password }) => {
const { user } = await Auth.signInWithEmailAndPassword(email, password);
return user;
};
```
```javascript
//screens/Login.js
import { login } from "../utils/firebase";const _handleLoginButtonPress = async () => {
try {
const user = await login({ email, password });
Alert.alert("Login Success", user.email);
} catch (e) {
Alert.alert("Login Error", e.message);
}
};
```### ๐ ํ์๊ฐ์
- ํ์ด์ด๋ฒ ์ด์ค๋ฅผ ์ด์ฉํ ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ด์ฉํด ์ฌ์ฉ์๋ฅผ ์์ฑํ๋ ํจ์๋ **createUserWithEmailAndPassword** ์ ๋๋ค.```javascript
//utils/firebase.jsexport const signup = async ({ email, password, name, photoUrl }) => {
const { user } = await Auth.createUserWithEmailAndPassword(email, password);
return user;
};
``````javascript
//screens/Signup.js
import { signup } from "../utils/firebase";const _handleSignupButtonPress = async () => {
try {
const user = await signup({ email, password, name, photoUrl });
console.log(user);
Alert.alert("Signup Success", user.email);
} catch (e) {
Alert.alert("Signup Error", e.message);
}
};
```
## ๐จ๐ปโ๐ป Spinner(with ContextAPI)
### ๐ Spinner Component
- Spinner ์ปดํฌ๋ํธ๋ ๋ก๊ทธ์ธ ํน์ ํ์๊ฐ์ ์ด ์งํ๋๋ ๋์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฑฐ๋ ๋ฒํผ์ ์ถ๊ฐ๋ก ํด๋ฆญํ๋ ์ผ์ด ๋ฐ์ํ์ง ์๋๋ก ๋ฐฉ์งํ๋ ๊ธฐ๋ฅ์ ํ๋ค.
- Spinner ์ปดํฌ๋ํธ๋ ๋ฆฌ์กํธ ๋ค์ดํฐ๋ธ์์ ์ ๊ณตํ๋ **AcitivityIndicator** ์ปดํฌ๋ํธ๋ฅผ ์ด์ฉํด์ ์ฝ๊ฒ ๋ง๋ค ์ ์๋ค.
- Spinner ์ปดํฌ๋ํธ๋ฅผ AuthStack ๋ด๋น๊ฒ์ด์ ์ ํ์ ์ปดํฌ๋ํธ๋ก ์ฌ์ฉํ๋ฉด ๋ด๋น๊ฒ์ด์ ์ ํฌํจํ ํ๋ฉด ์ ์ฒด๋ฅผ ์ฐจ์งํ ์ ์์ต๋๋ค. ๋ด๋น๊ฒ์ด์ ์ ํฌํจํ ํ๋ฉด ์ ์ฒด๋ฅผ ๊ฐ์ธ๊ธฐ ์ํด์๋ navigations ํด๋์ index.js์์ AuthStack ๋ด๋น๊ฒ์ด์ ๊ณผ ๊ฐ์ ์์น์ Spinner ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํด์ผ ๋ฉ๋๋ค.```javascript
import React, { useContext } from 'react';
import { ActivityIndicator } from 'react-native';
import styled, { ThemeContext } from 'styled-components/native';const Container = styled.View`
(...)
`;const Spinner = () => {
const theme = useContext(ThemeContext);
return (
)
};export default Spinner;
```- Spinner ์ปดํฌ๋ํธ๋ฅผ AuthStack ๋ด๋น๊ฒ์ด์ ์ ํ์ ์ปดํฌ๋ํธ๋ก ์ฌ์ฉํ๋ฉด ๋ด๋น๊ฒ์ด์ ์ ํฌํจํ ํ๋ฉด ์ ์ฒด๋ฅผ ์ฐจ์งํ ์ ์์ต๋๋ค. ๋ด๋น๊ฒ์ด์ ์ ํฌํจํ ํ๋ฉด ์ ์ฒด๋ฅผ ๊ฐ์ธ๊ธฐ ์ํด์๋ navigations ํด๋์ index.js์์ AuthStack ๋ด๋น๊ฒ์ด์ ๊ณผ ๊ฐ์ ์์น์ Spinner ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํด์ผ ๋ฉ๋๋ค.
```javascript
(...)
const Navigation = () => {
const { inProgress } = useContext(ProgressContext);return (
{inProgress && }
);
};
(...)
```### ๐ Context API
- createContext ํจ์๋ฅผ ์ด์ฉํด Context๋ฅผ ์์ฑํ๊ณ , Provider ์ปดํฌ๋ํธ์ value์ Spinner ์ปดํฌ๋ํธ์ ๋ ๋๋ง ์ํ๋ฅผ ๊ด๋ฆฌํ inPrgress ์ํ ๋ณ์์ ์ํ๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ ํจ์๋ฅผ ์ ๋ฌํฉ๋๋ค.```javascript
//contexts/Progress.jsimport React, { useState, createContext } from 'react';
//Context ์์ฑ
const ProgressContext = createContext({
inProgress: false,
spinner: () => {},
});//ProgressProvider
const ProgressProvider = ({ children }) => {
const [inProgress, setInProgress] = useState(false);const spinner = {
start: () => setInProgress(true),
stop: () => setInProgress(false),
};
const value = { inProgress, spinner };return (
{children}
);
};export { ProgressContext, ProgressProvider };
``````javascript
//contexts/index.js
import {ProgressContext, ProgressProvider } from './Progress';
export { ProgressContext, ProgressProvider };
``````javascript
//src/App.js
```
## ๐จ๐ปโ๐ป Stack ๋ด๋น๊ฒ์ด์ ์ Tab ๋ด๋น๊ฒ์ด์ ์ ํค๋ ๋ณ๊ฒฝ
- MainStack ๋ด๋น๊ฒ์ด์ ์์ MainTab ๋ด๋น๊ฒ์ด์ ์ด ํ๋ฉด์ผ๋ก ์ฌ์ฉ๋๋ Screen ์ปดํฌ๋ํธ์ name์ "Main"์ผ๋ก ์ค์ ๋์ด ์๋ค. ํค๋์ ํ์ดํ๊ณผ ๊ด๋ จํด ํน๋ณํ ์ค์ ํ์ง ์์ผ๋ฉด Screen ์ปดํฌ๋ํธ์ name์ ์ค์ ๋ ๊ฐ์ด ํค๋์ ํ์ดํ๋ก ๋๊ธฐ ๋๋ฌธ์, ํ๋กํ ํ๋ฉด๊ณผ ์ฑ๋ ๋ชฉ๋ก ๋ชจ๋ 'Main'์ผ๋ก ํ์ดํ์ด ๋ํ๋๋ค.
```javascript
(...)
```- MainTab ๋ด๋น๊ฒ์ด์ ์ MainStack ๋ด๋น๊ฒ์ด์ ์ ํ๋ฉด์ผ๋ก ์ฌ์ฉ๋์๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ํ๋ฉด๋ค๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก props๋ฅผ ํตํด navigation๊ณผ route๋ฅผ ์ ๋ฌ ๋ฐ๋๋ค.
- route์ ํฌํจ๋ state์ ๊ฐ์ ๋ค์๊ณผ ๊ฐ๋ค
1. index: ํ์ฌ ๋ ๋๋ง ๋๋ ํ๋ฉด์ ์ธ๋ฑ์ค
2. routeNames: ํ๋ฉด์ผ๋ก ์ฌ์ฉ๋๋ Navigator ์ปดํฌ๋ํธ์์ Screen ์ปดํฌ๋ํธ๋ค์ name ์์ฑ์ ๋ฐฐ์ด๋ก ๊ฐ๋๋ค.
3. type: ํ์ฌ ํ๋ฉด์ผ๋ก ์ฌ์ฉ๋๋ Navigator ์ปดํฌ๋ํธ์ ํ์ ์ด๋ฉฐ, MainTab ๋ด๋น๊ฒ์ด์ ์ ํญ ๋ด๋น๊ฒ์ด์ ์ด๊ธฐ ๋๋ฌธ์ 'tab' ๊ฐ์ ๊ฐ๋๋ค.```json
//route์ state
{
"index": 0,
"routeNames": [
"Channel List",
"Profile",
],
"type": "tab",
...
}
``````javascript
//MainTab
useEffect(() => {
const titles = route.state?.routeNames || ['Channels'];
const index = route.state?.index || 0;
navigation.setOptions({ headerTitle: titles[index ]});
}, [route]);
```
- ํ์ง๋ง ์์ ๋ฐฉ์๋๋ก ํ๋ฉด route์ state์ ์ง์ ์ ๊ทผํด์ ๋ฐ์ํ๋ ๊ฒฝ๊ณ ๋ฉ์์ง๊ฐ ๋ฌ๋ค. ์ด๊ฑธ ํด๊ฒฐํ๋ ค๋ฉด **getFocusedRouteNameFromRoute** ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
- ๐ ๊ด๋ จ ์ด์: https://github.com/Alchemist85K/my-first-react-native/discussions/26```javascript
useEffect(() => {
const screenName = getFocusedRouteNameFromRoute(route) || 'Channels';navigation.setOptions({
headerTitle: screenName,
});
}, [route]);
```
## ๐จ๐ปโ๐ป Setting a timer for a long period of time, ... ์ค๋ฅ
- ๐ ๊ด๋ จ ์ด์: https://github.com/Alchemist85K/my-first-react-native/discussions/28
- /node_modules/react-native/Libraries/Core/Timers/JSTimers.js
- MAX_TIMER_DURATION_MS ๋ผ๋ ๋ณ์ ๊ฐ์ 60 * 1000 ์์ 10000 * 1000์ผ๋ก ๋ณ๊ฒฝ
## ๐จ๐ปโ๐ป ๋ฐ์ดํฐ๋ฒ ์ด์ค
- ํ์ด์ด๋ฒ ์ด์ค์์ ์ ๊ณตํ๋ ํ์ด์ด์คํ ์ด๋ NoSQL ๋ฌธ์ ์ค์ฌ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ด๋ค.
- SQL ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฌ๋ฆฌ ํ ์ด๋ธ์ด๋ ํ์ด ์๊ณ ์ปฌ๋ ์ , ๋ฌธ์, ํ๋๋ก ๊ตฌ์ฑ๋๋ค.
1. ์ปฌ๋ ์ ์ ๋ฌธ์์ ์ปจํ ์ด๋ ์ญํ ์ ํ๋ฉฐ, ๋ชจ๋ ๋ฌธ์๋ ํญ์ ์ปฌ๋ ์ ์ ์ ์ฅ๋๋ค.
2. ๋ฌธ์๋ ํ์ด์ด์คํ ์ด์ ์ ์ฅ ๋จ์๋ก ๊ฐ์ด ์๋ ํ๋๋ฅผ ๊ฐ๋๋ค. ๋ฌธ์์ ๊ฐ์ฅ ํฐ ํน์ง์ ์ปฌ๋ ์ ์ ํ๋๋ก ๊ฐ์ง ์ ์๋ค.
- ํ์ด์ด์คํ ์ด๋ ์ผ๋ฐ์ ์ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฌ๋ฆฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ด์ฉ์ด ์์ ๋๋ฉด ์ค์๊ฐ์ผ๋ก ๋ณ๊ฒฝ๋ ๋ด์ฉ์ ์ ์ ์๋ค.
- ์ปฌ๋ ์ ๊ณผ ๋ฌธ์๋ ํญ์ ์ ์ผํ ID๋ฅผ ๊ฐ๊ณ ์์ด์ผ ํ๋ค๋ ๊ท์น์ด ์๋ค.
```
//ํ์ด์ด์คํ ์ด ๋ณด์ ๊ท์น ์์
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /channels/{channel} {
allow read, write: if request.auth.uid != null;
}
}
}
```
## ๐จ๐ปโ๐ป FlatList
- ์ง๊ธ๊น์ง ๋ง์ ์์ ๋ฐ์ดํฐ๋ฅผ ๋ ๋๋งํ ๋ ScrollView ์ปดํฌ๋ํธ๋ฅผ ์ด์ฉํด ํ๋ฉด์ด ๋์ด๊ฐ๋๋ก ์คํฌ๋กค์ด ์์ฑ๋์ด ํ์ธํ ์ ์๋๋ก ๋ง๋ค์์ต๋๋ค.
- FlatList์ปดํฌ๋ํธ๋ ScrollView ์ปดํฌ๋ํธ์ ๊ฐ์ ์ญํ ์ ํ๋๋ฐ, ScrollView ์ปดํฌ๋ํธ๋ ๋ ๋๋งํด์ผ ํ๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ํ๋ฒ์ ๋ ๋๋งํฉ๋๋ค. ์ฆ, ๋ฐ์ดํฐ์ ์์ ์๊ณ ์์ ๋ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๊ณ , ๋ฐ์ดํฐ๊ฐ ๋งค์ฐ ๋ง์ผ๋ฉด ๋ ๋๋ง ์๋๊ฐ ๋๋ ค์ง๊ณ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์ฆ๊ฐํ๋ ๋ฑ ์ฑ๋ฅ์ด ์ ํ๋ฉ๋๋ค.
- ๊ทธ์๋ฐํด, FlatList๋ ํ๋ฉด์ ์ ์ ํ ์์ ๋ฐ์ดํฐ๋ง ๋ ๋๋งํ๊ณ ์คํฌ๋กค์ ์ด๋์ ๋ง์ถฐ ํ์ํ ๋ถ๋ถ์ ์ถ๊ฐ์ ์ผ๋ก ๋ ๋๋งํ๋ ํน์ง์ด ์์ต๋๋ค.
- FlatList์๋ ๊ธฐ๋ณธ์ ์ผ๋ก 3๊ฐ์ง ์์ฑ์ด ์์ต๋๋ค.
1. data: ์ฒ์ ๋ ๋๋งํ ํญ๋ชฉ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐฐ์ด๋ก ์ ๋ฌํ๋ค.
2. renderItem: ์ ๋ฌ๋ ๋ฐฐ์ด์ ํญ๋ชฉ์ ์ด์ฉํด ๋ ๋๋งํ๋ ํจ์๋ฅผ ์์ฑํด์ผ ํ๋ค.
3. keyExtractor: key๋ฅผ ์ถ๊ฐํ๊ธฐ ์ํด ๊ณ ์ ํ ๊ฐ์ ๋ฐํํ๋ ํจ์๋ฅผ ์ ๋ฌํด์ผ ํ๋ค.
```js
item['id'].toString()}
data={channels}
renderItem={({ item }) => {
return (
)
}}
windowSize={3}
/>
```
### ๐ windowSize
- FlatList์์ ๋ ๋๋ง ๋๋ ๋ฐ์ดํฐ์ ์์ ์กฐ์ ํ๊ณ ์ถ๋ค๋ฉด windowSize ์์ฑ์ ์ถ๊ฐํด์ ๊ฐ์ ์ํ๋ ๊ฐ์ผ๋ก ์ค์ ํ๋ฉด ๋๋ค.
- windowSize์ ๊ฐ์ ์์ ๊ฐ์ผ๋ก ๋ณ๊ฒฝํ๋ฉด ๋ ๋๋ง๋๋ ๋ฐ์ดํฐ๊ฐ ์ค์ด๋ค์ด ๋ฉ๋ชจ๋ฆฌ์ ์๋น๋ฅผ ์ค์ด๊ณ ์ฑ๋ฅ์ ํฅ์ ์ํฌ ์ ์์ง๋ง, ๋น ๋ฅด๊ฒ ์คํฌ๋กคํ๋ ์ํฉ์์ ๋ฏธ๋ฆฌ ๋ ๋๋ง๋์ง ์์ ๋ถ๋ถ์ ์๊ฐ์ ์ผ๋ก ๋น ๋ด์ฉ์ด ๋ํ๋ ์ ์๋ค๋ ๋จ์ ์ด ์๋ค.
```js
```
### ๐ React.Memo
- React.Memo๋ useMemo Hook ํจ์์ ๋น์ทํ์ง๋ง, ๋ถํ์ํ ํจ์์ ์ฌ์ฐ์ฐ์ ๋ฐฉ์งํ๋ useMemo์ ๋ฌ๋ฆฌ ์ปดํฌ๋ํธ์ ๋ฆฌ๋ ๋๋ง์ ๋ฐฉ์งํ๋ค๋ ์ฐจ์ด๊ฐ ์๋ค.
- React.Memo๋ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ๋ ๊ฒ์ผ๋ก ๊ฐ๋จํ ์ ์ฉํ ์ ์๋ค.
- React.Memo๋ฅผ ์ฌ์ฉํ๋ฉด Item ์ปดํฌ๋ํธ๋ props๊ฐ ๋ณ๊ฒฝ๋ ๋๊น์ง ๋ฆฌ๋ ๋๋ง๋์ง ์๋๋ค.
```js
const Item = React.memo(({ item: { id, title, description, createdAt }, onPress }) => {
const theme = useContext(ThemeContext);
console.log(`Item ${id}`);return (
onPress({ id, title })}>
{title}
{description}
{createdAt}
);
```
## ๐จ๐ปโ๐ป ์ฑ๋ ๋ฐ์ดํฐ ์์
- firebase์ Cloud Firestore์์ ์ค์๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๊ธฐ ์ํด์๋ onSnapshop ๋ฉ์๋๋ฅผ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์์ ํ ์ ์์ต๋๋ค.
- **onSnapshop** ๋ฉ์๋๋ ์์ ๋๊ธฐ ์ํ๋ก ์๋ค๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฌธ์๊ฐ ์ถ๊ฐ๋๊ฑฐ๋ ์์ ๋ ๋๋ง๋ค ์ง์ ๋ ํจ์๊ฐ ํธ์ถ๋ฉ๋๋ค.
- ์ด๋, ์ค๋ฆ์ฐจ์, ๋ด๋ฆผ์ฐจ์์ **orderBy** ๋ฉ์๋๋ฅผ ์ด์ฉํด์ ํ ์ ์์ต๋๋ค.
```js
useEffect(() => {
const unsubscribe = DB.collection('channels')
.orderBy('createdAt', 'desc')
.onSnapshot(snapshot => {
const list = [];snapshot.forEach(doc => {
list.push(doc.data());
});
setChannels(list);
});return () => unsubscribe();
}, []);
```
## ๐จ๐ปโ๐ป react-native-gifted-chat
- ์ฑํ ํ๋ฉด์์ ์ฌ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ ๋ค์ํ๊ฒ ์ ๊ณตํ๋ react-native-gifted-chat ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- react-native-gifted-chat ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ GiftedChat ์ปดํฌ๋ํธ๋ ๋ค์ํ ์ค์ ์ด ๊ฐ๋ฅํ๋๋ก ๋ง์ ์์ฑ์ ์ ๊ณตํ๋ค.
1. ์ ๋ ฅ๋ ๋ด์ฉ์ ์ค์ ๋ ์ฌ์ฉ์์ ์ ๋ณด ๋ฐ ์๋์ผ๋ก ์์ฑ๋ ID์ ํจ๊ป ์ ๋ฌ ํ๋ ๊ธฐ๋ฅ
2. ์ ์ก ๋ฒํผ์ ์์ ํ๋ ๊ธฐ๋ฅ
3. ์คํฌ๋กค์ ์์น์ ๋ฐ๋ผ ์คํฌ๋กค ์์น๋ฅผ ๋ณ๊ฒฝํ๋ ๋ฒํผ ๋ ๋๋ง
```js
}
/>
```
- user์ ๋ค์๊ณผ ๊ฐ์ ํํ๋ก ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์ ๋ ฅํด๋๋ฉด onSend์ ์ ์ํ ํจ์๊ฐ ํธ์ถ๋ ๋ ์ ๋ ฅ๋ ๋ฉ์์ง์ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ํฌํจํ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ๋ค.
```
User {
_id: string | number;
name: string;
avatar: string | renderFunction;
}
```
```
Message {
_id: string | number;
text: string;
createdAt: Date | number;
user: User;
...
}
```