앱개발 4주차 과제
구현해야할 기능과 가이드
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. 찜 해제 누르면 찜 삭제
공식문서를 참고해서 찜 해제 버튼을 눌렀을 때, 사용자가 누른 찜 목록에서 삭제되는 기능을 구현해보자.
순서 정리
- 찜 해제 버튼에 remove 함수를 만들어 연결한다.
- 파이어베이스 삭제 함수에도 경로를 넣어줘야 하는데, 우리가 배웠던 그대로 어떤걸 지울지 경로를 자세히 써야 한다. '/like/'+userUniqueId+'/'+content.idx 이런식으로!
- 저장이 완료됐다! 그러면 새로고침 격으로 navigation.navigate('LikePage') 를 써서 LikePage 로 다시오면 사라진 데이터를 보게 된다.
🚨 navigation.navigate('LikePage') 코드로 페이지 리프레시가 안되는 경우
리액트와 navigation 버전문제로 환경에 따라 작동 방식이 다를 수 있다.
- LikePage 에 reload 함수를 만든다.
- 해당 함수에선 LikePage useEffect에 작성한 코드와 동일한 코드가 존재한다.
- 이유는 LikePage 상태를 변경시켜서 페이지 리프레쉬를 의도했기 때문!
- 이 reload 함수를 LikeCard 로 넘긴다.
- 삭제할 때 해당 함수를 실행하면서 상태를 변경시켜 데이터를 리로드 시킨다.
구현
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'
}
});