[수업 목표]

  1. 앱과 서버에 대한 이해
  2. 서버리스에 대한 이해
  3. 파이어베이스를 이용한 서버 구성

[목차]


01. 4주차 오늘 배울 것

  • 오늘 배울 내용

  • 앱과 서버

![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a04b5f4d-9616-418b-8fa6-707ed507833b/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a04b5f4d-9616-418b-8fa6-707ed507833b/Untitled.png)

<aside>
💡 앱에 모든 데이터를 담을 순 없습니다. 

1) 앱 용량이 커질 수도 있고
2) 앱 개발자가 새로운 데이터를 사용자에게 제공하려면, 새로운 데이터를 담아 다시 배포해야 겠죠?

그래서 데이터가 담긴 곳, 즉 서버라 부를 곳을 배우고 앱에서 서버의 데이터를 가져오고 변경하는 방법에 대해 배웁니다.

</aside>
  • 서버리스(serverless)

  • 파이어베이스

    https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ec36d7ec-751b-4041-b60f-b4f7258c4092/Untitled.png

02. [앱과 서버] 우리 앱에서의 서버

  • 앱과 서버의 동작 방식

      www.sparta.com/getdata ←- 데이터 조회 API
      www.sparta.com/setData ←- 데이터 저장 API
      db.ref('/like/').on('value') ←- 데이터 조회 API
      db.ref('/like/').set(new_like); <-- 데이터 저장 API
  • 서버에서 주는 데이터 형식 JSON

    https://s3-us-west-2.amazonaws.com/secure.notion-static.com/00ffef83-0d54-463d-b164-70f9b60f4b62/Untitled.png

  • 리액트 네이티브에 주로 데이터를 준비하는 시점

    • 1) 앱 화면이 그려진 다음 데이터를 준비 ← useEffect

        useEffect(()=>{
          //서버 API 사용
          //이 화면에서 사용 할 데이터 준비 등... 
        },[])
    • 2) 앱에서 사용자가 저장 버튼을 눌렀다!

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/87c228e8-2a99-40dc-a1d6-1da46a124905/Untitled.png

03. [앱과 서버]날씨 서버 외부 API - 휴대폰 위치 가져오기

  • 서버 API란?

  • 날씨 앱에 날씨 API 사용해보기

    • [실습 ✍️] 날씨 API 사용 (1)

      • (1) 앱 위치 정보 권한 설정

          expo install expo-location
        • [코드스니펫] MainPage.js 날씨 가져오기 도구 적용

            import React,{useState,useEffect} from 'react';
            import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';
          
            const main = 'https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png'
            import data from '../data.json';
            import Card from '../components/Card';
            import Loading from '../components/Loading';
            import { StatusBar } from 'expo-status-bar';
            import * as Location from "expo-location";
            export default function MainPage({navigation,route}) {
              //useState 사용법
                //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
              //setState는 state를 변경시킬때 사용해야하는 함수
          
              //모두 다 useState가 선물해줌
              //useState()안에 전달되는 값은 state 초기값
              const [state,setState] = useState([])
              const [cateState,setCateState] = useState([])
          
                //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
              //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
              const [ready,setReady] = useState(true)
          
              useEffect(()=>{
                navigation.setOptions({
                  title:'나만의 꿀팁'
                })  
                    //뒤의 1000 숫자는 1초를 뜻함
                //1초 뒤에 실행되는 코드들이 담겨 있는 함수
                setTimeout(()=>{
                    //헤더의 타이틀 변경
                    getLocation()
                    setState(data.tip)
                    setCateState(data.tip)
                    setReady(false)
                },1000)
          
          
              },[])

              const getLocation = async () => {
                //수많은 로직중에 에러가 발생하면
                //해당 에러를 포착하여 로직을 멈추고,에러를 해결하기 위한 catch 영역 로직이 실행
                try {
                  //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
                  await Location.requestForegroundPermissionsAsync();
                  const locationData= await Location.getCurrentPositionAsync();
                  console.log(locationData)

                } catch (error) {
                  //혹시나 위치를 못가져올 경우를 대비해서, 안내를 준비합니다
                  Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
                }
              }

              const category = (cate) => {
                if(cate == "전체보기"){
                    //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
                    setCateState(state)
                }else{
                    setCateState(state.filter((d)=>{
                        return d.category == cate
                    }))
                }
            }

              //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
              // let tip = state.tip;
              let todayWeather = 10 + 17;
              let todayCondition = "흐림"
              //return 구문 밖에서는 슬래시 두개 방식으로 주석
              return ready ? <Loading/> :  (
                /*
                  return 구문 안에서는 {슬래시 + * 방식으로 주석
                */

                <ScrollView style={styles.container}>
                  <StatusBar style="light" />
                  {/* <Text style={styles.title}>나만의 꿀팁</Text> */}
                         <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text>
                   <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}>
                      <Text style={styles.aboutButtonText}>소개 페이지</Text>
                    </TouchableOpacity>
                  <Image style={styles.mainImage} source={{uri:main}}/>
                  <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
                  <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>
                    <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
                    <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
                    <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
                    <TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
                  </ScrollView>
                  <View style={styles.cardContainer}>
                     {/* 하나의 카드 영역을 나타내는 View */}
                     {
                      cateState.map((content,i)=>{
                        return (<Card content={content} key={i} navigation={navigation}/>)
                      })
                    }

                  </View>

                </ScrollView>)
            }

            const styles = StyleSheet.create({
              container: {
                //앱의 배경 색
                backgroundColor: '#fff',
              },
              title: {
                //폰트 사이즈
                fontSize: 20,
                //폰트 두께
                fontWeight: '700',
                //위 공간으로 부터 이격
                marginTop:50,
                //왼쪽 공간으로 부터 이격
                marginLeft:20
              },
            weather:{
                alignSelf:"flex-end",
                paddingRight:20
              },
              mainImage: {
                //컨텐츠의 넓이 값
                width:'90%',
                //컨텐츠의 높이 값
                height:200,
                //컨텐츠의 모서리 구부리기
                borderRadius:10,
                marginTop:20,
                //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능)
                //각 속성의 값들은 공식문서에 고대로~ 나와 있음
                alignSelf:"center"
              },
              middleContainer:{
                marginTop:20,
                marginLeft:10,
                height:60
              },
              middleButtonAll: {
                width:100,
                height:50,
                padding:15,
                backgroundColor:"#20b2aa",
                borderColor:"deeppink",
                borderRadius:15,
                margin:7
              },
              middleButton01: {
                width:100,
                height:50,
                padding:15,
                backgroundColor:"#fdc453",
                borderColor:"deeppink",
                borderRadius:15,
                margin:7
              },
              middleButton02: {
                width:100,
                height:50,
                padding:15,
                backgroundColor:"#fe8d6f",
                borderRadius:15,
                margin:7
              },
              middleButton03: {
                width:100,
                height:50,
                padding:15,
                backgroundColor:"#9adbc5",
                borderRadius:15,
                margin:7
              },
              middleButton04: {
                width:100,
                height:50,
                padding:15,
                backgroundColor:"#f886a8",
                borderRadius:15,
                margin:7
              },
              middleButtonText: {
                color:"#fff",
                fontWeight:"700",
                //텍스트의 현재 위치에서의 정렬 
                textAlign:"center"
              },
              middleButtonTextAll: {
                color:"#fff",
                fontWeight:"700",
                //텍스트의 현재 위치에서의 정렬 
                textAlign:"center"
              },
              cardContainer: {
                marginTop:10,
                marginLeft:10
              },
              aboutButton: {
                backgroundColor:"pink",
                width:100,
                height:40,
                borderRadius:10,
                alignSelf:"flex-end",
                marginRight:20,
                marginTop:10
              },
              aboutButtonText: {
                color:"#fff",
                textAlign:"center",
                marginTop:10
              }

            });
            ```

        - 코드 분석
            - 위치 정보 접근 허용 팝업 화면

                <aside>
                👉 앱을 사용하다보면 이런 팝업을 본적이 있을 겁니다. 위치 정보를 가져올 때 허용을 사용자들에게 물어보야 하거든요!

                지금 여러분들은 `expo-location`으로 구현해낸겁니다! 쉽죠?

                </aside>

                ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b2abc5e1-00b1-44fb-ba8e-e4fc9f4501c7/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b2abc5e1-00b1-44fb-ba8e-e4fc9f4501c7/Untitled.png)

            - 외부 API 요청 작업은 `try /catch`로 감싸기

                <aside>
                👉 `try/ catch`는 API 사용 할 땐 주로 사용하는 에러 처리 방지 코드입니다.
                여러분들은 API 호출을 제대로 호출 했을 수도 있지만
                서버 측에서 혹은 휴대폰 자체에서 등, 앱 외적으로 오류가 발생 할 수 있습니다. 이런 상황들을 처리하는 코드입니다.

                `try{}` 부분엔 API요청 같은 작업 코드를
                `catch{}` 부분엔 에러가 발생 했을 때 실행 할 코드를 작성합니다.

                </aside>

                ```jsx
                try {
                      await Location.requestPermissionsAsync();
                      const locationData= await Location.getCurrentPositionAsync();
                            console.log(locationData)
                    } catch (error) {
                      //혹시나 위치를 못가져올 경우를 대비해서, 안내를 준비합니다
                      Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껐다 켜볼까요?");
                    }
                ```

            - 함수 실행 순서를 정해주는 `async / await`

                ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/dba603d4-3119-4c55-ba79-46895bff580a/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/dba603d4-3119-4c55-ba79-46895bff580a/Untitled.png)

                <aside>
                👉 코드 상에 `async / await` 키워드를 볼 수 있습니다.
                이는 단순히 외부 API 호출 및 휴대폰 기기에 대한 정보/파일 등에 접근할 때 사용하는 키워드라고 생각하면 쉬운데요!

                이걸 이때 쓰는 이유는 함수의 실행 순서를 딱! 고정하기 위해서입니다.
                외부 네트워크 작업(API 호출)이나 휴대폰의 파일 시스템 또는 위치정보 가져오기 같이 무거운 작업을들 하는 경우 어떤 작업부터 할지 알수가 없습니다.

                자바스크립트의 특징인 "`비동기`" 라는 특징 때문인데,
                이에 대해 자세히 알기 보다,

                1) 외부 API 작업과 
                2) 앱이 아닌 휴대폰 자체 기능(위치 정보 권한 물어보기 등)을 

                사용할 땐 `async / await`를 사용한다 생각하면 편합니다

                사용 할 땐 함수들을 감싸는 함수 선언 부 앞에 `async`
                사용하는 함수들 앞엔 `await` 입니다

                다음 예시를 보시면 금방 이해가 됩니다

                </aside>

                ```jsx
                const func = async function(){
                    await func01()
                  await func02()
                }
                ```

                ```jsx
                const func = async () => {
                   await func01()
                   await func02()
                }
                ```

                ```jsx
                async function func(){
                     await func01()
                     await func02()
                }
                ```

            - 위도 경도

                ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e7e33a88-d1bd-4af6-bbc3-4331b33bd1de/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e7e33a88-d1bd-4af6-bbc3-4331b33bd1de/Untitled.png)

                <aside>
                👉 `console.log()`로 찍어본 `locationData`에는 위치 좌표가 들어 있습니다. 딕셔너리 안에 말이죠! 그럼 `latitude`, `logtitude`이 두 좌표는 다음과 같이 꺼낼 수 있을 것 같습니다.

                </aside>

                - **[코드스니펫] MainPage.js 위도 경도 데이터만 취하기**

                    ```jsx
                    import React,{useState,useEffect} from 'react';
                    import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';

                    const main = 'https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmain.png?alt=media&token=8e5eb78d-19ee-4359-9209-347d125b322c'
                    import data from '../data.json';
                    import Card from '../components/Card';
                    import Loading from '../components/Loading';
                    import { StatusBar } from 'expo-status-bar';
                    import * as Location from "expo-location";
                    export default function MainPage({navigation,route}) {
                      //useState 사용법
                        //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
                      //setState는 state를 변경시킬때 사용해야하는 함수

                      //모두 다 useState가 선물해줌
                      //useState()안에 전달되는 값은 state 초기값
                      const [state,setState] = useState([])
                      const [cateState,setCateState] = useState([])

                        //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
                      //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
                      const [ready,setReady] = useState(true)

                      useEffect(()=>{
                        navigation.setOptions({
                          title:'나만의 꿀팁'
                        })  
                            //뒤의 1000 숫자는 1초를 뜻함
                        //1초 뒤에 실행되는 코드들이 담겨 있는 함수
                        setTimeout(()=>{
                            //헤더의 타이틀 변경
                            getLocation()
                            setState(data.tip)
                            setCateState(data.tip)
                            setReady(false)
                        },1000)


                      },[])

                      const getLocation = async () => {
                        //수많은 로직중에 에러가 발생하면
                        //해당 에러를 포착하여 로직을 멈추고,에러를 해결하기 위한 catch 영역 로직이 실행
                        try {
                          //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
                          await Location.requestForegroundPermissionsAsync();
                          const locationData= await Location.getCurrentPositionAsync();
                          console.log(locationData['coords']['latitude'])
                          console.log(locationData['coords']['longitude'])

                        } catch (error) {
                          //혹시나 위치를 못가져올 경우를 대비해서, 안내를 준비합니다
                          Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
                        }
                      }

                      const category = (cate) => {
                        if(cate == "전체보기"){
                            //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
                            setCateState(state)
                        }else{
                            setCateState(state.filter((d)=>{
                                return d.category == cate
                            }))
                        }
                    }

                      //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
                      // let tip = state.tip;
                      let todayWeather = 10 + 17;
                      let todayCondition = "흐림"
                      //return 구문 밖에서는 슬래시 두개 방식으로 주석
                      return ready ? <Loading/> :  (
                        /*
                          return 구문 안에서는 {슬래시 + * 방식으로 주석
                        */

                        <ScrollView style={styles.container}>
                          <StatusBar style="light" />
                          {/* <Text style={styles.title}>나만의 꿀팁</Text> */}
                                 <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text>
                           <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}>
                              <Text style={styles.aboutButtonText}>소개 페이지</Text>
                            </TouchableOpacity>
                          <Image style={styles.mainImage} source={{uri:main}}/>
                          <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
                          <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>
                            <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
                            <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
                            <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
                            <TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
                          </ScrollView>
                          <View style={styles.cardContainer}>
                             {/* 하나의 카드 영역을 나타내는 View */}
                             {
                              cateState.map((content,i)=>{
                                return (<Card content={content} key={i} navigation={navigation}/>)
                              })
                            }

                          </View>

                        </ScrollView>)
                    }

                    const styles = StyleSheet.create({
                      container: {
                        //앱의 배경 색
                        backgroundColor: '#fff',
                      },
                      title: {
                        //폰트 사이즈
                        fontSize: 20,
                        //폰트 두께
                        fontWeight: '700',
                        //위 공간으로 부터 이격
                        marginTop:50,
                        //왼쪽 공간으로 부터 이격
                        marginLeft:20
                      },
                    weather:{
                        alignSelf:"flex-end",
                        paddingRight:20
                      },
                      mainImage: {
                        //컨텐츠의 넓이 값
                        width:'90%',
                        //컨텐츠의 높이 값
                        height:200,
                        //컨텐츠의 모서리 구부리기
                        borderRadius:10,
                        marginTop:20,
                        //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능)
                        //각 속성의 값들은 공식문서에 고대로~ 나와 있음
                        alignSelf:"center"
                      },
                      middleContainer:{
                        marginTop:20,
                        marginLeft:10,
                        height:60
                      },
                      middleButtonAll: {
                        width:100,
                        height:50,
                        padding:15,
                        backgroundColor:"#20b2aa",
                        borderColor:"deeppink",
                        borderRadius:15,
                        margin:7
                      },
                      middleButton01: {
                        width:100,
                        height:50,
                        padding:15,
                        backgroundColor:"#fdc453",
                        borderColor:"deeppink",
                        borderRadius:15,
                        margin:7
                      },
                      middleButton02: {
                        width:100,
                        height:50,
                        padding:15,
                        backgroundColor:"#fe8d6f",
                        borderRadius:15,
                        margin:7
                      },
                      middleButton03: {
                        width:100,
                        height:50,
                        padding:15,
                        backgroundColor:"#9adbc5",
                        borderRadius:15,
                        margin:7
                      },
                      middleButton04: {
                        width:100,
                        height:50,
                        padding:15,
                        backgroundColor:"#f886a8",
                        borderRadius:15,
                        margin:7
                      },
                      middleButtonText: {
                        color:"#fff",
                        fontWeight:"700",
                        //텍스트의 현재 위치에서의 정렬 
                        textAlign:"center"
                      },
                      middleButtonTextAll: {
                        color:"#fff",
                        fontWeight:"700",
                        //텍스트의 현재 위치에서의 정렬 
                        textAlign:"center"
                      },
                      cardContainer: {
                        marginTop:10,
                        marginLeft:10
                      },
                      aboutButton: {
                        backgroundColor:"pink",
                        width:100,
                        height:40,
                        borderRadius:10,
                        alignSelf:"flex-end",
                        marginRight:20,
                        marginTop:10
                      },
                      aboutButtonText: {
                        color:"#fff",
                        textAlign:"center",
                        marginTop:10
                      }

                    });
                    ```

04. [앱과 서버] 날씨 서버 외부 API - API 적용

  • [실습 ✍️] 날씨 API 사용 (2)

    • (2) API 사용

        yarn add axios
      • [코드스니펫] MainPage.js API 사용해보기

          import React,{useState,useEffect} from 'react';
          import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';
        
          const main = 'https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png'
          import data from '../data.json';
          import Card from '../components/Card';
          import Loading from '../components/Loading';
          import { StatusBar } from 'expo-status-bar';
          import * as Location from "expo-location";
          import axios from "axios"
        
          export default function MainPage({navigation,route}) {
            //useState 사용법
              //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
            //setState는 state를 변경시킬때 사용해야하는 함수
        
            //모두 다 useState가 선물해줌
            //useState()안에 전달되는 값은 state 초기값
            const [state,setState] = useState([])
            const [cateState,setCateState] = useState([])
        
              //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
            //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
            const [ready,setReady] = useState(true)
        
            useEffect(()=>{
              navigation.setOptions({
                title:'나만의 꿀팁'
              })  
                  //뒤의 1000 숫자는 1초를 뜻함
              //1초 뒤에 실행되는 코드들이 담겨 있는 함수
              setTimeout(()=>{
                  //헤더의 타이틀 변경
                  getLocation()
                  setState(data.tip)
                  setCateState(data.tip)
                  setReady(false)
              },1000)
        
        
          },[])

          const getLocation = async () => {
            //수많은 로직중에 에러가 발생하면
            //해당 에러를 포착하여 로직을 멈추고,에러를 해결하기 위한 catch 영역 로직이 실행
            try {
              //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
              await Location.requestForegroundPermissionsAsync();
              const locationData= await Location.getCurrentPositionAsync();
              console.log(locationData)
              console.log(locationData['coords']['latitude'])
              console.log(locationData['coords']['longitude'])
              const latitude = locationData['coords']['latitude']
              const longitude = locationData['coords']['longitude']
              const API_KEY = "cfc258c75e1da2149c33daffd07a911d";
              const result = await axios.get(
                `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
              );

              console.log(result)

            } catch (error) {
              //혹시나 위치를 못가져올 경우를 대비해서, 안내를 준비합니다
              Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
            }
          }

          const category = (cate) => {
            if(cate == "전체보기"){
                //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
                setCateState(state)
            }else{
                setCateState(state.filter((d)=>{
                    return d.category == cate
                }))
            }
        }

          //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
          // let tip = state.tip;
          let todayWeather = 10 + 17;
          let todayCondition = "흐림"
          //return 구문 밖에서는 슬래시 두개 방식으로 주석
          return ready ? <Loading/> :  (
            /*
              return 구문 안에서는 {슬래시 + * 방식으로 주석
            */

            <ScrollView style={styles.container}>
              <StatusBar style="light" />
              {/* <Text style={styles.title}>나만의 꿀팁</Text> */}
                     <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text>
               <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}>
                  <Text style={styles.aboutButtonText}>소개 페이지</Text>
                </TouchableOpacity>
              <Image style={styles.mainImage} source={{uri:main}}/>
              <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
              <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
              </ScrollView>
              <View style={styles.cardContainer}>
                 {/* 하나의 카드 영역을 나타내는 View */}
                 {
                  cateState.map((content,i)=>{
                    return (<Card content={content} key={i} navigation={navigation}/>)
                  })
                }

              </View>

            </ScrollView>)
        }

        const styles = StyleSheet.create({
          container: {
            //앱의 배경 색
            backgroundColor: '#fff',
          },
          title: {
            //폰트 사이즈
            fontSize: 20,
            //폰트 두께
            fontWeight: '700',
            //위 공간으로 부터 이격
            marginTop:50,
            //왼쪽 공간으로 부터 이격
            marginLeft:20
          },
        weather:{
            alignSelf:"flex-end",
            paddingRight:20
          },
          mainImage: {
            //컨텐츠의 넓이 값
            width:'90%',
            //컨텐츠의 높이 값
            height:200,
            //컨텐츠의 모서리 구부리기
            borderRadius:10,
            marginTop:20,
            //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능)
            //각 속성의 값들은 공식문서에 고대로~ 나와 있음
            alignSelf:"center"
          },
          middleContainer:{
            marginTop:20,
            marginLeft:10,
            height:60
          },
          middleButtonAll: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#20b2aa",
            borderColor:"deeppink",
            borderRadius:15,
            margin:7
          },
          middleButton01: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#fdc453",
            borderColor:"deeppink",
            borderRadius:15,
            margin:7
          },
          middleButton02: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#fe8d6f",
            borderRadius:15,
            margin:7
          },
          middleButton03: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#9adbc5",
            borderRadius:15,
            margin:7
          },
          middleButton04: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#f886a8",
            borderRadius:15,
            margin:7
          },
          middleButtonText: {
            color:"#fff",
            fontWeight:"700",
            //텍스트의 현재 위치에서의 정렬 
            textAlign:"center"
          },
          middleButtonTextAll: {
            color:"#fff",
            fontWeight:"700",
            //텍스트의 현재 위치에서의 정렬 
            textAlign:"center"
          },
          cardContainer: {
            marginTop:10,
            marginLeft:10
          },
          aboutButton: {
            backgroundColor:"pink",
            width:100,
            height:40,
            borderRadius:10,
            alignSelf:"flex-end",
            marginRight:20,
            marginTop:10
          },
          aboutButtonText: {
            color:"#fff",
            textAlign:"center",
            marginTop:10
          }

        });
        ```

    - 날씨 API에서 전달해주는 데이터 모습

        ```jsx
        Object {
          "config": Object {
            "adapter": [Function xhrAdapter],
            "data": undefined,
            "headers": Object {
              "Accept": "application/json, text/plain, */*",
            },
            "maxContentLength": -1,
            "method": "get",
            "timeout": 0,
            "transformRequest": Array [
              [Function transformRequest],
            ],
            "transformResponse": Array [
              [Function transformResponse],
            ],
            "url": "http://api.openweathermap.org/data/2.5/weather?lat=37.4219284&lon=-122.0839242&appid=cfc258c75e1da2149c33daffd07a911d&units=metric",
            "validateStatus": [Function validateStatus],
            "xsrfCookieName": "XSRF-TOKEN",
            "xsrfHeaderName": "X-XSRF-TOKEN",
          },
          "data": Object {
            "base": "stations",
            "clouds": Object {
              "all": 90,
            },
            "cod": 200,
            "coord": Object {
              "lat": 37.42,
              "lon": -122.08,
            },
            "dt": 1595942079,
            "id": 5375480,
            "main": Object {
              "feels_like": 15.72,
              "humidity": 82,
              "pressure": 1013,
              "temp": 15.4,
              "temp_max": 16.11,
              "temp_min": 14.44,
            },
            "name": "Mountain View",
            "sys": Object {
              "country": "US",
              "id": 5122,
              "sunrise": 1595941783,
              "sunset": 1595992779,
              "type": 1,
            },
            "timezone": -25200,
            "visibility": 10000,
            "weather": Array [
              Object {
                "description": "overcast clouds",
                "icon": "04d",
                "id": 804,
                "main": "Clouds",
              },
            ],
            "wind": Object {
              "deg": 312,
              "speed": 0.58,
            },
          },
          "headers": Object {
            "access-control-allow-credentials": "true",
            "access-control-allow-methods": "GET, POST",
            "access-control-allow-origin": "*",
            "cache-control": "public, max-age=0",
            "connection": "keep-alive",
            "content-length": "477",
            "content-type": "application/json; charset=utf-8",
            "date": "Tue, 28 Jul 2020 13:16:24 GMT",
            "server": "openresty",
            "x-cache-key": "/data/2.5/weather?lat=37.42&lon=-122.08&units=metric",
          },
          "request": XMLHttpRequest {
            "DONE": 4,
            "HEADERS_RECEIVED": 2,
            "LOADING": 3,
            "OPENED": 1,
            "UNSENT": 0,
            "_aborted": false,
            "_cachedResponse": undefined,
            "_hasError": false,
            "_headers": Object {
              "accept": "application/json, text/plain, */*",
            },
            "_incrementalEvents": false,
            "_lowerCaseResponseHeaders": Object {
              "access-control-allow-credentials": "true",
              "access-control-allow-methods": "GET, POST",
              "access-control-allow-origin": "*",
              "cache-control": "public, max-age=0",
              "connection": "keep-alive",
              "content-length": "477",
              "content-type": "application/json; charset=utf-8",
              "date": "Tue, 28 Jul 2020 13:16:24 GMT",
              "server": "openresty",
              "x-cache-key": "/data/2.5/weather?lat=37.42&lon=-122.08&units=metric",
            },
            "_method": "GET",
            "_requestId": null,
            "_response": "{\"coord\":{\"lon\":-122.08,\"lat\":37.42},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],\"base\":\"stations\",\"main\":{\"temp\":15.4,\"feels_like\":15.72,\"temp_min\":14.44,\"temp_max\":16.11,\"pressure\":1013,\"humidity\":82},\"visibility\":10000,\"wind\":{\"speed\":0.58,\"deg\":312},\"clouds\":{\"all\":90},\"dt\":1595942079,\"sys\":{\"type\":1,\"id\":5122,\"country\":\"US\",\"sunrise\":1595941783,\"sunset\":1595992779},\"timezone\":-25200,\"id\":5375480,\"name\":\"Mountain View\",\"cod\":200}",
            "_responseType": "",
            "_sent": true,
            "_subscriptions": Array [],
            "_timedOut": false,
            "_trackingName": "unknown",
            "_url": "http://api.openweathermap.org/data/2.5/weather?lat=37.4219284&lon=-122.0839242&appid=cfc258c75e1da2149c33daffd07a911d&units=metric",
            "readyState": 4,
            "responseHeaders": Object {
              "Access-Control-Allow-Credentials": "true",
              "Access-Control-Allow-Methods": "GET, POST",
              "Access-Control-Allow-Origin": "*",
              "Cache-Control": "public, max-age=0",
              "Connection": "keep-alive",
              "Content-Length": "477",
              "Content-Type": "application/json; charset=utf-8",
              "Date": "Tue, 28 Jul 2020 13:16:24 GMT",
              "Server": "openresty",
              "X-Cache-Key": "/data/2.5/weather?lat=37.42&lon=-122.08&units=metric",
            },
            "responseURL": "http://api.openweathermap.org/data/2.5/weather?lat=37.4219284&lon=-122.0839242&appid=cfc258c75e1da2149c33daffd07a911d&units=metric",
            "status": 200,
            "timeout": 0,
            "upload": XMLHttpRequestEventTarget {},
            "withCredentials": true,
          },
          "status": 200,
          "statusText": undefined,
        }
        Object {
          "config": Object {
            "adapter": [Function xhrAdapter],
            "data": undefined,
            "headers": Object {
              "Accept": "application/json, text/plain, */*",
            },
            "maxContentLength": -1,
            "method": "get",
            "timeout": 0,
            "transformRequest": Array [
              [Function transformRequest],
            ],
            "transformResponse": Array [
              [Function transformResponse],
            ],
            "url": "http://api.openweathermap.org/data/2.5/weather?lat=37.4219284&lon=-122.0839242&appid=cfc258c75e1da2149c33daffd07a911d&units=metric",
            "validateStatus": [Function validateStatus],
            "xsrfCookieName": "XSRF-TOKEN",
            "xsrfHeaderName": "X-XSRF-TOKEN",
          },
          "data": Object {
            "base": "stations",
            "clouds": Object {
              "all": 90,
            },
            "cod": 200,
            "coord": Object {
              "lat": 37.42,
              "lon": -122.08,
            },
            "dt": 1595942079,
            "id": 5375480,
            "main": Object {
              "feels_like": 15.72,
              "humidity": 82,
              "pressure": 1013,
              "temp": 15.4,
              "temp_max": 16.11,
              "temp_min": 14.44,
            },
            "name": "Mountain View",
            "sys": Object {
              "country": "US",
              "id": 5122,
              "sunrise": 1595941783,
              "sunset": 1595992779,
              "type": 1,
            },
            "timezone": -25200,
            "visibility": 10000,
            "weather": Array [
              Object {
                "description": "overcast clouds",
                "icon": "04d",
                "id": 804,
                "main": "Clouds",
              },
            ],
            "wind": Object {
              "deg": 312,
              "speed": 0.58,
            },
          },
          "headers": Object {
            "access-control-allow-credentials": "true",
            "access-control-allow-methods": "GET, POST",
            "access-control-allow-origin": "*",
            "cache-control": "public, max-age=0",
            "connection": "keep-alive",
            "content-length": "477",
            "content-type": "application/json; charset=utf-8",
            "date": "Tue, 28 Jul 2020 13:16:24 GMT",
            "server": "openresty",
            "x-cache-key": "/data/2.5/weather?lat=37.42&lon=-122.08&units=metric",
          },
          "request": XMLHttpRequest {
            "DONE": 4,
            "HEADERS_RECEIVED": 2,
            "LOADING": 3,
            "OPENED": 1,
            "UNSENT": 0,
            "_aborted": false,
            "_cachedResponse": undefined,
            "_hasError": false,
            "_headers": Object {
              "accept": "application/json, text/plain, */*",
            },
            "_incrementalEvents": false,
            "_lowerCaseResponseHeaders": Object {
              "access-control-allow-credentials": "true",
              "access-control-allow-methods": "GET, POST",
              "access-control-allow-origin": "*",
              "cache-control": "public, max-age=0",
              "connection": "keep-alive",
              "content-length": "477",
              "content-type": "application/json; charset=utf-8",
              "date": "Tue, 28 Jul 2020 13:16:24 GMT",
              "server": "openresty",
              "x-cache-key": "/data/2.5/weather?lat=37.42&lon=-122.08&units=metric",
            },
            "_method": "GET",
            "_requestId": null,
            "_response": "{\"coord\":{\"lon\":-122.08,\"lat\":37.42},\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],\"base\":\"stations\",\"main\":{\"temp\":15.4,\"feels_like\":15.72,\"temp_min\":14.44,\"temp_max\":16.11,\"pressure\":1013,\"humidity\":82},\"visibility\":10000,\"wind\":{\"speed\":0.58,\"deg\":312},\"clouds\":{\"all\":90},\"dt\":1595942079,\"sys\":{\"type\":1,\"id\":5122,\"country\":\"US\",\"sunrise\":1595941783,\"sunset\":1595992779},\"timezone\":-25200,\"id\":5375480,\"name\":\"Mountain View\",\"cod\":200}",
            "_responseType": "",
            "_sent": true,
            "_subscriptions": Array [],
            "_timedOut": false,
            "_trackingName": "unknown",
            "_url": "http://api.openweathermap.org/data/2.5/weather?lat=37.4219284&lon=-122.0839242&appid=cfc258c75e1da2149c33daffd07a911d&units=metric",
            "readyState": 4,
            "responseHeaders": Object {
              "Access-Control-Allow-Credentials": "true",
              "Access-Control-Allow-Methods": "GET, POST",
              "Access-Control-Allow-Origin": "*",
              "Cache-Control": "public, max-age=0",
              "Connection": "keep-alive",
              "Content-Length": "477",
              "Content-Type": "application/json; charset=utf-8",
              "Date": "Tue, 28 Jul 2020 13:16:24 GMT",
              "Server": "openresty",
              "X-Cache-Key": "/data/2.5/weather?lat=37.42&lon=-122.08&units=metric",
            },
            "responseURL": "http://api.openweathermap.org/data/2.5/weather?lat=37.4219284&lon=-122.0839242&appid=cfc258c75e1da2149c33daffd07a911d&units=metric",
            "status": 200,
            "timeout": 0,
            "upload": XMLHttpRequestEventTarget {},
            "withCredentials": true,
          },
          "status": 200,
          "statusText": undefined,
        }
        ```


    <aside>
    👉  날씨 API가 주는 데이터는 다음과 같이 엄청 깁니다
    여기서 우린 필요한 데이터만 취하면 됩니다

    필요한 데이터를 어떻게 아냐구요?

    [https://openweathermap.org/current](https://openweathermap.org/current)
    여기에 나와있습니다

    API 공식문서를 보면 어떤 키값은 어떤 데이터를 뜻하는지 나와 있습니다. 찬찬히 읽어보면 어려울 것 없이 아! 이게 이거구나라고 아실거에요!

    우린 딱 날씨와 현재 날씨 상태만 알면되므로, 다음과 같은 데이터를 찾아서 추려봤습니다.

    </aside>

    - 결과 값에  따른 키값 공식 문서설명

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/047513d8-1755-430c-a28a-5f92adeaf427/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/047513d8-1755-430c-a28a-5f92adeaf427/Untitled.png)

    ```jsx
    const temp = result.data.main.temp; 
    const condition = result.data.weather[0].main
    ```

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/5ac45328-a43a-477d-aafd-5623cd23d457/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/5ac45328-a43a-477d-aafd-5623cd23d457/Untitled.png)

    <aside>
    💡 그럼 이 데이터를 가지고 MainPage.js 우측 하단에 표시해봅시다.

    컴포넌트에서 데이터를 다루게 되었고,
    데이터 변경 시점에 따라 화면을 다시 그리니 **날씨 데이터**를 **상태** 관리 해야겠죠?

    </aside>

    - **[코드스니펫] MainPage.js 날씨 적용**

        ```jsx
        import React,{useState,useEffect} from 'react';
        import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';

        const main = 'https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png'
        import data from '../data.json';
        import Card from '../components/Card';
        import Loading from '../components/Loading';
        import { StatusBar } from 'expo-status-bar';
        import * as Location from "expo-location";
        import axios from "axios"

        export default function MainPage({navigation,route}) {
          //useState 사용법
            //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
          //setState는 state를 변경시킬때 사용해야하는 함수

          //모두 다 useState가 선물해줌
          //useState()안에 전달되는 값은 state 초기값
          const [state,setState] = useState([])
          const [cateState,setCateState] = useState([])
          //날씨 데이터 상태관리 상태 생성!
          const [weather, setWeather] = useState({
            temp : 0,
            condition : ''
          })

            //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
          //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
          const [ready,setReady] = useState(true)

          useEffect(()=>{
            navigation.setOptions({
              title:'나만의 꿀팁'
            })  
                //뒤의 1000 숫자는 1초를 뜻함
            //1초 뒤에 실행되는 코드들이 담겨 있는 함수
            setTimeout(()=>{
                //헤더의 타이틀 변경
                getLocation()
                setState(data.tip)
                setCateState(data.tip)
                setReady(false)
            },1000)


          },[])

          const getLocation = async () => {
            //수많은 로직중에 에러가 발생하면
            //해당 에러를 포착하여 로직을 멈추고,에러를 해결하기 위한 catch 영역 로직이 실행
            try {
              //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
              await Location.requestForegroundPermissionsAsync();
              const locationData= await Location.getCurrentPositionAsync();
              console.log(locationData)
              console.log(locationData['coords']['latitude'])
              console.log(locationData['coords']['longitude'])
              const latitude = locationData['coords']['latitude']
              const longitude = locationData['coords']['longitude']
              const API_KEY = "cfc258c75e1da2149c33daffd07a911d";
              const result = await axios.get(
                `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
              );

              console.log(result)
              const temp = result.data.main.temp; 
              const condition = result.data.weather[0].main

              console.log(temp)
              console.log(condition)

              //오랜만에 복습해보는 객체 리터럴 방식으로 딕셔너리 구성하기!!
              //잘 기억이 안난다면 1주차 강의 6-5를 다시 복습해보세요!
              setWeather({
                temp,condition
              })

            } catch (error) {
              //혹시나 위치를 못가져올 경우를 대비해서, 안내를 준비합니다
              Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
            }
          }

          const category = (cate) => {
            if(cate == "전체보기"){
                //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
                setCateState(state)
            }else{
                setCateState(state.filter((d)=>{
                    return d.category == cate
                }))
            }
        }

          //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
          // let tip = state.tip;
          let todayWeather = 10 + 17;
          let todayCondition = "흐림"
          //return 구문 밖에서는 슬래시 두개 방식으로 주석
          return ready ? <Loading/> :  (
            /*
              return 구문 안에서는 {슬래시 + * 방식으로 주석
            */

            <ScrollView style={styles.container}>
              <StatusBar style="light" />
              {/* <Text style={styles.title}>나만의 꿀팁</Text> */}
              <Text style={styles.weather}>오늘의 날씨: {weather.temp + '°C   ' + weather.condition} </Text>
               <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}>
                  <Text style={styles.aboutButtonText}>소개 페이지</Text>
                </TouchableOpacity>
              <Image style={styles.mainImage} source={{uri:main}}/>
              <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
              <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
              </ScrollView>
              <View style={styles.cardContainer}>
                 {/* 하나의 카드 영역을 나타내는 View */}
                 {
                  cateState.map((content,i)=>{
                    return (<Card content={content} key={i} navigation={navigation}/>)
                  })
                }

              </View>

            </ScrollView>)
        }

        const styles = StyleSheet.create({
          container: {
            //앱의 배경 색
            backgroundColor: '#fff',
          },
          title: {
            //폰트 사이즈
            fontSize: 20,
            //폰트 두께
            fontWeight: '700',
            //위 공간으로 부터 이격
            marginTop:50,
            //왼쪽 공간으로 부터 이격
            marginLeft:20
          },
        weather:{
            alignSelf:"flex-end",
            paddingRight:20
          },
          mainImage: {
            //컨텐츠의 넓이 값
            width:'90%',
            //컨텐츠의 높이 값
            height:200,
            //컨텐츠의 모서리 구부리기
            borderRadius:10,
            marginTop:20,
            //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능)
            //각 속성의 값들은 공식문서에 고대로~ 나와 있음
            alignSelf:"center"
          },
          middleContainer:{
            marginTop:20,
            marginLeft:10,
            height:60
          },
          middleButtonAll: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#20b2aa",
            borderColor:"deeppink",
            borderRadius:15,
            margin:7
          },
          middleButton01: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#fdc453",
            borderColor:"deeppink",
            borderRadius:15,
            margin:7
          },
          middleButton02: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#fe8d6f",
            borderRadius:15,
            margin:7
          },
          middleButton03: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#9adbc5",
            borderRadius:15,
            margin:7
          },
          middleButton04: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#f886a8",
            borderRadius:15,
            margin:7
          },
          middleButtonText: {
            color:"#fff",
            fontWeight:"700",
            //텍스트의 현재 위치에서의 정렬 
            textAlign:"center"
          },
          middleButtonTextAll: {
            color:"#fff",
            fontWeight:"700",
            //텍스트의 현재 위치에서의 정렬 
            textAlign:"center"
          },
          cardContainer: {
            marginTop:10,
            marginLeft:10
          },
          aboutButton: {
            backgroundColor:"pink",
            width:100,
            height:40,
            borderRadius:10,
            alignSelf:"flex-end",
            marginRight:20,
            marginTop:10
          },
          aboutButtonText: {
            color:"#fff",
            textAlign:"center",
            marginTop:10
          }

        });
        ```

    - 날씨 API 적용 모습

        ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e89b9e53-e4b2-4df2-bf7c-f8cc9ed6da78/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e89b9e53-e4b2-4df2-bf7c-f8cc9ed6da78/Untitled.png)

05. [서버 리스] 서버를 사용만 하자! 서버리스(serverless)

  • 서버리스(serverless)란?

  • 앱 서비스에서 필요한 기능들

06. [파이어베이스] 파이어베이스(firebase) 소개

  • 우리의 선택 파이어베이스(firebase)
![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/13c34c4b-1512-4906-bfd9-d2eea39fc7ea/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/13c34c4b-1512-4906-bfd9-d2eea39fc7ea/Untitled.png)

<aside>
👉 파이어베이스는 구글에서 만든 서버리스 서비스입니다.

서버에 대한 지식이 그렇게 깊지 않아도
서버적인 기능들을 사용할 수 있게끔 도와주는 서비스입니다.

</aside>

![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fdb1e756-032c-4ec5-a744-2f0217c5e6f7/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fdb1e756-032c-4ec5-a744-2f0217c5e6f7/Untitled.png)

<aside>
👉 한 서비스를 만드는데 충분한 많은 서버적 기능들을 제공해줍니다.

우리가 필요한 데이터베이스, 이미지 파일 서버는 물론
여러분들이 지금은 몰라도 되는 푸시 알람 기능, 로그인 인증 기능 등등 아주 다양한 기능들이 준비되어 있습니다

우린 여기서 데이터베이스, 이미지 파일 서버를 사용할 예정입니다

</aside>
  • [실습 ✍️] 파이어베이스 프로젝트 생성하기

    • 파이어베이스 가입

      • 가입 절차
        ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bfb7cc70-f4b0-4172-acdb-3eac87cee1e6/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bfb7cc70-f4b0-4172-acdb-3eac87cee1e6/Untitled.png)

        ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ca97fdc7-460c-4044-9517-dd50afb0e44f/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ca97fdc7-460c-4044-9517-dd50afb0e44f/Untitled.png)

- 파이어베이스 프로젝트 생성


    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/316b78d1-062a-45cf-b204-95f0c4ff1f46/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/316b78d1-062a-45cf-b204-95f0c4ff1f46/Untitled.png)

    <aside>
    👉 구글 애널리틱스 활성화를 하면, 추후에 여러분들이 만든 앱  분석을 편리하게 할 수 있습니다. 일단 설정해놓고, 앱개발 공부가 모두 끝난 후 살펴보길 권장 드립니다!

    </aside>

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b80dede3-64e9-497b-8237-e4ee4d58b474/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b80dede3-64e9-497b-8237-e4ee4d58b474/Untitled.png)

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d8cf6038-9c09-4ac5-9bbf-b23b29e77060/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d8cf6038-9c09-4ac5-9bbf-b23b29e77060/Untitled.png)

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/42331e85-7bd3-4d67-bce5-4bf96de83ab4/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/42331e85-7bd3-4d67-bce5-4bf96de83ab4/Untitled.png)

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/8f60d61e-2b32-4c48-801c-456a4d5e5938/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/8f60d61e-2b32-4c48-801c-456a4d5e5938/Untitled.png)

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d57ff6bd-35b2-4bad-aa6e-572f6894f075/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d57ff6bd-35b2-4bad-aa6e-572f6894f075/Untitled.png)

07. [파이어베이스] 파이어베이스를 앱에 연결하기

  • firebase 라이브러리 설치 및 연결

    • [실습 ✍️] 파이어베이스 프로젝트 내에서 앱 생성하기
    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/76dabdb9-7b44-42a2-adc9-098775036d1b/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/76dabdb9-7b44-42a2-adc9-098775036d1b/Untitled.png)

    <aside>
    👉 호스팅 설정은 체크 하지말고 진행!

    </aside>

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d25d66be-831f-4fbd-aeba-702287f06e6e/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d25d66be-831f-4fbd-aeba-702287f06e6e/Untitled.png)

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/64a72ce9-965d-41d3-9894-140a08cd789d/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/64a72ce9-965d-41d3-9894-140a08cd789d/Untitled.png)

    <aside>
    👉 이렇게 파이어베이스 프로젝트 내에서 앱을 생성하면, 최종적으로 연결 정보를 줍니다.

    </aside>

- [실습 ✍️] 설치
    - 1) 파이어베이스 연결 정보 확인


        <aside>
        👉 앱이 완성되면, 콘솔로 이동한다음 설정 버튼을 눌러주세요

        </aside>

        ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/61564b22-a885-49c2-88d8-77ab788649b5/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/61564b22-a885-49c2-88d8-77ab788649b5/Untitled.png)

        <aside>
        👉 드래그해서 밑을 보면, 우리가 사용할 접속 정보가 코드로 존재합니다

        </aside>

        ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/15273a4f-e63a-440c-b567-402fa7f274cf/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/15273a4f-e63a-440c-b567-402fa7f274cf/Untitled.png)

    - 2) 앱에 파이어베이스 도구 설치 및 연결


        <aside>
        👉 파이어베이스를 이용할 수 있게 도와주는 expo 도구를 설치합니다

        [파이어베이스 공식 문서](https://docs.expo.io/guides/using-firebase/)

        </aside>

        ```jsx
        expo install firebase
        ```

        <aside>
        👉 설치가 완료됐으면, `firebaseConfig.js` 파일을 하나 생성해주세요. 그리고 다음 코드를 입력해주세요!

        접속 정보엔 여러분들 접속 정보 코드를 넣어야 합니다!

        </aside>

        - firebaseConfig.js 생성

            ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/4f86f360-7076-4825-90e3-09059d5741ff/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/4f86f360-7076-4825-90e3-09059d5741ff/Untitled.png)

        - **[코드스니펫] firebaseConfig.js**

            ```jsx
            import firebase from "firebase/compat/app";

            // 사용할 파이어베이스 서비스 주석을 해제합니다
            //import "firebase/compat/auth";
            import "firebase/compat/database";
            //import "firebase/compat/firestore";
            //import "firebase/compat/functions";
            import "firebase/compat/storage";

            // Initialize Firebase
            //파이어베이스 사이트에서 봤던 연결정보를 여기에 가져옵니다
            const firebaseConfig = {
              apiKey: "AIzaSyBKG2xY91x23W8PF1231k5OUJ5o9kHSKYQeNWUw",
              authDomain: "sparta-psytest-gun.firebaseapp.com",
              **databaseURL**: "https://sparta-psytest-gun.firebaseio.com",
                //위 databaseURL은 firebase에서 기본제공 해주지 않으니 직접 작성해주세요!
              projectId: "sparta-psytest-gun",
              storageBucket: "sparta-psytest-gun.appspot.com",
              messagingSenderId: "781790378482",
              appId: "1:78179037128482:web:ddbca5330779f67b947136b",
              measurementId: "G-3F5L9F3340Q3"
            };

            //사용 방법입니다. 
            //파이어베이스 연결에 혹시 오류가 있을 경우를 대비한 코드로 알아두면 됩니다.
            if (!firebase.apps.length) {
                firebase.initializeApp(firebaseConfig);
            }

            export const firebase_db = firebase.database()
            ```

        - [🤯firebaseConfig.js 코드 설정시 유의사항!!!]

            최근 파이어베이스에서 프로젝트를 만들면 원래는 설정 코드에 넣어줬던 databaseUrl을 안넣어주고 있습니다. 따라서 다음과 같이 리얼타임 데이터베이스 탭으로 들어가신다음, 데이터베이스 주소를 설정코드에 넣어주세요!

            ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bcd6b370-62fe-4844-8c1a-c943a9c29791/_2020-12-12__2.14.21.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bcd6b370-62fe-4844-8c1a-c943a9c29791/_2020-12-12__2.14.21.png)

            ```css
            const firebaseConfig = {
              apiKey: "AIzaSyBKG2xY91x23W8PF1231k5OUJ5o9kHSKYQeNWUw",
              authDomain: "sparta-psytest-gun.firebaseapp.com",
              **databaseURL: "https://sparta-psytest-gun.firebaseio.com",**
              projectId: "sparta-psytest-gun",
              storageBucket: "sparta-psytest-gun.appspot.com",
              messagingSenderId: "781790378482",
              appId: "1:78179037128482:web:ddbca5330779f67b947136b",
              measurementId: "G-3F5L9F3340Q3"
            };
            ```

            - 이건 제 코드니까 중간에 databaseURL 부분에 여러분들 주소를 넣어야 합니다.!!!!
        - [🤯파이어베이스 버전업에 따른 코드 유의사항!!!]

            <aside>
            💡 파이어베이스 라이브러리가 v9로 업데이트되면서 강의와 맞지 않는 부분이 생겼습니다. 강의의 스니펫과 강의노트 업데이트해주시면 좋을거 같습니다.기존 코드 :

            </aside>

            <aside>
            💡 Firebase 패키지의 버전이 올라가면서 API 사용법이 변경되어서 config파일에 오류가 날 수도 있습니다. 이 오류는 파이어베이스 패키지 버전을 낮춰서 해결할 수도 있구요, 아래와 같이 소스 코드를 일부 변경해서 해결할 수도 있어요!
            `firebaseConfig.j`s 의
            `import * as firebase from 'firebase/app';`
            이거를
            `import firebase from 'firebase/app';`
            이렇게 바꿔주시면 문제가 해결됩니다!

            </aside>

            기존 코드 :

            ```
            import * as firebase from 'firebase/app';
            혹은
            import firebase from 'firebase/app';

            // 사용할 파이어베이스 서비스 주석을 해제합니다
            //import "firebase/auth";
            import "firebase/database";
            //import "firebase/firestore";
            //import "firebase/functions";
            import "firebase/storage";
            ```

            새로운 코드 :

            ```
            import firebase from "firebase/compat/app";

            // 사용할 파이어베이스 서비스 주석을 해제합니다
            //import "firebase/compat/auth";
            import "firebase/compat/database";
            //import "firebase/compat/firestore";
            //import "firebase/compat/functions";
            import "firebase/compat/storage";
            ```

        - [🤯 파이어베이스를 설치하고 idb 오류가 뜨면서 서버에 접속이 되지 않아요!!!]

            파이어베이스가 업데이트 되면서 파일을 cjs로 되돌려주는데 해당 파일을 받을수 있는 컴포넌트(resolver)가 없어서 발생한 오류입니다!

            아래 구문을 프로젝트 루트 폴더에 metro.config.js 라는 파일을 만들어 붙여넣기하고
            서버를 완전히 껏다가 켜주세요!

            ```jsx
            module.exports = {
                transformer: {
                  getTransformOptions: async () => ({
                    transform: {
                      experimentalImportSupport: false,
                      inlineRequires: false,
                    },
                  }),
                },
                resolver: {
                  sourceExts: ['jsx', 'js', 'ts', 'tsx', 'cjs', 'fs'],
                },
              };
            ```

            ![캡처.PNG](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e2b2f8cc-06f5-4d81-a826-c41fef6cd1f7/캡처.png)

08. [파이어베이스] 파일 스토리지(storage)

  • 파일 저장소 스토리지(storage)

  • [실습 ✍️] 이미지 스토리지에 올려보기

    • 파일 저장소 생성

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e9fa0edb-94aa-4309-bf5d-561f92994b96/Untitled.png

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/6c64c727-7b75-42c6-a1b2-dc196d01bdfa/Untitled.png

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/36edf415-2d02-4bb9-8abc-89091da502ce/Untitled.png

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/cda65f5f-c1fd-477c-95d4-359054a21104/Untitled.png

    • 이미지 올리기

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/9aba3559-9263-43de-aef7-59df17875264/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/9aba3559-9263-43de-aef7-59df17875264/Untitled.png)

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2b4ad1c2-a58c-4372-8de2-e7d6c87ffb0e/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2b4ad1c2-a58c-4372-8de2-e7d6c87ffb0e/Untitled.png)
  • [실습 ✍️]이미지 확인하기

    https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a3d9b6d5-b9d2-4a11-9b97-d969d673ffc8/Untitled.png

09. [파이어베이스] 리얼타임 데이터베이스 - 설정

  • 리얼타임 데이터베이스 소개

    • [🤯firebaseConfig.js 코드 설정시 유의사항!!!]

      최근 파이어베이스에서 프로젝트를 만들면 원래는 설정 코드에 넣어줬던 databaseUrl을 안넣어주고 있습니다. 따라서 다음과 같이 리얼타임 데이터베이스 탭으로 들어가신다음, 데이터베이스 주소를 설정코드에 넣어주세요!

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bcd6b370-62fe-4844-8c1a-c943a9c29791/_2020-12-12__2.14.21.png

        const firebaseConfig = {
          apiKey: "AIzaSyBKG2xY91x23W8PF1231k5OUJ5o9kHSKYQeNWUw",
          authDomain: "sparta-psytest-gun.firebaseapp.com",
          **databaseURL: "https://sparta-psytest-gun.firebaseio.com",**
          projectId: "sparta-psytest-gun",
          storageBucket: "sparta-psytest-gun.appspot.com",
          messagingSenderId: "781790378482",
          appId: "1:78179037128482:web:ddbca5330779f67b947136b",
          measurementId: "G-3F5L9F3340Q3"
        };
      • 이건 제 코드니까 중간에 databaseURL 부분에 여러분들 주소를 넣어야 합니다.!!!!
  • [실습 ✍️] 리얼타임 데이터베이스 생성

    • Realtime Database가 정상적으로 보일 때

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/74e332aa-a82d-49a0-b30f-8bece4366c47/Untitled.png

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2a6ea77b-db27-463e-8275-a020396b1691/Untitled.png

    • Realtime Database가 보일 때

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/35d4a156-2c95-4340-b5c1-cfb559b7236a/Untitled.png

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/56a9c90a-fff2-4d70-ad05-388652c5a33d/Untitled.png

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b26d4fc1-4124-4afa-856d-e7db62409d6b/Untitled.png

<aside>
👉 여기까지 완료되면 준비 끝!

</aside>
  • [실습 ✍️] 데이터 업로드

    • 데이터 업로드

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/1be0d38c-4324-4cf0-b25c-3b860c437597/Untitled.png

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d81039a4-cd05-4a85-aaba-25cffc6ef288/Untitled.png

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d40499b9-126c-421b-b307-678a6d5a96ce/Untitled.png

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d3659590-991e-40b2-abec-08eedfce95c6/Untitled.png

10. [파이어베이스] 리얼타임 데이터베이스 - 전체 데이터 읽기

  • 앱에서 리얼타임 데이터베이스 사용 하기

    • 현재 MainPage.js 코드 리뷰

      • 코드

          import React,{useState,useEffect} from 'react';
          import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';
        
          const main = 'https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png'
          import data from '../data.json';
          import Card from '../components/Card';
          import Loading from '../components/Loading';
          import { StatusBar } from 'expo-status-bar';
          import * as Location from "expo-location";
          import axios from "axios"
        
          export default function MainPage({navigation,route}) {
            //useState 사용법
              //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
            //setState는 state를 변경시킬때 사용해야하는 함수
        
            //모두 다 useState가 선물해줌
            //useState()안에 전달되는 값은 state 초기값
            const [state,setState] = useState([])
            const [cateState,setCateState] = useState([])
            //날씨 데이터 상태관리 상태 생성!
            const [weather, setWeather] = useState({
              temp : 0,
              condition : ''
            })
        
              //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
            //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
            const [ready,setReady] = useState(true)
        
            useEffect(()=>{
              navigation.setOptions({
                title:'나만의 꿀팁'
              })  
                  //뒤의 1000 숫자는 1초를 뜻함
              //1초 뒤에 실행되는 코드들이 담겨 있는 함수
              setTimeout(()=>{
                  //헤더의 타이틀 변경
                  getLocation()
                  setState(data.tip)
                  setCateState(data.tip)
                  setReady(false)
              },1000)
        
        
          },[])

          const getLocation = async () => {
            //수많은 로직중에 에러가 발생하면
            //해당 에러를 포착하여 로직을 멈추고,에러를 해결하기 위한 catch 영역 로직이 실행
            try {
              //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
              await Location.requestForegroundPermissionsAsync();
              const locationData= await Location.getCurrentPositionAsync();
              console.log(locationData)
              console.log(locationData['coords']['latitude'])
              console.log(locationData['coords']['longitude'])
              const latitude = locationData['coords']['latitude']
              const longitude = locationData['coords']['longitude']
              const API_KEY = "cfc258c75e1da2149c33daffd07a911d";
              const result = await axios.get(
                `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
              );

              console.log(result)
              const temp = result.data.main.temp; 
              const condition = result.data.weather[0].main

              console.log(temp)
              console.log(condition)

              //오랜만에 복습해보는 객체 리터럴 방식으로 딕셔너리 구성하기!!
              //잘 기억이 안난다면 1주차 강의 6-5를 다시 복습해보세요!
              setWeather({
                temp,condition
              })

            } catch (error) {
              //혹시나 위치를 못가져올 경우를 대비해서, 안내를 준비합니다
              Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
            }
          }

          const category = (cate) => {
            if(cate == "전체보기"){
                //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
                setCateState(state)
            }else{
                setCateState(state.filter((d)=>{
                    return d.category == cate
                }))
            }
        }

          //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
          // let tip = state.tip;
          let todayWeather = 10 + 17;
          let todayCondition = "흐림"
          //return 구문 밖에서는 슬래시 두개 방식으로 주석
          return ready ? <Loading/> :  (
            /*
              return 구문 안에서는 {슬래시 + * 방식으로 주석
            */

            <ScrollView style={styles.container}>
              <StatusBar style="light" />
              {/* <Text style={styles.title}>나만의 꿀팁</Text> */}
              <Text style={styles.weather}>오늘의 날씨: {weather.temp + '°C   ' + weather.condition} </Text>
               <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}>
                  <Text style={styles.aboutButtonText}>소개 페이지</Text>
                </TouchableOpacity>
              <Image style={styles.mainImage} source={{uri:main}}/>
              <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
              <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
              </ScrollView>
              <View style={styles.cardContainer}>
                 {/* 하나의 카드 영역을 나타내는 View */}
                 {
                  cateState.map((content,i)=>{
                    return (<Card content={content} key={i} navigation={navigation}/>)
                  })
                }

              </View>

            </ScrollView>)
        }

        const styles = StyleSheet.create({
          container: {
            //앱의 배경 색
            backgroundColor: '#fff',
          },
          title: {
            //폰트 사이즈
            fontSize: 20,
            //폰트 두께
            fontWeight: '700',
            //위 공간으로 부터 이격
            marginTop:50,
            //왼쪽 공간으로 부터 이격
            marginLeft:20
          },
        weather:{
            alignSelf:"flex-end",
            paddingRight:20
          },
          mainImage: {
            //컨텐츠의 넓이 값
            width:'90%',
            //컨텐츠의 높이 값
            height:200,
            //컨텐츠의 모서리 구부리기
            borderRadius:10,
            marginTop:20,
            //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능)
            //각 속성의 값들은 공식문서에 고대로~ 나와 있음
            alignSelf:"center"
          },
          middleContainer:{
            marginTop:20,
            marginLeft:10,
            height:60
          },
          middleButtonAll: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#20b2aa",
            borderColor:"deeppink",
            borderRadius:15,
            margin:7
          },
          middleButton01: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#fdc453",
            borderColor:"deeppink",
            borderRadius:15,
            margin:7
          },
          middleButton02: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#fe8d6f",
            borderRadius:15,
            margin:7
          },
          middleButton03: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#9adbc5",
            borderRadius:15,
            margin:7
          },
          middleButton04: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#f886a8",
            borderRadius:15,
            margin:7
          },
          middleButtonText: {
            color:"#fff",
            fontWeight:"700",
            //텍스트의 현재 위치에서의 정렬 
            textAlign:"center"
          },
          middleButtonTextAll: {
            color:"#fff",
            fontWeight:"700",
            //텍스트의 현재 위치에서의 정렬 
            textAlign:"center"
          },
          cardContainer: {
            marginTop:10,
            marginLeft:10
          },
          aboutButton: {
            backgroundColor:"pink",
            width:100,
            height:40,
            borderRadius:10,
            alignSelf:"flex-end",
            marginRight:20,
            marginTop:10
          },
          aboutButtonText: {
            color:"#fff",
            textAlign:"center",
            marginTop:10
          }

        });
        ```

    - 코드 리뷰

        <aside>
        👉 1)
        상단에서 `import data from "../data.json";` 로 필요 데이터를 가져왔습니다.

        2) 
        //꿀팁 데이터를 관리하는 상태입니다.
        `const [state, setState] = useState([])`
        //선택한 카테고리에 맞는 문제 데이터를 저장하고 관리하는 상태입니다.
        `const [cateState, setCateState] = useState([])`

        3)
        `useEffect`에서 데이터를 실제 상태관리 하기 시작합니다.
        useEffect(()=>{
                navigation.setOptions({
                    title:'나만의 꿀팁'
                })
                setTimeout(()=>{
                    getLocation()
                    setState(tip)
                    setCateState(tip)
                    setReady(true)
                },1000)

            },[])

        </aside>


<aside>
👉 파이어베이스를 이용해 전체 데이터를 조회 하는 방법은
전체 데이터를 가져오게끔 해주는 파이어베이스 제공 함수 사용 방법과
가져올 데이터가 어떠한 이름으로 리얼타임 데이터베이스에 저장되어 있는지를 알아야 합니다.

</aside>

- 나만의 꿀팁 저장 위치

    <aside>
    👉 리얼 타임 데이터 베이스를 생성하면서 부터 고유한 DB 주소를 갖게됩니다.

    저의 경우
    `https://sparta-myhoneytip.firebaseio.com/`

    위와 같은 주소에 리얼타임 데이터베이스가 생성되었는데요!
    이 고유 주소에 `tip` 이란 방에 꿀팁들이 저장되었습니다

    `https://sparta-myhoneytip.firebaseio.com/tip`

    </aside>

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b58c684d-92b2-4cde-a8c9-1db9b9b17ebf/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b58c684d-92b2-4cde-a8c9-1db9b9b17ebf/Untitled.png)

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a257ebfa-c33a-4c19-9357-24612c98a49a/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a257ebfa-c33a-4c19-9357-24612c98a49a/Untitled.png)

- 전체 데이터 가져오기 함수

    <aside>
    👉 데이터의 저장 위치를 알았다면, 파이어베이스의 리얼타임 데이터베이스 전용 함수에 데이터 저장 위치를 알려주면서 데이터를 가져올 수 있습니다.

    </aside>

    - 데이터 가져오기 함수
        - 파이어베이스 API 함수는 그럼 어딨는가?

            ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/31b36d2e-f53a-422b-82a0-f5432233f636/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/31b36d2e-f53a-422b-82a0-f5432233f636/Untitled.png)

            ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ab8e5195-a669-4d1c-95a5-af5c86561aad/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ab8e5195-a669-4d1c-95a5-af5c86561aad/Untitled.png)

            ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b8406b07-c976-4a8f-9281-3aaa0a5beb42/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b8406b07-c976-4a8f-9281-3aaa0a5beb42/Untitled.png)


        ```jsx
        firebase_db.ref('/tip').once('value').then((snapshot) => {
           let tip = snapshot.val();
        })
        ```

        <aside>
        👉 `ref('/tip')` 이 부분에서 /tip 영역에 가지고 오고 싶은 데이터의 주소를 넣어주면 됩니다. 이 주소 앞부분에는 `https://sparta-myhoneytip.firebaseio.com/` 과 같은 기본 주소가 생략되어 있습니다.

        `firebaseConfig.js`에서 이미 파이어베이스 계정을 세팅했기 때문에, 기본 주소와 정보들은 앱 내에서 사용하는 파이어베이스 함수들이 알고 있는 상태입니다

        </aside>

        <aside>
        👉 `firebase_db.ref('/tip').once('value').then((snapshot) => {})`

        이 코드는 서버리스를 이용하여 데이터베이스를 조회하기 위해,
        파이어베이스 측에서 정해놓은 API 사용방법입니다. 따라서 우린 공식 문서 그대로 사용 방법을 적용해야 합니다.

        조회한 데이터는 `snapshot` 부분에 담겨서 `{}` 내부에서 사용할 수 있는데, 그 중 실제 우리에게 필요한 데이터는 `snapshot.val()`로 가져와 변수에 담아 사용할 수 있습니다.

        </aside>


<aside>
👉 그럼 사용방법을 알았으니 `MainPage.js`에 적용해보겠습니다

</aside>

- [실습 ✍️] 적용
    - **[코드스니펫] MainPage.js**

        ```jsx
        import React,{useState,useEffect} from 'react';
        import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';

        const main = 'https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png'
        import data from '../data.json';
        import Card from '../components/Card';
        import Loading from '../components/Loading';
        import { StatusBar } from 'expo-status-bar';
        import * as Location from "expo-location";
        import axios from "axios"
        import {firebase_db} from "../firebaseConfig"

        export default function MainPage({navigation,route}) {
          //useState 사용법
            //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
          //setState는 state를 변경시킬때 사용해야하는 함수

          //모두 다 useState가 선물해줌
          //useState()안에 전달되는 값은 state 초기값
          const [state,setState] = useState([])
          const [cateState,setCateState] = useState([])
          //날씨 데이터 상태관리 상태 생성!
          const [weather, setWeather] = useState({
            temp : 0,
            condition : ''
          })

            //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
          //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
          const [ready,setReady] = useState(true)

          useEffect(()=>{
            navigation.setOptions({
              title:'나만의 꿀팁'
            })  
                //뒤의 1000 숫자는 1초를 뜻함
            //1초 뒤에 실행되는 코드들이 담겨 있는 함수
            setTimeout(()=>{
                firebase_db.ref('/tip').once('value').then((snapshot) => {
                  console.log("파이어베이스에서 데이터 가져왔습니다!!")
                  let tip = snapshot.val();

                  setState(tip)
                  setCateState(tip)
                  getLocation()
                  setReady(false)
                });
                // getLocation()
                // setState(data.tip)
                // setCateState(data.tip)
                // setReady(false)
            },1000)


          },[])

          const getLocation = async () => {
            //수많은 로직중에 에러가 발생하면
            //해당 에러를 포착하여 로직을 멈추고,에러를 해결하기 위한 catch 영역 로직이 실행
            try {
              //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
              await Location.requestForegroundPermissionsAsync();
              const locationData= await Location.getCurrentPositionAsync();
              console.log(locationData)
              console.log(locationData['coords']['latitude'])
              console.log(locationData['coords']['longitude'])
              const latitude = locationData['coords']['latitude']
              const longitude = locationData['coords']['longitude']
              const API_KEY = "cfc258c75e1da2149c33daffd07a911d";
              const result = await axios.get(
                `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
              );

              console.log(result)
              const temp = result.data.main.temp; 
              const condition = result.data.weather[0].main

              console.log(temp)
              console.log(condition)

              //오랜만에 복습해보는 객체 리터럴 방식으로 딕셔너리 구성하기!!
              //잘 기억이 안난다면 1주차 강의 6-5를 다시 복습해보세요!
              setWeather({
                temp,condition
              })

            } catch (error) {
              //혹시나 위치를 못가져올 경우를 대비해서, 안내를 준비합니다
              Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
            }
          }

          const category = (cate) => {
            if(cate == "전체보기"){
                //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
                setCateState(state)
            }else{
                setCateState(state.filter((d)=>{
                    return d.category == cate
                }))
            }
        }

          //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
          // let tip = state.tip;
          let todayWeather = 10 + 17;
          let todayCondition = "흐림"
          //return 구문 밖에서는 슬래시 두개 방식으로 주석
          return ready ? <Loading/> :  (
            /*
              return 구문 안에서는 {슬래시 + * 방식으로 주석
            */

            <ScrollView style={styles.container}>
              <StatusBar style="light" />
              {/* <Text style={styles.title}>나만의 꿀팁</Text> */}
              <Text style={styles.weather}>오늘의 날씨: {weather.temp + '°C   ' + weather.condition} </Text>
               <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}>
                  <Text style={styles.aboutButtonText}>소개 페이지</Text>
                </TouchableOpacity>
              <Image style={styles.mainImage} source={{uri:main}}/>
              <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
              <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
                <TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
              </ScrollView>
              <View style={styles.cardContainer}>
                 {/* 하나의 카드 영역을 나타내는 View */}
                 {
                  cateState.map((content,i)=>{
                    return (<Card content={content} key={i} navigation={navigation}/>)
                  })
                }

              </View>

            </ScrollView>)
        }

        const styles = StyleSheet.create({
          container: {
            //앱의 배경 색
            backgroundColor: '#fff',
          },
          title: {
            //폰트 사이즈
            fontSize: 20,
            //폰트 두께
            fontWeight: '700',
            //위 공간으로 부터 이격
            marginTop:50,
            //왼쪽 공간으로 부터 이격
            marginLeft:20
          },
        weather:{
            alignSelf:"flex-end",
            paddingRight:20
          },
          mainImage: {
            //컨텐츠의 넓이 값
            width:'90%',
            //컨텐츠의 높이 값
            height:200,
            //컨텐츠의 모서리 구부리기
            borderRadius:10,
            marginTop:20,
            //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능)
            //각 속성의 값들은 공식문서에 고대로~ 나와 있음
            alignSelf:"center"
          },
          middleContainer:{
            marginTop:20,
            marginLeft:10,
            height:60
          },
          middleButtonAll: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#20b2aa",
            borderColor:"deeppink",
            borderRadius:15,
            margin:7
          },
          middleButton01: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#fdc453",
            borderColor:"deeppink",
            borderRadius:15,
            margin:7
          },
          middleButton02: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#fe8d6f",
            borderRadius:15,
            margin:7
          },
          middleButton03: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#9adbc5",
            borderRadius:15,
            margin:7
          },
          middleButton04: {
            width:100,
            height:50,
            padding:15,
            backgroundColor:"#f886a8",
            borderRadius:15,
            margin:7
          },
          middleButtonText: {
            color:"#fff",
            fontWeight:"700",
            //텍스트의 현재 위치에서의 정렬 
            textAlign:"center"
          },
          middleButtonTextAll: {
            color:"#fff",
            fontWeight:"700",
            //텍스트의 현재 위치에서의 정렬 
            textAlign:"center"
          },
          cardContainer: {
            marginTop:10,
            marginLeft:10
          },
          aboutButton: {
            backgroundColor:"pink",
            width:100,
            height:40,
            borderRadius:10,
            alignSelf:"flex-end",
            marginRight:20,
            marginTop:10
          },
          aboutButtonText: {
            color:"#fff",
            textAlign:"center",
            marginTop:10
          }

        });
        ```

    - 코드 리뷰

        <aside>
        👉 우리가 사용했던 `data.json`과 구조가 완전히 동일한 데이터를 리얼타임 데이터베이스에 업로드했고, 그대로 가져와 사용하고 있으므로

        기존에 관리하던 상태에 파이어베이스 데이터를 넣으면 앱이 정상적으로 동작합니다.
        한 가지 달라진 점이 있다면, useEffect에서 setTimeout 함수를 더이상 사용하지 않는 다는 것입니다.

        </aside>

        ```jsx
        useEffect(()=>{
              //헤더의 타이틀 변경
                navigation.setOptions({
                    title:'나만의 꿀팁'
                })
                firebase_db.ref('/tip').once('value').then((snapshot) => {
                  console.log("파이어베이스에서 데이터 가져왔습니다!!")
                  let tip = snapshot.val();
                  setState(tip)
                  setCateState(tip)
                  getLocation()
                  setReady(false)
                });
                // setTimeout(()=>{
                //     let tip = data.tip;
                //     setState(tip)
                //     setCateState(tip)
                //     getLocation()
                //     setReady(true)
                // },1000)

            },[])
        ```

        <aside>
        💡 사용자들 마다 네트워크 상태가 모두 다를 것이기 때문에, 무조건 몇초 뒤에 실행 시키는 setTimeout 함수 기능보다, 파이어베이스 API의 실행 완료 시간에 맡겨두는 편히 더 나아보입니다. 이러한 이유로 setTimeout 함수를 사용하지 더이상 사용하지 않습니다.

        따라서 파이어베이스로부터 즉, 서버로부터 데이터를 가져와 준비할 때까지 로딩 화면을 보여줬다가, 데이터가 준비가되면 실제 본 화면을 보여주는 자연스러운 서비스 환경을 사용자들에게 제공해줄 수 있습니다.

        </aside>

        ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e063b4c1-75c9-484e-8def-ba81dcce18ec/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e063b4c1-75c9-484e-8def-ba81dcce18ec/Untitled.png)

11. [파이어베이스] 리얼타임 데이터베이스 - 특정 데이터 읽기

  • 특정 데이터 조회하기

    https://s3-us-west-2.amazonaws.com/secure.notion-static.com/88c5c9a5-4c2b-405d-9d0d-708e0c580006/Untitled.png

      //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용
      export default function Card({content,navigation}){
          return(
              //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용
              <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',content)}}>
                  <Image style={styles.cardImage} source={{uri:content.image}}/>
                  <View style={styles.cardText}>
                      <Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
                      <Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
                      <Text style={styles.cardDate}>{content.date}</Text>
                  </View>
              </TouchableOpacity>
          )
      }
    • [실습 ✍️] 적용

      • [코드스니펫] Card.js에서 DetailPage.js 전달 데이터로 idx 넘겨주기

          import React from 'react';
          import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native'
        
          //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용
          export default function Card({content,navigation}){
              return(
                  //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용
                  <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',{idx:content.idx})}}>
                      <Image style={styles.cardImage} source={{uri:content.image}}/>
                      <View style={styles.cardText}>
                          <Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
                          <Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
                          <Text style={styles.cardDate}>{content.date}</Text>
                      </View>
                  </TouchableOpacity>
              )
          }
        
          const styles = StyleSheet.create({
        
              card:{
                flex:1,
                flexDirection:"row",
                margin:10,
                borderBottomWidth:0.5,
                borderBottomColor:"#eee",
                paddingBottom:10
              },
              cardImage: {
                flex:1,
                width:100,
                height:100,
                borderRadius:10,
              },
              cardText: {
                flex:2,
                flexDirection:"column",
                marginLeft:10,
              },
              cardTitle: {
                fontSize:20,
                fontWeight:"700"
              },
              cardDesc: {
                fontSize:15
              },
              cardDate: {
                fontSize:10,
                color:"#A6A6A6",
              }
          });
      • [코드스니펫] DetailPage.js에서 넘겨받은 idx 팁 상세 데이터 조회

          import React,{useState,useEffect} from 'react';
          import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native';
          import * as Linking from 'expo-linking';
          import {firebase_db} from "../firebaseConfig"
        
          export default function DetailPage({navigation,route}) {
        
              const [tip, setTip] = useState({
                  "idx":9,
                  "category":"재테크",
                  "title":"렌탈 서비스 금액 비교해보기",
                  "image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/money1.png",
                  "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ",
                  "date":"2020.09.09"
              })
        
              useEffect(()=>{
                  console.log(route)
                  navigation.setOptions({
                      title:route.params.title,
                      headerStyle: {
                          backgroundColor: '#000',
                          shadowColor: "#000",
                      },
                      headerTintColor: "#fff",
                  })
                  //넘어온 데이터는 route.params에 들어 있습니다.
                  const { idx } = route.params;
                  firebase_db.ref('/tip/'+idx).once('value').then((snapshot) => {
                      let tip = snapshot.val();
                      setTip(tip)
                  });
              },[])
        
              const popup = () => {
                  Alert.alert("팝업!!")
              }
        
              const share = () => {
                  Share.share({
                      message:`${tip.title} \n\n ${tip.desc} \n\n ${tip.image}`,
                  });
              }
        
              const link = () => {
                  Linking.openURL("https://spartacodingclub.kr")
              }
              return ( 
                  // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고
                  // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. 
                  // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. 
                  <ScrollView style={styles.container}>
                      <Image style={styles.image} source={{uri:tip.image}}/>
                      <View style={styles.textContainer}>
                          <Text style={styles.title}>{tip.title}</Text>
                          <Text style={styles.desc}>{tip.desc}</Text>
                          <View style={styles.buttonGroup}>
                              <TouchableOpacity style={styles.button} onPress={()=>popup()}><Text style={styles.buttonText}>팁 찜하기</Text></TouchableOpacity>
                              <TouchableOpacity style={styles.button} onPress={()=>share()}><Text style={styles.buttonText}>팁 공유하기</Text></TouchableOpacity>
                              <TouchableOpacity style={styles.button} onPress={()=>link()}><Text style={styles.buttonText}>외부 링크</Text></TouchableOpacity>
                          </View>
        
                      </View>
        
                  </ScrollView>
        
              )
          }
        
          const styles = StyleSheet.create({
              container:{
                  backgroundColor:"#000"
              },
              image:{
                  height:400,
                  margin:10,
                  marginTop:40,
                  borderRadius:20
              },
              textContainer:{
                  padding:20,
                  justifyContent:'center',
                  alignItems:'center'
              },
              title: {
                  fontSize:20,
                  fontWeight:'700',
                  color:"#eee"
              },
              desc:{
                  marginTop:10,
                  color:"#eee"
              },
              buttonGroup: {
                  flexDirection:"row",
              },
              button:{
                  width:90,
                  marginTop:20,
                  marginRight:10,
                  marginLeft:10,
                  padding:10,
                  borderWidth:1,
                  borderColor:'deeppink',
                  borderRadius:7
              },
              buttonText:{
                  color:'#fff',
                  textAlign:'center'
              }
          })
    <aside>
    👉 기존과 동일한 화면이 나타나고 있나요?
    물론입니다! 우린 동일한 데이터를 이용해서 화면을 나타내고 있으니까요
    그런데 VSCode의 로그를 보면, 서버로부터 데이터를 가지고 오고 있음을 알 수 있습니다.

    </aside>

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/6af7fb96-6345-4c40-a78a-634d0c2642a4/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/6af7fb96-6345-4c40-a78a-634d0c2642a4/Untitled.png)

12. [파이어베이스] 리얼타임 데이터베이스 - 쓰기

  • 나만의 꿀팁 앱에서 데이터를 저장해야하는 상황

  • 필요한 데이터 구조

    • 어떠한 꿀 팁을

    • 누가

        expo install expo-application
        import * as Application from 'expo-application';
        const isIOS = Platform.OS === 'ios';
      
        let uniqueId;
        if(isIOS){
          let iosId = await Application.getIosIdForVendorAsync();
          uniqueId = iosId
        }else{
          uniqueId = Application.androidId
        }
      
        console.log(uniqueId)
    • 실제 디비엔 이렇게 저장이 되어야 할 것 같습니다.

  • [실습 ✍️] 파이어베이스 저장 구현하기

    • 1) 저장하는 시점

      • DetailPage 꿀팁 찜하기 버튼

        https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b970e887-eea0-4c05-b1c0-74aeb8ba4835/Untitled.png

    <aside>
    👉 단순히 팝업이 뜨게끔 간단한 함수가 연결되어 있는데, 이부분에 파이어베이스 기능을 추가하면 될것 같습니다!

    </aside>

- 2) 저장해야하는 데이터

    <aside>
    💡 그럼 우린 어떤 찜 데이터를 데이터베이스에 저장해야할까요?
    여러 방안이 있겠지만, 지금은 간단히 DetailPage에서 idx로 조회한 해당 찜의 전체 데이터를 저장해보기로 합니다!

    해당 찜 데이터는 어디에 위치하고 있죠 지금?

    맞습니다! 

    `const [tip, setTip] = useState()`

    tip 상태에 저장되어 관리 되고 있습니다!
    이 tip을 저장하면 되겠네요!

    </aside>

- 3) onPress에 외부 함수 연결하기
    - **[코드스니펫] DetailPage.js**

        ```jsx
        import React,{useState,useEffect} from 'react';
        import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share,Platform } from 'react-native';
        import * as Linking from 'expo-linking';
        import {firebase_db} from "../firebaseConfig"
        import * as Application from 'expo-application';
        const isIOS = Platform.OS === 'ios';

        export default function DetailPage({navigation,route}) {

            const [tip, setTip] = useState({
                "idx":9,
                "category":"재테크",
                "title":"렌탈 서비스 금액 비교해보기",
                "image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/money1.png",
                "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ",
                "date":"2020.09.09"
            })

            useEffect(()=>{
                console.log(route)
                navigation.setOptions({
                    title:route.params.title,
                    headerStyle: {
                        backgroundColor: '#000',
                        shadowColor: "#000",
                    },
                    headerTintColor: "#fff",
                })
                //넘어온 데이터는 route.params에 들어 있습니다.
                const { idx } = route.params;
                firebase_db.ref('/tip/'+idx).once('value').then((snapshot) => {
                    let tip = snapshot.val();
                    setTip(tip)
                });
            },[])

            const like = async () => {

                // like 방 안에
                // 특정 사용자 방안에
                // 특정 찜 데이터 아이디 방안에
                // 특정 찜 데이터 몽땅 저장!
                // 찜 데이터 방 > 사용자 방 > 어떤 찜인지 아이디
                let userUniqueId;
                if(isIOS){
                let iosId = await Application.getIosIdForVendorAsync();
                    userUniqueId = iosId
                }else{
                    userUniqueId = await Application.androidId
                }

                console.log(userUniqueId)
                   firebase_db.ref('/like/'+userUniqueId+'/'+ tip.idx).set(tip,function(error){
                     console.log(error)
                     Alert.alert("찜 완료!")
                 });
            }

            const share = () => {
                Share.share({
                    message:`${tip.title} \n\n ${tip.desc} \n\n ${tip.image}`,
                });
            }

            const link = () => {
                Linking.openURL("https://spartacodingclub.kr")
            }
            return ( 
                // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고
                // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. 
                // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. 
                <ScrollView style={styles.container}>
                    <Image style={styles.image} source={{uri:tip.image}}/>
                    <View style={styles.textContainer}>
                        <Text style={styles.title}>{tip.title}</Text>
                        <Text style={styles.desc}>{tip.desc}</Text>
                        <View style={styles.buttonGroup}>
                            <TouchableOpacity style={styles.button} onPress={()=>like()}><Text style={styles.buttonText}>팁 찜하기</Text></TouchableOpacity>
                            <TouchableOpacity style={styles.button} onPress={()=>share()}><Text style={styles.buttonText}>팁 공유하기</Text></TouchableOpacity>
                            <TouchableOpacity style={styles.button} onPress={()=>link()}><Text style={styles.buttonText}>외부 링크</Text></TouchableOpacity>
                        </View>

                    </View>

                </ScrollView>

            )
        }

        const styles = StyleSheet.create({
            container:{
                backgroundColor:"#000"
            },
            image:{
                height:400,
                margin:10,
                marginTop:40,
                borderRadius:20
            },
            textContainer:{
                padding:20,
                justifyContent:'center',
                alignItems:'center'
            },
            title: {
                fontSize:20,
                fontWeight:'700',
                color:"#eee"
            },
            desc:{
                marginTop:10,
                color:"#eee"
            },
            buttonGroup: {
                flexDirection:"row",
            },
            button:{
                width:90,
                marginTop:20,
                marginRight:10,
                marginLeft:10,
                padding:10,
                borderWidth:1,
                borderColor:'deeppink',
                borderRadius:7
            },
            buttonText:{
                color:'#fff',
                textAlign:'center'
            }
        })
        ```


    <aside>
    👉 찜 데이터 방 > 사용자 방 > 어떤 찜인지 방 아이디

    그리고 여기에 팁을 저장!

    순으로 차근차근 방에 들어가는 구조입니다.
    그래야 관리가 편하겠죠? 그리고 이렇게 관리하라고 파이어베이스 리얼타임 데이터베이스가 규칙을 정해놨습니다!

    </aside>

    - 코드 리뷰
        - 1) 데이터베이스에 저장 할 딕셔너리

            <aside>
            👉 리얼타임 데이터베이스는 딕셔너리 구조의 데이터, 즉 JSON 형태로 관리 되기 때문에, 우리가 저장할 때도 이에 맞게 딕셔너리 구조로 저장해야합니다.

            그런데 이미 Detailpage tip 상태에서 idx로 조회한 찜 데이터, 즉 딕셔너리 데이터를 저장관리하고 있죠?

            </aside>

            ```jsx

            const [tip, setTip] = useState({
                    "idx":9,
                    "category":"재테크",
                    "title":"렌탈 서비스 금액 비교해보기",
                    "image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/money1.png",
                    "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ",
                    "date":"2020.09.09"
                })

            //넘어온 데이터는 route.params에 들어 있습니다.
                    const { idx } = route.params;
                    firebase_db.ref('/tip/'+idx).once('value').then((snapshot) => {
                        let tip = snapshot.val();
                        setTip(tip)
                    });
            ```

        - 2) 사용자 고유아이디 생성

            <aside>
            👉 위에서 설치한 `expo-application` 를 이용해 사용자 고유 아이디를 생성합니다.

            </aside>

            ```jsx
            import * as Application from 'expo-application';
            const isIOS = Platform.OS === 'ios';
            ```

        - 3) 리얼타임 데이터베이스 저장 함수

            <aside>
            👉 파이어베이스 공식 문서 사용법 그대로
            리얼타임 데이터 베이스에 저장하게끔 해주는 함수를 사용합니다.

            </aside>

            ```jsx
            const like = async () => {

                    // like 방 안에
                    // 특정 사용자 방안에
                    // 특정 찜 데이터 아이디 방안에
                    // 특정 찜 데이터 몽땅 저장!
                    // 찜 데이터 방 > 사용자 방 > 어떤 찜인지 아이디
                    let userUniqueId;
                    if(isIOS){
                    let iosId = await Application.getIosIdForVendorAsync();
                        userUniqueId = iosId
                    }else{
                        userUniqueId = await Application.androidId
                    }

                    console.log(userUniqueId)
                    firebase_db.ref('/like/'+userUniqueId+'/'+ tip.idx).set(tip,function(error){
                        console.log(error)
                        Alert.alert("찜 완료!")
                    });
                }
            ```

            <aside>
            👉 `'/like/'+user_id+'/'+ tip.idx`  이 부분은 데이터가 저장될 경로를 결정합니다.

            `.set()` 부분 첫 번째 인자로는, 저장할 데이터(`tip 상태`)를 두 번째 인자로는 혹시 에러가 발생하면 나중에 처리 할 함수를 둡니다.

            </aside>

- 4) 저장이 제대로 됐는 지 확인해보기


    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b414d6fe-3134-4653-ad18-1e780026edab/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b414d6fe-3134-4653-ad18-1e780026edab/Untitled.png)

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/4788419f-7f3c-45f0-b745-93ef34230f88/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/4788419f-7f3c-45f0-b745-93ef34230f88/Untitled.png)

13. 4주차 끝 & 숙제 설명

  • 숙제 1: LikePage에 찜데이터 모두 보여주기!

      firebase_db.ref('/tip').once('value').then((snapshot) => {
                console.log("파이어베이스에서 데이터 가져왔습니다!!")
                let tip = snapshot.val();
      })
  • 숙제 2: 그런데 찜한 데이터가 없다? 데이터가 없을때 조회하려는 에러를 처리!

    
              firebase_db.ref('/like/'+userUniqueId).once('value').then((snapshot) => {
                  console.log("파이어베이스에서 데이터 가져왔습니다!!")
                  let tip = snapshot.val();
                  setTip(tip)
                  setReady(false)
    
      })
      firebase_db.ref('/like/'+userUniqueId).once('value').then((snapshot) => {
                  console.log("파이어베이스에서 데이터 가져왔습니다!!")
                  let tip = snapshot.val();
    
                  if(tip.length){
                                      setTip(tip)
                      setReady(false)
                  }
    
       })
    • 안되시는 분!

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/673eb475-95f1-46e0-8a22-875570b8ce3b/_2020-09-27__6.29.12.png

  • 숙제 3: LikeCard에서 받은 버튼 두개 만들기

    https://s3-us-west-2.amazonaws.com/secure.notion-static.com/9c6ced9d-232c-479c-bd93-27f695e236ec/Untitled.png

  • 숙제 4: 자세히 보기 누르면 DetailPage로,

  • 숙제 5: 찜 해제 누르면 찜 삭제!

    • 참고 공식문서

    • 참고 스택오버플로우(미국 지식인)

<aside>
💡 순서는 이렇게 정리해서 생각하시면 편합니다!

1) 찜 해제 버튼에 remove 함수를 만들어 연결합니다
2) 파이어베이스 삭제 함수에도 경로를 넣어야 하는데, 우리가 배웠던 것 고대로 어떤걸 지울지 경로를 자세히 써워야해요 이렇게! '/like/'+userUniqueId+'/'+content.idx
3) 저장이 완료됐다! 그러면 새로고침 격으로 `navigation.navigate('LikePage')`  를 써서 LikePage로 다시오면 사라진 데이터를 보게됩니다!

</aside>

- 안 되시는 분!

    <aside>
    💡 앗 그런데 `navigation.navigate('LikePage')` 코드로 페이지 리프레시가 안된다구요? 안되는 분들도 계십니다! 리액트와 navigation 버전문제로 환경에 따라 작동 방식이 다를 수 있는데요!

    그땐 이렇게 해보세요!

    1) LikePage에 reload 함수를 만든다
    2) 해당 함수에선 LikePage useEffect에 작성한 코드와 동인한 코드가 존재한다
    3) 이유는 LikePage 상태를 변경시켜서 페이지 리프레쉬를 의도했기 때문!
    4) 이 reload 함수를 LikeCard로 넘긴다
    5) 삭제 할 때 해당 함수를 실행하면서 상태를 변경시켜 데이터를 리로드 시킨다!

    </aside>
  • 애드몹 가입

    • [실습 ✍️] 가입 절차
    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/f5054ad9-9652-4fda-9705-3c1739b36a6f/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/f5054ad9-9652-4fda-9705-3c1739b36a6f/Untitled.png)

    <aside>
    👉 아래 팁은 원하는 대로! 설정 가능합니다

    </aside>

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/adcf811b-394e-48d1-89fe-4e2a30daea6d/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/adcf811b-394e-48d1-89fe-4e2a30daea6d/Untitled.png)
  • 애드몹 앱 생성

    • [실습 ✍️] 절차
    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c1989853-df16-4df7-a4e5-eb9262befa79/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c1989853-df16-4df7-a4e5-eb9262befa79/Untitled.png)

    <aside>
    👉 안드로이드 iOS 모두 앱 생성이 동일합니다.

    </aside>

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fba807c1-60c8-4bab-9a47-b98b1d0bd400/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fba807c1-60c8-4bab-9a47-b98b1d0bd400/Untitled.png)

    <aside>
    👉 앱 생성 후 알려주는 앱 ID의 경우 우리가 적용할 내용이지만, 광고 단위를 생성하면서 또 만나기 때문에 지금은 일단 나중에 하기 버튼을 눌러주세요!

    </aside>

    ![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2776791d-909a-4f3b-9394-cfbb24577ffe/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2776791d-909a-4f3b-9394-cfbb24577ffe/Untitled.png)
  • 필수 인적사항 기입
![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/f52cb530-b4b2-45cd-b31b-79fe9154e701/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/f52cb530-b4b2-45cd-b31b-79fe9154e701/Untitled.png)

<aside>
👉 지급 설정 클릭 후 개인정보 입력 페이지에서 인적사항 기입.
완료!

</aside>

![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/0764935f-5b82-4802-a3fa-cdbe12159eea/Untitled.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/0764935f-5b82-4802-a3fa-cdbe12159eea/Untitled.png)

HW. 4주차 숙제 해설

  • 숙제 1: LikePage.js

      import React,{useState, useEffect} from 'react';
      import {ScrollView, Text, StyleSheet} from 'react-native';
      import LikeCard from '../components/LikeCard';
      import Card from '../components/Card';
      import * as Application from 'expo-application';
      const isIOS = Platform.OS === 'ios';
      import {firebase_db} from "../firebaseConfig"
    
      export default function LikePage({navigation,route}){
    
          const [tip, setTip] = useState([])
    
          useEffect(()=>{
              navigation.setOptions({
                  title:'꿀팁 찜'
              })
              getLike()
          },[])
    
          const getLike = async () => {
              let userUniqueId;
              if(isIOS){
              let iosId = await Application.getIosIdForVendorAsync();
                  userUniqueId = iosId
              }else{
                  userUniqueId = await Application.androidId
              }
    
              console.log(userUniqueId)
              firebase_db.ref('/like/'+userUniqueId).once('value').then((snapshot) => {
                  console.log("파이어베이스에서 데이터 가져왔습니다!!")
                  let tip = snapshot.val();
                  setTip(tip)
              })
          }
    
          return (
              <ScrollView style={styles.container}>
                 {
                     tip.map((content,i)=>{
                         return(<LikeCard key={i} content={content} navigation={navigation}/>)
                     })
                 }
              </ScrollView>
          )
      }
    
      const styles = StyleSheet.create({
          container:{
              backgroundColor:"#fff"
          }
      })
  • 숙제 2: LikePage.js

      import React,{useState, useEffect} from 'react';
      import {ScrollView, Text, StyleSheet} from 'react-native';
      import LikeCard from '../components/LikeCard';
      import Loading from '../components/Loading';
      import * as Application from 'expo-application';
      const isIOS = Platform.OS === 'ios';
      import {firebase_db} from "../firebaseConfig"
    
      export default function LikePage({navigation,route}){
    
          const [tip, setTip] = useState([])
          const [ready,setReady] = useState(true)
    
          useEffect(()=>{
              navigation.setOptions({
                  title:'꿀팁 찜'
              })
              getLike()
          },[])
    
          const getLike = async () => {
              let userUniqueId;
              if(isIOS){
              let iosId = await Application.getIosIdForVendorAsync();
                  userUniqueId = iosId
              }else{
                  userUniqueId = await Application.androidId
              }
    
              console.log(userUniqueId)
              firebase_db.ref('/like/'+userUniqueId).once('value').then((snapshot) => {
                  console.log("파이어베이스에서 데이터 가져왔습니다!!")
                  let tip = snapshot.val();
                              // tip이 null도 아니고(실제 값이 존재 하고)
                              // tip의 갯수가 0개 이상! 즉 있을때만 상태 변경하여 화면을 다시 그리기!
                  if(tip && tip.length > 0){
                      setTip(tip)
                      setReady(false)
                  }
    
              })
          }
    
          return (
              <ScrollView style={styles.container}>
                 {
                     tip.map((content,i)=>{
                         return(<LikeCard key={i} content={content} navigation={navigation}/>)
                     })
                 }
              </ScrollView>
          )
      }
    
      const styles = StyleSheet.create({
          container:{
              backgroundColor:"#fff"
          }
      })
  • 숙제 2번 안 되는 분: LikePage.js

      import React,{useState, useEffect} from 'react';
      import {ScrollView, Text, StyleSheet} from 'react-native';
      import LikeCard from '../components/LikeCard';
      import Loading from '../components/Loading';
      import * as Application from 'expo-application';
      const isIOS = Platform.OS === 'ios';
      import {firebase_db} from "../firebaseConfig"
    
      export default function LikePage({navigation,route}){
    
          const [tip, setTip] = useState([])
          const [ready,setReady] = useState(true)
    
          useEffect(()=>{
              navigation.setOptions({
                  title:'꿀팁 찜'
              })
              getLike()
          },[])
    
          const getLike = async () => {
              let userUniqueId;
              if(isIOS){
              let iosId = await Application.getIosIdForVendorAsync();
                  userUniqueId = iosId
              }else{
                  userUniqueId = await Application.androidId
              }
    
              console.log(userUniqueId)
              firebase_db.ref('/like/'+userUniqueId).once('value').then((snapshot) => {
                  console.log("파이어베이스에서 데이터 가져왔습니다!!")
                  let tip = snapshot.val();
                  let tip_list = Object.values(tip)
                  if(tip_list && tip_list.length > 0){
                      setTip(tip_list)
                      setReady(false)
                  }
    
              })
          }
    
          return (
              <ScrollView style={styles.container}>
                 {
                     tip.map((content,i)=>{
                         return(<LikeCard key={i} content={content} navigation={navigation}/>)
                     })
                 }
              </ScrollView>
          )
      }
    
      const styles = StyleSheet.create({
          container:{
              backgroundColor:"#fff"
          }
      })
  • 숙제 3~4: LikeCard.js

      import React from 'react';
      import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native'
    
      //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용
      export default function LikeCard({content,navigation}){
    
          const detail = () => {
              navigation.navigate('DetailPage',{idx:content.idx})
          }
    
          const remove = () => {
    
          }
          return(
              //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용
              <View style={styles.card}>
                  <Image style={styles.cardImage} source={{uri:content.image}}/>
                  <View style={styles.cardText}>
                      <Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
                      <Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
                      <Text style={styles.cardDate}>{content.date}</Text>
    
                      <View style={styles.buttonGroup}>
                          <TouchableOpacity style={styles.button} onPress={()=>detail()}><Text style={styles.buttonText}>자세히보기</Text></TouchableOpacity>
                          <TouchableOpacity style={styles.button} onPress={()=>remove()}><Text style={styles.buttonText}>찜 해제</Text></TouchableOpacity>
    
                      </View>
                  </View>
              </View>
          )
      }
    
      const styles = StyleSheet.create({
    
          card:{
            flex:1,
            flexDirection:"row",
            margin:10,
            borderBottomWidth:0.5,
            borderBottomColor:"#eee",
            paddingBottom:10
          },
          cardImage: {
            flex:1,
            width:100,
            height:100,
            borderRadius:10,
          },
          cardText: {
            flex:2,
            flexDirection:"column",
            marginLeft:10,
          },
          cardTitle: {
            fontSize:20,
            fontWeight:"700"
          },
          cardDesc: {
            fontSize:15
          },
          cardDate: {
            fontSize:10,
            color:"#A6A6A6",
          },
          buttonGroup: {
              flexDirection:"row",
          },
          button:{
              width:90,
              marginTop:20,
              marginRight:10,
              marginLeft:10,
              padding:10,
              borderWidth:1,
              borderColor:'deeppink',
              borderRadius:7
          },
          buttonText:{
              color:'deeppink',
              textAlign:'center'
          }
      });
  • 숙제 5: LikePage.js

      import React,{useState, useEffect} from 'react';
      import {ScrollView, Text, StyleSheet,Platform} from 'react-native';
      import LikeCard from '../components/LikeCard';
      import Loading from '../components/Loading';
      import * as Application from 'expo-application';
      const isIOS = Platform.OS === 'ios';
      import {firebase_db} from "../firebaseConfig"
    
      export default function LikePage({navigation,route}){
    
          const [tip, setTip] = useState([])
          const [ready,setReady] = useState(true)
    
          useEffect(()=>{
              navigation.setOptions({
                  title:'꿀팁 찜'
              })
              getLike()
          },[])
    
          const getLike = async () => {
              let userUniqueId;
              if(isIOS){
              let iosId = await Application.getIosIdForVendorAsync();
                  userUniqueId = iosId
              }else{
                  userUniqueId = await Application.androidId
              }
    
              console.log(userUniqueId)
              firebase_db.ref('/like/'+userUniqueId).once('value').then((snapshot) => {
                  console.log("파이어베이스에서 데이터 가져왔습니다!!")
                  let tip = snapshot.val();
                  let tip_list = Object.values(tip)
                  if(tip_list && tip_list.length > 0){
                      setTip(tip_list)
                      setReady(false)
                  }
    
              })
          }
    
          return (
              <ScrollView style={styles.container}>
                 {
                     tip.map((content,i)=>{
                         // LikeCard에서 꿀팀 상태 데이터(==tip)과 꿀팁 상태 데이터를 변경하기 위한
                         // 상태 변경 함수(== setTip)을 건네준다.
                         //즉 자기 자신이 아닌, 자식 컴포넌트에서도 부모의 상태를 변경할 수 있다.
                         return(<LikeCard key={i} content={content} navigation={navigation} tip={tip} setTip={setTip}/>)
                     })
                 }
              </ScrollView>
          )
      }
    
      const styles = StyleSheet.create({
          container:{
              backgroundColor:"#fff"
          }
      })
  • 숙제 5: LikeCard.js

      import React from 'react';
      import {Alert,View, Image, Text, StyleSheet,TouchableOpacity,Platform} from 'react-native'
      import {firebase_db} from "../firebaseConfig"
      const isIOS = Platform.OS === 'ios';
      import * as Application from 'expo-application';
      //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용
      export default function LikeCard({content,navigation,tip, setTip}){
    
          const detail = () => {
              navigation.navigate('DetailPage',{idx:content.idx})
          }
    
          const remove = async (cidx) => {
            let userUniqueId;
            if(isIOS){
            let iosId = await Application.getIosIdForVendorAsync();
                userUniqueId = iosId
            }else{
                userUniqueId = await Application.androidId
            }
    
            console.log(userUniqueId)
            firebase_db.ref('/like/'+userUniqueId+'/'+cidx).remove().then(function(){
              Alert.alert("삭제 완료");
              //내가 찝 해제 버튼을 누른 카드 idx를 가지고
              //찝페이지의 찜데이터를 조회해서
              //찜해제를 원하는 카드를 제외한 새로운 찜 데이터(리스트 형태!)를 만든다
              let result = tip.filter((data,i)=>{
                return data.idx !== cidx
              })
              //이렇게 만들었으면!
              //LikePage로 부터 넘겨 받은 tip(찜 상태 데이터)를
              //filter 함수로 새롭게 만든 찜 데이터를 구성한다!
              console.log(result)
              setTip(result)
    
            })
    
          }
    
          return(
              //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용
              <View style={styles.card}>
                  <Image style={styles.cardImage} source={{uri:content.image}}/>
                  <View style={styles.cardText}>
                      <Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
                      <Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
                      <Text style={styles.cardDate}>{content.date}</Text>
    
                      <View style={styles.buttonGroup}>
                          <TouchableOpacity style={styles.button} onPress={()=>detail()}><Text style={styles.buttonText}>자세히보기</Text></TouchableOpacity>
                          <TouchableOpacity style={styles.button} onPress={()=>remove(content.idx)}><Text style={styles.buttonText}>찜 해제</Text></TouchableOpacity>
    
                      </View>
                  </View>
              </View>
          )
      }
    
      const styles = StyleSheet.create({
    
          card:{
            flex:1,
            flexDirection:"row",
            margin:10,
            borderBottomWidth:0.5,
            borderBottomColor:"#eee",
            paddingBottom:10
          },
          cardImage: {
            flex:1,
            width:100,
            height:100,
            borderRadius:10,
          },
          cardText: {
            flex:2,
            flexDirection:"column",
            marginLeft:10,
          },
          cardTitle: {
            fontSize:20,
            fontWeight:"700"
          },
          cardDesc: {
            fontSize:15
          },
          cardDate: {
            fontSize:10,
            color:"#A6A6A6",
          },
          buttonGroup: {
              flexDirection:"row",
          },
          button:{
              width:90,
              marginTop:20,
              marginRight:10,
              marginLeft:10,
              padding:10,
              borderWidth:1,
              borderColor:'deeppink',
              borderRadius:7
          },
          buttonText:{
              color:'deeppink',
              textAlign:'center'
          }
      });

+ Recent posts