개발 일지✨

앱개발 4주차 과제

하루 2022. 8. 24. 00:34

구현해야할 기능과 가이드

1. LikePage 에 찜 데이터 모두 보여주기

LikePage 를 숙제로 구현 완료했다면, 찜 구현을 통해 저장한 찜 데이터 목록을 가져올 수 있다.

firebase_db.ref('/tip').once('value').then((snapshot) => {
  console.log("파이어베이스에서 데이터 가져왔습니다!!")
  let tip = snapshot.val();
})

파이어베이스에서 데이터를 가져올 때 이런식으로 가져왔었다. 

이제 찜한 데이터를 LikePage 에서 보여려고 한다면 /tip 부분이 /like 가 되어야 한다.

그리고 /like/userUniqueId 가 되어야 한다. 사용자마다 다르기 때문에!

 

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)
    }
 })

이렇게 길이값을 확인하면 데이터가 없을 때, 0 일 때는 실행이 안되고, 데이터가 들어있는 상태면 길이 값을 확인하고 준비상태를 바꾸도록 해주면 된다!

🚨 여기서 또 오류! tip 이 리스트 형태가 아니라 딕셔너리 안에 딕셔너리들이 존재하는 형태라서 오류가 뜰 수 있다.

{
 1:  {},
 2: {},
 3: {},
}

이럴땐 key 에 물려있는 값으로만 리스트를 새롭게 만드는 object.value() 라는 것을 이용해보자!

 

3. LikeCard 에서 받은 버튼 두개 만들기

 

4. 자세히보기 누르면 DetailPage 로 이동하기

Card.js 에서 navigation.navigate('DetailPage', {idx: content.idx}) 이런식으로 디테일 페이지로 갈 때 idx 를 넘겨줬듯이 LikePage도 동일하게 처리해주면된다.

 

5. 찜 해제 누르면 찜 삭제

파이어베이스 리얼타임 데이터베이스 삭제 관련 공식문서

참고 스택오버플로우 

공식문서를 참고해서 찜 해제 버튼을 눌렀을 때, 사용자가 누른 찜 목록에서 삭제되는 기능을 구현해보자.

순서 정리

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

🚨 navigation.navigate('LikePage') 코드로 페이지 리프레시가 안되는 경우

리액트와 navigation 버전문제로 환경에 따라 작동 방식이 다를 수 있다.

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

 

구현

1. LikePage 에 찜 데이터 모두 보여주기

// 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"
    }
})
// LikePage.js 방안2
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. LikeCard 에서 받은 버튼 두개 만들기

4. 자세히보기 누르면 DetailPage 로 이동하기

// 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"
    }
})
// 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'
    }
});