[왕초보] 나만의 수익성 앱, 앱개발 종합반 4주차

2022. 9. 2. 02:22배울수록 연봉은 늘어간다./앱개발

728x90

서버리스(serverless)

서버리스(serverless)란 개발자가 서버를 관리할 필요 없이 애플리케이션을 빌드하고 실행할 수 있도록 하는 클라우드 네이티브 개발 모델입니다.

 


[앱과 서버] 날씨 서버 외부 API

1. 종류

1) 서버가 제공하는 도메인 사용
2) 서버가 만들어놓은 함수 사용

1) 서버가 제공하는 도메인 사용

a. 현재 위치(좌표) 데이터 필요, 가져오기 (어디의 날씨인지 알아야 하니까요!)

  • Location(위치) 공식문서
 

Location - Expo Documentation

Expo is an open-source platform for making universal native apps for Android, iOS, and the web with JavaScript and React.

docs.expo.dev

 

  • expo install expo-location => 터미널 실행
  • MainPage.js 코드 붙여 넣기
    더보기
    import React,{useState,useEffect} from 'react';
    import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';

    const main = 'https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png'
    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}) {
      //useState 사용법
    //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
      //setState는 state를 변경시킬 때 사용해야 하는 함수

      //모두 다 useState가 선물해줌
      //useState()안에 전달되는 값은 state 초기값
      const [state,setState] = useState([])
      const [cateState,setCateState] = useState([])

    //하단의 return 문이 실행되어 화면이 그려진 다음 실행되는 useEffect 함수
      //내부에서 data.json으로부터 가져온 데이터를 state 상태에 담고 있음
      const [ready,setReady] = useState(true)

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

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

      //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
      // let tip = state.tip;
      let todayWeather = 10 + 17;
      let todayCondition = "흐림"
      //return 구문 밖에서는 슬래시 두 개 방식으로 주석
      return ready ? <Loading/> :  (
        /*
          return 구문 안에서는 {슬래시 + * 방식으로 주석
        */

        <ScrollView style={styles.container}>
          <StatusBar style="light" />
          {/* <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={{uri: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
      }


    });
  • 변경된 코드 확인

1-1
1-2
1-3

b. 위치 데이터를 이용해 현재 위치 날씨 데이터 가져오기

 

애러 나서 더 이상 진행이 안됨!!


[파이어베이스] 파이어베이스(firebase) 소개

파이어베이스는 구글에서 만든 서버리스 서비스입니다.

 

1. 파이어베이스 프로젝트 생성하기

파이어베이스를 이용하는 절차는, 게임 캐릭터를 생성하는 것과 유사합니다.

육성 게임을 한다 했을 때, 보통 이런 절차로 게임을 하게 되죠?

1) 게임에 가입한다. → 파이어베이스에 가입한다
2) 캐릭터를 생성한다 → 파이베이스 프로젝트 생성
3) 캐릭터에 필요한 장구류를 착용한다. → 사용할 파이어베이스 서비스 활성화

1) 게임에 가입한다. → 파이어베이스에 가입한다

 

Firebase

Firebase는 고품질 앱을 빠르게 개발하고 비즈니스를 성장시키는 데 도움이 되는 Google의 모바일 플랫폼입니다.

firebase.google.com

 

2) 캐릭터를 생성한다 → 파이베이스 프로젝트 생성

  • 프로젝트를 만들 때 우리가 만든 프로젝트와 이름을 일치시키는 게 좋습니다.
  • 구글 애널리틱스는 권장으로 합니다.
  • 위치는 대한민국으로 바꾸어 줍니다. 저는 미국으로 되어 있어서 바꾸어 주었습니다.

 

3) 캐릭터에 필요한 장구류를 착용한다. → 사용할 파이어베이스 서비스 활성화

  • 우리가 사용하는 코드는 자바스크립트를 기초로 하기 때문에 자바스크립트는 웹으로 인식을 합니다.

  • 프로젝트 이름과 동일하게 만들어 줍니다.
  • 호스팅은 선택하지 마세요!
  • 앱 등록 버튼을 누른 후 콘솔로 이동합니다.

  • 톱니바퀴 모양을 누른 뒤 '프로젝트 설정'을 누른 후 SDK 값이 들어와 있는지 확인을 합니다.
  • 앱에 파이어베이스 도구를 설치합니다
expo install firebase
  • 터미널에 설치해주세요!

  • sparta-myhoneytip-kim에 firebaseConfig.js 파일을 만들어 줍니다.

firebaseConfig.js 를 붙여넣기!

더보기

import firebase from "firebase/compat/app";

// 사용할 파이어베이스 서비스 주석을 해제합니다
//import "firebase/compat/auth";
import "firebase/compat/database";
//import "firebase/compat/firestore";
//import "firebase/compat/functions";
import "firebase/compat/storage";

// Initialize Firebase
//파이어베이스 사이트에서 봤던 연결정보를 여기에 가져옵니다
const firebaseConfig = {
  apiKey: "AIzaSyBKG2xY91x23W8PF1231k5OUJ5o9kHSKYQeNWUw",
  authDomain: "sparta-psytest-gun.firebaseapp.com",
  databaseURL: "https://sparta-psytest-gun.firebaseio.com",
//위 databaseURL은 firebase에서 기본제공 해주지 않으니 직접 작성해주세요!
  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()

  • 빨강 글씨는 자신이 받은 키를 입력해 주세요! 외부 공개되면 안 됩니다.
  • databaseURL: "https://sparta-psytest-gun.firebaseio.com",
  • databaseURL은 firebase에서 기본 제공해주지 않으니 직접 작성해주세요!

 

2. 파일 저장소 스토리지(storage)

파일 저장소에 이미지 및 사용할 파일을 올려두고, 필요할 때마다 꺼내 쓰는 용도로 사용

 

 

  • 스토리지를 눌러서 생성합니다.
  • 위치 설정은 아시아로 해줍니다.
  • 폴더를 만들어 줍니다 '/imaes
  • 이미지가 잘 들어갔는지 확인해 보시면 됩니다. 
  • 구글 드라이브를 사용하신 분들이라면 어렵지 않게 사용이 가능합니다.

[파이어베이스] 리얼타임 데이터베이스 - 설정

리스트, 딕셔너리 구조, 즉 JSON 형태로 저장/관리되는 데이터베이스 서비스입니다.

그럼 언제 파일 저장소 스토리지를 쓰고, 언제 리얼타임 데이터베이스를 사용하나요?

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

 

1. 리얼타임 데이터베이스 생성

  • 우리나라에서 가장 가까운 나라를 선택합니다.
  • 잠금 설정으로 선택합니다.

  • 위에 있는 주소를 아까 누락되었던 databaseURL에 넣어 줍니다.

  • 규칙을 true로 바꾸어 줍니다. (외부에 공개하겠다는 뜻!)
  • 규칙을 변경하고 나면 "개시"를 꼭 눌러 줍니다.

 

  • 우측을 눌러 JSON 파일 가져오기를 눌러 data.json 파일을 가져옵니다.

 

[파이어베이스] 리얼타임 데이터베이스 - 전체 데이터 읽기

파이어베이스 API는 함수를 사용합니다.

1. 파이어베이스 API 함수는 그럼 어딨는가?

  • 문서를 클릭합니다.
  • 공식문서에서 확인할 수 있습니다.
 

DataSnapshot class  |  Firebase JavaScript API reference

FirebaseVisionOnDeviceAutoMLImageLabelerOptions

firebase.google.com

 

MainPage.js 를 붙여 넣기!

import React,{useState,useEffect} from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';

const main = 'https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png'
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";
import axios from "axios"
import {firebase_db} from "../firebaseConfig"

export default function MainPage({navigation,route}) {
  //useState 사용법
//[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
  //setState는 state를 변경시킬때 사용해야 하는 함수

  //모두 다 useState가 선물해줌
  //useState()안에 전달되는 값은 state 초기값
  const [state,setState] = useState([])
  const [cateState,setCateState] = useState([])
  //날씨 데이터 상태 관리 상태 생성!
  const [weather, setWeather] = useState({
    temp : 0,
    condition : ''
  })

//하단의 return 문이 실행되어 화면이 그려진 다음 실행되는 useEffect 함수
  //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
  const [ready,setReady] = useState(true)

  useEffect(()=>{
    navigation.setOptions({
      title:'나만의 꿀팁'
    })  
//뒤의 1000 숫자는 1초를 뜻함
    //1초 뒤에 실행되는 코드들이 담겨 있는 함수
    setTimeout(()=>{
        firebase_db.ref('/tip').once('value').then((snapshot) => {
          console.log("파이어베이스에서 데이터 가져왔습니다!!")
          let tip = snapshot.val();
          
          setState(tip)
          setCateState(tip)
          getLocation()
          setReady(false)
        });
        // getLocation()
        // setState(data.tip)
        // setCateState(data.tip)
        // setReady(false)
    },1000)
 
    
  },[])

  const getLocation = async () => {
    //수많은 로직 중에 에러가 발생하면
    //해당 에러를 포착하여 로직을 멈추고, 에러를 해결하기 위한 catch 영역 로직이 실행
    try {
      //자바스크립트 함수의 실행 순서를 고정하기 위해 쓰는 async,await
      await Location.requestForegroundPermissionsAsync();
      const locationData= await Location.getCurrentPositionAsync();
      console.log(locationData)
      console.log(locationData['coords']['latitude'])
      console.log(locationData['coords']['longitude'])
      const latitude = locationData['coords']['latitude']
      const longitude = locationData['coords']['longitude']
      const API_KEY = "cfc258c75e1da2149c33daffd07a911d";
      const result = await axios.get(
        `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
      );

      console.log(result)
      const temp = result.data.main.temp; 
      const condition = result.data.weather[0].main
      
      console.log(temp)
      console.log(condition)

      //오랜만에 복습해보는 객체 리터럴 방식으로 딕셔너리 구성하기!!
      //잘 기억이 안 난다면 1주차 강의 6-5를 다시 복습해보세요!
      setWeather({
        temp,condition
      })

    } catch (error) {
      //혹시나 위치를 못 가져올 경우를 대비해서, 안내를 준비합니다
      Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껐다 켜볼까요?");
    }
  }

  const category = (cate) => {
    if(cate == "전체보기"){
        //전체보기면 원래 꿀팁 데이터를 담고 있는 상태 값으로 다시 초기화
        setCateState(state)
    }else{
        setCateState(state.filter((d)=>{
            return d.category == cate
        }))
    }
}

  //data.json 데이터는 state에 담기므로 상태에서 꺼내옴
  // let tip = state.tip;
  let todayWeather = 10 + 17;
  let todayCondition = "흐림"
  //return 구문 밖에서는 슬래시 두개 방식으로 주석
  return ready ? <Loading/> :  (
    /*
      return 구문 안에서는 {슬래시 + * 방식으로 주석
    */

    <ScrollView style={styles.container}>
      <StatusBar style="light" />
      {/* <Text style={styles.title}>나만의 꿀팁</Text> */}
      <Text style={styles.weather}>오늘의 날씨: {weather.temp + '°C   ' + weather.condition} </Text>
       <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}>
          <Text style={styles.aboutButtonText}>소개 페이지</Text>
        </TouchableOpacity>
      <Image style={styles.mainImage} source={{uri: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
  }


});

애러 때문에 더 이상 진행이 안됩니다!


 

Total : Today : Yesterday :