본문 바로가기

기타/React Native 공부

[앱개발 종합반] 4주차 개발일지

앱개발 종합반을 수강한지는 꽤 됐지만 4주차 개발일지를 마지막으로 정말 마무리하려고 한다.

 

4주차에서 배운 내용은 크게 3가지이고 서버에 관련해서 배웠다.

 

1. 앱과 서버

 

1) 앱에서의 서버

 

서버가 앱에 데이터를 줄 땐 JSON 형태로 데이터를 전달해준다.

리액트 네이티브로 앱을 만들면서 서버와 통신(대화)하는 시점은 크게 두 가지이다.

 

첫번째, 앱 화면이 그려진 다음 데이터를 준비 ← useEffect

이 경우 우리는 보통 앱 화면이 그려진 다음 서버측에서 제공해주는 API를 이용해 필요한 데이터들을 준비한다. 

 

두번째, 앱에서 사용자가 저장 버튼을 눌렀을 때

예를 들어, 나만의 꿀팁 앱에서 데이터 저장 시점은, 사용자가 꿀팁을 보고 팁 찜하기 버튼을 눌렀을 때이다.

 

2) 서버 외부 API - 휴대폰 위치 가져오기

API를 통해 날씨 데이터를 가져온다는 것은 다음 순서로 진행되어야 한다.

첫번째, 현재 위치(좌표) 데이터 필요, 가져오기
두번째, 위치 데이터를 이용해 현재 위치 날씨 데이터 가져오기

 

아래의 코드는 메인 페이지에 날씨 가져오기 도구를 적용시킨 코드이다.

import React,{useState,useEffect} from 'react';
import main from '../assets/main.png';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';
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}) {
  console.disableYellowBox = true;
  //return 구문 밖에서는 슬래시 두개 방식으로 주석

  //기존 꿀팁을 저장하고 있을 상태
  const [state,setState] = useState([])
  //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태
  const [cateState,setCateState] = useState([])

  //컴포넌트에 상태를 여러개 만들어도 됨
  //관리할 상태이름과 함수는 자유자재로 정의할 수 있음
  //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음.
  const [ready,setReady] = useState(true)

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

    
  },[])

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


	let todayWeather = 10 + 17;
    let todayCondition = "흐림"

	//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨
  //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)
  return ready ? <Loading/> :  (
    /*
      return 구문 안에서는 {슬래시 + * 방식으로 주석
    */
    <ScrollView style={styles.container}>
        <StatusBar style="black" />
        {/* <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={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
  }


});

 

2. 서버리스

 

서버리스란 이름 그대로 서버가 없다는 게 아니라 서버를 직접 만들 필요가 없다는 정도로 이해하면 된다.
우리가 서버를 직접 구현, 구성 할 필요없이 필요한 서버 기능을 제공하는 곳에서, 서비스를 사용하기만 하면 된다.

나만의 꿀팁 앱을 기준으로 필요한 서버적 기능은 꿀팁을 서버로 부터 가져오기, 꿀팁 찜하기이다.

3. 파이어베이스(Firebase)

 

파이어베이스는 구글에서 만든 서버리스 서비스이다. 서버에 대한 지식이 그렇게 깊지 않아도 서버적인 기능들을 사용할 수 있게끔 도와주는 서비스이다.

파이어베이스를 사용하려면 프로젝트를 생성해야 하는데 먼저 파이어베이스에 가입하고, 파이베이스 프로젝트를 생성한다. 그리고 사용 할 파이어베이스 서비스를 활성화하면 끝이다.

 

다음으로는 파이어베이스를 앱에 연결해야한다.

//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";

// Initialize Firebase
//파이어베이스 사이트에서 봤던 연결정보를 여기에 가져옵니다
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"
};

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

export const firebase_db = firebase.database()

 

1) 파일 스토리지

 

가장먼저 사용하게 되는 파이어베이스 서비스는 파일 스토리지이다. 멀리 있는 파일 저장소에 이미지 및 사용 할 파일을 올려두고, 필요할 때마다 꺼내 쓰는 용도로 사용하면 된다.

2) 리얼타임 데이터베이스

 

리얼타임 데이터베이스는 그동안 배운 리스트, 딕셔너리 구조, 즉 JSON 형태로 저장/관리되는 데이터베이스 서비스를 말한다. 이 서비스를 사용 할 땐, 파이어베이스에서 제공해주는 함수들을 이용하기만 하면 데이터 저장/수정/삭제가 가능하다. 이름이 리얼타임 데이터베이스인 이유는, 플랫폼과 실시간 데이터 주고 받는 것에 특화되어 있어서 그렇다고 한다. 파일 저장소 스토리지를 사용하는 경우와 리얼타임 데이터베이스를 사용하는 경우는 이렇게 나눌 수 있다.

이미지 저장 → 파일 저장소 스토리지
JSON 데이터 → 리얼 타임 데이터베이스

위의 사진은 리얼타임 데이터베이스를 사용하여 데이터를 업로드한 결과이다. 꿀팁앱에서 우리가 가진 팁들이 딕셔너리 구조로 저장이 되며, 숫자 0,1,2,3,4...은 리스트의 형태로 문제가 차곡차곡 쌓여 있는 모습을 볼 수 있다. (리스트 형태)
따라서 이 데이터를 앱에 가져오게 된다면, data.json 데이터를 이용했던 것과 동일하게 사용이 가능하다.
또한, image의 값 부분을 눌러보면 값을 수정할 수 있게 활성화 되는데, 이 부분엔 그럼 우리가 파일 저장소에 올렸던 이미지 주소를 넣을 수 있다. (수정을 하고 싶을때)

 

3) 리얼타임 데이터베이스 - 전체 데이터 조회하기(읽기)

 

파이어베이스를 이용해 전체 데이터를 조회 하는 방법은 전체 데이터를 가져오게끔 해주는 파이어베이스 제공 함수 사용 방법과
가져올 데이터가 어떠한 이름으로 리얼타임 데이터베이스에 저장되어 있는지를 알아야 한다. 리얼 타임 데이터 베이스를 생성하면서 부터 고유한 DB 주소를 갖게되는데, 데이터의 저장 위치를 알았다면, 파이어베이스의 리얼타임 데이터베이스 전용 함수에 데이터 저장 위치를 알려주면서 데이터를 가져올 수 있다.

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

ref('/tip') 이 부분에서 /tip 영역에 가지고 오고 싶은 데이터의 주소를 넣어주면 된다. 이 주소 앞부분에는 기본 주소가 생략되어 있다.

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

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

 

이 코드는 서버리스를 이용하여 데이터베이스를 조회하기 위해, 파이어베이스 측에서 정해놓은 API 사용방법이다. 따라서 우린 공식 문서 그대로 사용 방법을 적용해야 한다.
조회한 데이터는 `snapshot` 부분에 담겨서 `{}` 내부에서 사용할 수 있는데, 그 중 실제 우리에게 필요한 데이터는 `snapshot.val()`로 가져와 변수에 담아 사용할 수 있다.

4) 리얼타임 데이터베이스 - 쓰기

 

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

나만의 꿀팁 앱에서 파이어베이스로 데이터를 보내 저장하는 상황은 바로 꿀팁 찜! 하기 버튼을 눌렀을 때이다.
또한, '특정' 사용자가 내가 만든 앱을 이용하다 꿀팁 찜! 버튼을 눌렀다면 파이어베이스에 어떤 식으로 저장될지 생각해봐야 한다.

 

만약에 내가 만든 앱의 사용자들이 늘어나면 데이터베이스에도 여러 사람들의 데이터가 쌓일텐데, 이 때, 사용자마다 고유한 정보들을 관리하려면, 사용자 고유 ID 값 정도의 데이터가 필요하다. 그래야 구분을 할 수 있다.

다행히 Expo는 앱을 사용할 사용자들의 고유 아이디를 생성해서 알려주는데 이를 통해 사용자들마다 고유한 ID 값으로 데이터를 관리할 수 있다.

이 코드를 이용하면 앱 안에서 어디서든지 동일한 사용자 유니크 아이디를 생성 및 사용할 수 있다.

//설치하기
expo install expo-application
import * as Application from 'expo-application';
const isIOS = Platform.OS === 'ios';


...

const like = async () => {

		let uniqueId;
		if(isIOS){
		  let iosId = await Application.getIosIdForVendorAsync();
		  uniqueId = iosId
		}else{
		  uniqueId = Application.androidId
		}
		
		console.log(uniqueId)

}

 

아래는 리얼타임 데이터베이스 저장 함수 사용 코드인데 파이어베이스 공식 문서 사용법 그대로 리얼타임 데이터 베이스에 저장하게끔 해주는 함수를 사용했다.

const like = () => {
        
        // like 방 안에
        // 특정 사용자 방안에
        // 특정 찜 데이터 아이디 방안에
        // 특정 찜 데이터 몽땅 저장!
        // 찜 데이터 방 > 사용자 방 > 어떤 찜인지 아이디
        const user_id = Constants.installationId;
        firebase_db.ref('/like/'+user_id+'/'+ tip.idx).set(tip,function(error){
            console.log(error)
            Alert.alert("찜 완료!")
        });
    }

'/like/'+user_id+'/'+ tip.idx  이 부분은 데이터가 저장될 경로를 결정한다.
.set() 부분 첫 번째 인자로는, 저장할 데이터(tip 상태)를 두 번째 인자로는 혹시 에러가 발생하면 나중에 처리 할 함수를 둔다.

 

 

앱개발 종합반 개발일지는 여기서 마무리할텐데 그 이유는 5주차 마지막 강의는 구글 애드몹 설정이랑 앱 배포하는 내용이기 때문이다.

나는 수업 실습용인 이 꿀팁앱을 배포할 생각이 없었기 때문에 그 과정은 따로 요약하지 않았다.