본문 바로가기

코딩 개발일지

[개발일지]스파르타코딩클럽_앱개발 종합반 4주차 숙제

앱개발 종합반 4주차 숙제 

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

//기존 LikePage

import
React,{useState, useEffect} from 'react';
import {ScrollView, Text, StyleSheet} from 'react-native';
import LikeCard from '../components/LikeCard';
import Card from '../components/Card';

export default function LikePage({navigation,route}){
   
    const [tip, setTip] = useState([{
            "idx":3,
            "category":"재테크",
            "title":"잠자는 내 돈을 찾아라",
            "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.",
            "date":"2020.09.09"
        },
        {
            "idx":4,
            "category":"재테크",
            "title":"할인행사, 한정할인판매 문구의 함정 탈출!",
            "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ",
            "date":"2020.09.09"
    }])

    useEffect(()=>{
        navigation.setOptions({
            title:'꿀팁 찜'
        })
    })

    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를 만들면 될것이다.

지금 이상태에선 무조건 useState의 기본 정보값이 출력될테니 useState 정보를 지우고

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

이제 생각해보자.. setReady도 만들어야 할거같지만 그게 숙제2라고 하니까 일단 숙제 1부터.

 

지금 상태 tip안에 아무것도 없으므로 tip 안에 데이터를 넣어줘야 한다. 무슨 데이터? 데이터베이스 Like안에 들어있는 것들을 tip에 넣어주면 된다.

 

앞서 MainPage.js에서 썻던 전체 tip 데이터를 가져왔던 코드를 활용하면 될것 같은데

//MainPage 코드
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)
  },[])

이 코드는 전체 /tip 데이터를 가져오는 것이었고 우리는 /like 데이터를 가져와야 한다.

추가로 특정한 사용자가 '찜'한 데이터를 가져오는것이기 때문에  /like/userUniqueId 주소로 가져와야한다

firebase_db.ref('/like/'+userUniqueId).once...아 그런데 이걸 사용하려면 userUniqueId가 필요하다.

 

//여기서 잠깐. 앞에 DetailPage에서 분명 userUniqueId를 얻어냈었는데 거기서 얻은 값을 여기로 가져올수가 없나? 네비게이션 함수를 써서 userUniqueId를 어떻게든 옮겨서 여기서도 쓸수 있을거 같긴 한데.. 다만 그렇게 하려면 사용자가 DetailPage로 먼저 들어가서 찜하기를 눌렀을때만 userUniqueId를 얻을수 있고, 처음부터 바로 LikePage로 가면 userUniqueId를 얻을수가 없기때문에 안되는걸까? 아닌거같기도하고,,

 

 아무튼 userUniqueId를 얻기위해선 DetailPage에서 찜한 데이터를 저장할때 사용했던 코드를 사용하면 될것같다.

//DetailPage에서 유저id를 얻고, like폴더+유저id+tip 을 저장하는 코드
const
like = 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+'/'+ tip.idx).set(tip,function(error){
             console.log(error)
             Alert.alert("찜 완료!")
         });
    }

이 코드들을 사용하려면 먼저 임포트 해줘야 할것들이 있다.

import {firebase_db} from "../firebaseConfig"
const isIOS = Platform.OS === 'ios';
import * as Application from 'expo-application';

잊지말고 임포트 추가하자. const like를 getLike로 바꾸고 파이어베이스에서 데이터를 저장하는 코드에서 데이터를 가져오는 코드로 바꿔준다

useEffect(()=>{
        navigation.setOptions({
            title:'꿀팁 찜'
          })
        getLike()
      },[])

    const getLike = 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).once('value').then((snapshot) => {
            console.log("파이어베이스에서 like데이터 가져왔습니다!!")
            let tip = snapshot.val();
            setTip(tip)
         });
    }

이 상태로 돌린다고 했을때 useEffect는 화면이 그려지고나서 실행되는거니까 넘어가고, 바로 getLike 함수부분이 돌아가면서 like데이터가 let tip 안에 들어가게되고, setTip(tip으로 상태tip안에 들어간 like데이터가 아래 tip.map을 통해서 스크롤뷰화면에 출력되게 된다.

//다만 이 상태는 찜한 데이터가 없다면 분명 에러가 발생할 상태라서 그다음 숙제가 있다.

 

숙제 2: 그런데 찜한 데이터가 없다? 데이터가 없을때 조회하려는 에러를 처리!

강의노트에 친절하게 setReady(false)를 추가하라는 말이 있으므로 const [ready, setReady] = useState([true])

해주고 이렇게 힌트가 있는데,,

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

            if(tip.length){
                setTip(tip)
                setReady(false)
            }
        });

만약 tip.length가 true면 setTip(tip) 이라는건가? 잘 모르겠는데 이상태로 돌리면 똑같이 화면이 출력된다. 강제로 에러 상황을 만들어야 하는데 아직 찜 데이터 삭제 기능은 없으므로, 파이어베이스 데이터베이스로 들어가서 Like폴더를 삭제하고나서 다시 눌러보니 에러가 발생한다.

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

           
            if(tip && tip.length > 0){
                setTip(tip)
                setReady(false)
            }
        });

어찌할바를 몰라서 정답 코드를 가져와봤다.  && and연산자로 둘다 true이고 0보다 커야 한다.

// tip이 null도 아니고(실제 값이 존재 하고)

// tip의 갯수가 0개 이상! 즉 있을때만 상태 변경하여 화면을 다시 그리기!

음.. 그렇담 이 상태에서 tip에 아무런 데이터가 없으면 어떻게 되는거지? 에러가 안뜨나?

-> 에러가 안뜨고 아무런 데이터 없이 상단에 title과 뒤로가기 버튼만 나온다 (신기하네)

//숙제2번 오류나는분을 위한 답
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)
    }

위의 코드는 1위에 코드대로 돌렸을때 안되는분을 위한 코드라는데 난 오히려 이 코드를 넣고 돌리면 노란색으로 무슨 에러 메세지가 뜬다. 실행은 되긴 하는데..

 

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

간단한 숙제라 다행이다. DetailPage에서 썻던 코드를 그대로 쓰면 될것 같다.

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

여기서, 숙제 3은 onPress 기능을 빼버리고 뒤에 텍스트만 교체하면 된다. 돌려보니 텍스트가 흰색이라 보이질 않는다

buttonText:{
      fontSize:13,
      color:'deeppink',
      textAlign:'center'
  }
이렇게 바꿔주니 정상적으로 보인다. fontSize도 기본이 15인것 같은데 내 폰에선 자세히 보기버튼이 두줄로 나오길래 폰트 사이즈를 바꾸니 비슷하게 나온다.
//그런데 뭘 건드렸는지 하다보니 꿀팁 찜 페이지에 아무런 데이터가 안뜨고 흰 화면만 나온다.. 숙제2번 오류나는분을 위한 코드를 적용하니 화면이 뜬다. (대체 뭐지)

 

 

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

onPress 기능을 넣는데, 눌렀을때 디테일페이지로 가야한다. 네비게이션 함수를 써야 하는데, 그냥 디테일페이지로만 가는게 아니고 특정 idx의 디테일페이지로 이동해야한다.

MainPage에서 버튼을 눌러서 페이지를 이동하는 코드가 있는데

<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
여기선 따로 데이터 없이 페이지만 이동하고 있는데 이렇게 데이터도 함께 전달해주면 될것같다.
 
<TouchableOpacity style={styles.button} onPress={()=>{navigation.navigate('DetailPage',{idx:content.idx})}}><Text style={styles.buttonText}>자세히보기</Text></TouchableOpacity>
 
 
숙제 5: 찜 해제 누르면 찜 삭제!
 
하 이건 감이 안오는데, 일단 찜 해제 버튼을 눌렀을때 onPress기능으로 delete 기능이 실행되도록 코드를 짜야할것 같다. 기존에 저장하던 방식대로 userUniqueId를 가져와서 파이어베이스 로 삭제하는데, 공식문서에 삭제하는 기능이 어딘가 적혀있겠지? 찾아보니 공식문서에 이렇게 적혀있다.
var adaRef = firebase.database().ref('users/ada');
adaRef.remove()
  .then(function() {
    console.log("Remove succeeded.")
  })
  .catch(function(error) {
    console.log("Remove failed: " + error.message)
  });

음... 일단 잠시 뒤에 생각하고 순서대로 만들어보자

찜해제 버튼을 눌러야 하니까 LikeCard.js에서 버튼을 눌렀을때 remove 함수가 실행되도록 해야한다

<TouchableOpacity style={styles.button} onPress={()=>remove()}><Text style={styles.buttonText}>찜 해제</Text></TouchableOpacity>
 아, 버튼을 눌렀을때 삭제할 데이터도 함께 전달되야 한다 Like 폴더에 가장 마지막 번호폴더를 삭제해야하는데 그 번호는 idx와 같게 설정되어 있으니 idx값과 함께 넘긴다
<TouchableOpacity style={styles.button} onPress={()=>remove(content.idx)}><Text style={styles.buttonText}>찜 해제</Text></TouchableOpacity>
 
이렇게 되면 const remove해서 함수를 또 한창 만들어야한다. 어쩔수 없나?
DetailPage에서 찜하던 절차를 그대로 복사해와서 수정하면 될것같다.
 
//DetailPage 찜하기 코드
const
like = 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+'/'+ tip.idx).set(tip,function(error){
             console.log(error)
             Alert.alert("찜 완료!")
         });
    }
이걸 쓰려면 맨 위에 import해야할것들을 잊지말고 해주고
그리고 firebase_db 부분부터 한번 고민해보자... 공식문서는 뭔말인지 이해가 잘 안되서
미국지식인 이라는 스택오버플로우 링크를 가보니 
dmin.ref(`/users/${userid}`).remove()
 
대충 이렇게 쓰라는 말이 있다. 음.. 열심히 고민했으나 답답해서 정답을 가지고 왔다.
 
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'
    }
});
 
이 LikeCard.js를 적용해서 직접 돌려보니까 동작이 되긴 하는데,
찜 해제 버튼을 누르면 즉시 꿀팀찜 페이지에서 즉시 적용되지 않고 뒤로가기를 하고 오면 적용이 된다, 그리고 찜 해제를 누르면 노란색으로 console Warning 에러팝업이 뜨는데,정확한 이유는 잘 모르겠다. 해당 내용이 즉시 적용이 안되서 그런걸까?
 
정답에는 LikePage 코드도 함께 있다.
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"
    }
})
LikePage 코드를 적용하니까 위에서 발생했던 찜 해제 버튼을 눌러도 즉시 해제가 안되는 에러가 해결됬다.
노란 에러창도 더이상 뜨지 않는다. 전/후 차이점으론
//이전 코드
<
ScrollView style={styles.container}>
           {       tip.map((content,i)=>{
                   return(<LikeCard key={i} content={content} navigation={navigation}/>)})
           }
        </ScrollView>

스크롤뷰 영역에서 상태 데이터를 넘겨주는 부분이 추가됬다. 사실 지금 잘 이해는 안되지만, 상태 데이터가 최신으로 관리된다는것 같다.

------------------------

알수없는 에러가 뜨면.. 정말 쉽지 않다