qcoding

8)[사이드프로젝트]App개발-전국국밥_realm를 사용한 list 저장 페이지 본문

[사이드 프로젝트]App개발 - 전국 국밥 찾기

8)[사이드프로젝트]App개발-전국국밥_realm를 사용한 list 저장 페이지

Qcoding 2022. 2. 6. 14:02
반응형

저장 리스트

1) 기본구성

-> 지역별 LIST에서 Detail 페이지로 이동한 뒤 하트 아이콘을 클릭하면 한줄평을 작성하는 모달이 뜨고, 모달을 작성하면 realm DB를 통해 휴대폰내에 저장이 되게 된다. 나만의 국밥집 List 페이지에 들어가면 저장된 list를 불러오고 해당 하트 버튼을 클릭 시 삭제가 된다.

 

2) 기능소개

◆ realm DB 구조 만들기 (scheme 만들기)

// scheme 구조를 만드는 것으로 이는 내가 사용할 DB를 만들어 놓는 것이다.
// primaryKey는 "_id" 로 설정하면 나중에 특정 객체를 선택할 때 primary key를 사용하여 선택할 수 있다.

import Realm from "realm";

const favoriteSchema = {
  name: "favorite_kookbab",
  properties: {
    _id: "string",
    diningCode_url: "string",
    score: "int",
    name: "string",
    recommend_food: "string",
    keyword:"string",
    dong:"string",
    address:"string",
    lat:"int",
    lon:"int",
    img_url:"string",
    comment:"string"
  },
  primaryKey: "_id",
};

◆ Apploading 시 realm DB생성

-> 내 경우 expo-apploaindg 시에 시작되는 startLoading에 realm을 생성하게 하였다.

realm의 경우 schmeVersion이 숫자로 있으며, 수정시에는 반드시 버전의 숫자가 올라가야 한다.

또한 여기서 deleteRealmIfMigrationNeeded: true, 로 하면 DB그 구조가 바뀔 시 기존의 DB가 삭제되고 새로 만든다는 의미이다. 만약 구조를 변경하거나 string되어야 되는 값을 int가 되어야한다고 위에서 스키마 변경 시 에러가 나서 동작이 되지 않을 것이다. 이때 true로 정하면 기존 DB는 삭제되고 새로운 DB가 생성되므로 에러가 뜨지 않지만 기존 DB는 다 삭제된다.

  // realm 생성
  const createRealm=async()=>{
    const realm_favorite = await Realm.open({
      path: "myrealm",
      schema: [favoriteSchema],
      schemaVersion: 1,
      // deleteRealmIfMigrationNeeded: true,
    });
    setRealmFavorite(realm_favorite)
  }


//Apploading이 시작될 때 실행되는 함수
  const startLoading = () => {
      createRealm();
      myChannel();
  }

  if (!ready || !assets) {
    return (
      <>
        <StatusBar style="auto" />
          <AppLoading
            onError={(e)=>console.log(e)}
            startAsync={startLoading}
            onFinish={onFinish}
          />
      </>
    );
  }

◆ Detail 페이지에서 기존 DB에 있는 음식점이 있는 지 확인 //  DB에 저장

-> 위에서 Schme에서 보면 id가 string으로 해놓은 것은 저장 시 id를 음식점이름으로 하기 위해서 였고, 코드에서도 아래의 DB저장을 살펴보면 _id : "item.name" 으로 저장을 하였다. DB내에 해당 음식점이 존재하는 지 를 확인하기 위해서는 id (음식점 이름) 으로 조회 후 해당 값이 있으면 favorite 상태를 변경한다.

* const IsFavorite=realmFavorite.objectForPrimaryKey("favorite_kookbab", item.name) 사용

기존 DB에 있는 지 확인 후 Favorite 버튼 색 변경

    // Context API를 통해 realm에서 불러옴
    const {realmFavorite}= useContext(DBContext);
	
    //기존 DB에 해당 음식점이 있으면 true , 없으면 false 이며 기본값은 false
    const [favorite,setFavorite]=useState(false)
    const {params:{item}} = useRoute();

    useEffect(()=>{
        // realm에 데이터가 들어 있는 것을 확인함
        // realmFavorite.objects("favorite_kookbab")=[{},{} ...] 
        
        // DB안에 값이 있는 경우 
        if(realmFavorite.objects("favorite_kookbab").length!=0){
            // realmFavorite를 그냥 쓰면 안되고 .object("favorite_kookbab")을 해줘야함
            // 이렇게 하면 [{}.{}] 형태로 가져올 수 있다.
			
            // 값이 있으면 favorite 값을 true로 변경
            const IsFavorite=realmFavorite.objectForPrimaryKey("favorite_kookbab", item.name)
            if(IsFavorite){
                setFavorite(prev=>!prev)
            }
        }
    },[])
    
    
   	// Favorite 버튼을 favorite 스테이트 상태에 따라서 이모티콘을 다르게 사용한다.
	//  Favorite button
    const FavoriteButton=()=>{
        return(
            <TouchableOpacity 
                onPress={()=>
                    { favorite?delteFavorite(item):toggleModal() }
            }>
                <FontAwesome name={favorite?"heart":"heart-o"} size={24} color="red" />
            </TouchableOpacity>
        )}

DB저장

 // add Favorite
    const addFavorite=async(item)=>{
        let addData
        if(!oneComment){
            Alert.alert("한줄평을 입력하세요")
            return;
        }
        
        // 실제 DB에 저장하는 코드이며, 구조는 Scheme와 동일하다
        // 저장한 뒤에는 favorite 상태를 변경하여 UI를 변경해준다.
        
        await realmFavorite.write(()=>{
            addData=realmFavorite.create("favorite_kookbab", {
                _id: item.name,
                diningCode_url: item.diningCode_url,
                score: item.score,
                name: item.name,
                recommend_food: item.recommend_food,
                keyword:item.keyword,
                dong:item.dong,
                address:item.address,
                lat:item.lat,
                lon:item.lon,
                img_url:item.img_url,
                comment:oneComment
            });
            setFavorite(prev=>!prev);
            toggleModal();
        })
    }

◆ 나만의 국밥집 List 탭 클릭 시 DB내용 불러오기

    // Context API에서 realm에서 자료가져오기
    const {realmFavorite:realmFavorite}= useContext(DBContext);
    
	// name으로 가져온다.    
    useEffect(()=>{
        const content_temp=realmFavorite.objects("favorite_kookbab");
        
        // DB내의 score순으로 내림차순 정렬을 한다. false하면 오름차순
        setContent(content_temp.sorted("score",true))
        
        // 저장하거나 삭제할 때 마다 DB에 있는 값을 새롭게 불러오기 위하여
        // eventListener를 연결하여 DB가 변경될 시 값을 계속 불러온다.
        content_temp.addListener(()=>{
            const content_temp=realmFavorite.objects("favorite_kookbab");
            setContent(content_temp.sorted("score",true))
        })
        
        // useEffect의 return 함수는 clean up 함수로 위에서 생성한 eventLisener를 
        // 제거한다.
        return () => {
            content_temp.removeAllListeners();
          };
    },[])

◆ DB에서 삭제하기

    // delete Favorite 
    // useCallback을 사용한 이유는 해당 함수를 하위 컴포넌트에 넘겨줘서 사용하기 때문에 
    // 리렌더링이 되는 것을 방지하기 위해서 사용하였다. 그러나 사용하지 않아도 크게 
    // 상관는 없는 듯 하다.
    
    const removeFavorite=useCallback(async(item)=>{
    
    	// realm DB에서 objectForPrimaryKey 메서드를 사용하여 선택한 item을 불러오고
        // delete를 통해서 삭제한다.
        // 이때 objectForPrimaryKey는 "_id" 로 설정하였으므로 ,_id값과 동일하며 현재 DB에서는
        // 음식점 이름이다.
        
        await realmFavorite.write(() => {
            const selectedItem = realmFavorite.objectForPrimaryKey("favorite_kookbab", item.name);
            realmFavorite.delete(selectedItem);
            });
    },[])

◆ Favorite.js 전체코드

import React ,{useContext,useEffect,useState,useCallback}from 'react'
import { View, Text,FlatList,Dimensions } from 'react-native'
import styled from 'styled-components/native';	
import { DBContext } from '../Context';
import CardComponent from '../components/CardComponent';



const TopNavigator=styled.View`
    flex:1;
    justify-content:center;
    align-items:center;
    background-color:white;
`;

const TypeBtnText=styled.Text`
    color:black;
    font-size:20px;
    font-weight:bold;
`;


const Favorite = () => {
    // realm에서 자료가져오기
    const {realmFavorite:realmFavorite}= useContext(DBContext);
    const [content,setContent]=useState({});

    // delete Favorite 
    const removeFavorite=useCallback(async(item)=>{
        await realmFavorite.write(() => {
            const selectedItem = realmFavorite.objectForPrimaryKey("favorite_kookbab", item.name);
            realmFavorite.delete(selectedItem);
            });
    },[])

    // name으로 가져온다.    
    useEffect(()=>{
        const content_temp=realmFavorite.objects("favorite_kookbab");
        setContent(content_temp.sorted("score",true))
        content_temp.addListener(()=>{
            const content_temp=realmFavorite.objects("favorite_kookbab");
            setContent(content_temp.sorted("score",true))
        })
        return () => {
            content_temp.removeAllListeners();
          };
    },[])

    // 여기서 content의 형태는 array 
    //  [{},{},{}]
    // console.log(`favorite 페이지 :  ${content[2].name}`)

    return (
    <View style={{backgroundColor:"white",flex:1}}>
        {content.length == 0?
        <TopNavigator>
                <TypeBtnText>리스트를 저장해주세요!</TypeBtnText>
        </TopNavigator>:        
        <FlatList
            contentContainerStyle={{paddingHorizontal:10,backgroundColor:"white"}}
            data={content}
            ItemSeparatorComponent={() => <View style={{ height: 15}} />}
            keyExtractor={(item)=>item._id}
            renderItem={({item,index})=>{ 
                return(
                    <CardComponent item={item} index={index} removeFavorite={removeFavorite}></CardComponent>
                )
            }}
        />
}


    </View>
      
    )
}

export default Favorite

◆ CardComponent.js ( Favorite.js 하위 컴포넌트 )

--> 여기서 import * as WebBrowser from 'expo-web-browser'; 를 사용하여 해당 url 클릭 시 웹브라우저를 열어서 해당 사이트로 접속할 수 있다. 

import React from 'react'
import styled from 'styled-components/native';
import { View,TouchableOpacity} from 'react-native'
import { useNavigation } from '@react-navigation/native';
import { FontAwesome } from '@expo/vector-icons'; 
import * as WebBrowser from 'expo-web-browser';


const CardContainer=styled.View`
    height:80px;
    justify-content:center;
    flex-direction:row;
    align-items:center;
    /* border-color:black;
    border-width:2px; */
`
const ColmnFirst=styled.View`
    flex:1;
    justify-content:center;
    align-items:center;
`;

const ColmnSecond=styled.View`
    flex:5;
    /* border-color:black;
    border-width:2px; */
`;

const RowFist=styled.View`
    flex:1;
    border-bottom-color:black;
    border-bottom-width:2px;
    justify-content:space-between;
    align-items:center;
    flex-direction:row;
`;  
const RowSecond=styled.View`
    flex:1;
    justify-content:space-between;
    align-items:center;
    flex-direction:row;
`; 

const IndexTitle=styled.Text`
    font-size:30px;
    font-weight:bold;
`;

const FoodName=styled.Text`
    font-size:20px;
    font-weight:bold;
`;

const Comment=styled.Text`
    font-size:15px;
`;

const InfoBtn=styled.TouchableOpacity`
    width:70px;
    padding:5px 5px;
    border-radius:5px;
    background-color:#a29bfe;
    justify-content:center;
    align-items:center;
`

const InfoText=styled.Text`
    color:white;
    font-weight:bold;
`;


const CardComponent=({item,index,removeFavorite})=>{

    // url 클릭 시 diningCode로 이동
    const goDiningCodeUrl=async(diningCode_url)=>{
        await WebBrowser.openBrowserAsync(diningCode_url);
    };

    return(
        <CardContainer>
            <ColmnFirst>
                <IndexTitle>{index+1}</IndexTitle>
            </ColmnFirst>
            <ColmnSecond>
                <RowFist>
                    <FoodName>{item.name}</FoodName>
                    <TouchableOpacity style={{justifyContent:"center",alignSelf:"center"}}
                        onPress={()=>{
                            removeFavorite(item);
                        }}>
                        <FontAwesome name="heart" size={24} color="red" />
                    </TouchableOpacity>
                </RowFist>
                <RowSecond>
                    <Comment>⭐{item.comment}</Comment>
                    <InfoBtn onPress={()=>{ 
                        goDiningCodeUrl(item.diningCode_url);
                    }}>
                        <InfoText>More</InfoText>
                    </InfoBtn>
                </RowSecond>
            </ColmnSecond>
        </CardContainer>
    )
}

export default CardComponent;
반응형
Comments