DEVELOP
article thumbnail

스파르타코딩클럽 - [왕초보] 나만의 수익성 앱, 앱개발 종합반 강의를 듣고 내용을 정리한 게시글입니다.

 

스파르타코딩클럽

5주 완성! 코딩을 전혀 모르는 비개발자 대상의 웹개발 강의

spartacodingclub.kr


API (Application Programming Interface)

API

: 서버 쪽에서 정한 규칙 

  • 앱에서 서버에 데이터를 요청하거나 데이터를 보내는 대화를 하려면 서버가 정한 규칙에 따라 대화 요청을 해야한다. 
  • 정한 규칙에 따라 요청을 하지 않으면 응답이 오지 않는다. 
  • 그 규칙을 API 라고 부른다. 
  • 그 규칙의 형태는 서버가 제공하는 도메인일 수도 있고, 서버가 만들어놓은 함수를 그냥 이용해서 사용하는 규칙일 수도 있다. 
  • 서버가 앱에 데이터를 줄 땐 JSON 형태로 데이터를 전달해준다. 

React Native에 주로 데이터를 준비하는 시점 

  1. 앱 화면이 그려진 다음 데이터를 준비 (useEffect)
  2. 앱에서 사용자가 저장 버튼을 눌렀을 때 (ex. 팁 찜하기 버튼)

서버 API 

: 서버가 제공하는 도메인을 그대로 사용하는 방식 


날씨 API 사용하기

openweathermap api

- 날씨 데이터를 제공해주는 일정 요청에 대해서는 무료 API를 제공해주는 openweathermap api를 사용한다. 

 

Сurrent weather and forecast - OpenWeatherMap

Access current weather data for any location on Earth including over 200,000 cities! The data is frequently updated based on the global and local weather models, satellites, radars and a vast network of weather stations. how to obtain APIs (subscriptions w

openweathermap.org

  1. 현재 위치(좌표) 데이터 가져오기
  2. 위치 데이터를 이용해 현재 위치 날씨 데이터 가져오기 

Expo location

 

Location

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 location 설치하기 

$ expo install expo-location

 

▼ myapp/pages/MainPage.js 

  • expo-location 라이브러리 import 하기 
  • getLocation 함수 작성 
    위치 접근 권한을 받고, 현재 위치 데이터를 locationData에 저장하고 콘솔에 출력한다. 
    만약 에러 발생 시 경고창을 띄운다 
  • 앱을 시작함과 동시에 위치 접근 권한을 받아오기 위해 useEffect안에 getLocation 함수를 실행한다. 
import * as Location from "expo-location";
  const getLocation = async () => {
    try {
      //자바스크립트 함수의 실행순서를 고정하기 위해 쓰는 async,await
      await Location.requestForegroundPermissionsAsync();
      const locationData = await Location.getCurrentPositionAsync();
      console.log(locationData);
    } catch (error) {
      Alert.alert("위치를 찾을 수가 없습니다.", "앱을 껏다 켜볼까요?");
    }
  };
      getLocation();

- 앱 실행 시 위치 권한 허용 창이 뜨고, 유저가 허용을 하면, 위치 데이터를 받아와 콘솔창에 출력한다. 

- 여기에서 latitude 값과 longitude 값을 사용해 날씨 데이터를 얻어올 것이다. 


axios

: 서버가 제공하는 도메인 형식의 API를 사용하기 위한 도구 

 

- axios 설치하기

$ yarn add axios

▼ myapp/pages/MainPage.js 

  • axios import 하기 
import axios from "axios"
  • 상태 weather 생성하고, temp와 condition을 기본값으로 초기화하기
  const [weather, setWeather] = useState({
    temp: 0,
    condition: "",
  });
  • getLocation 함수 수정 
    openweather 사이트 회원가입 후 본인의 API를 API_KEY에 넣고. 
    경도, 위도, API KEY 값을 넘겨 result에 저장하고, temp와 condition 값을 각각 저장하고 해당 값으로 setWeather한다. 
      const latitude = locationData["coords"]["latitude"];
      const longitude = locationData["coords"]["longitude"];
      const API_KEY = "write your API KEY!";
      const result = await axios.get(
        `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
      );
      const temp = result.data.main.temp;
      const condition = result.data.weather[0].main;
      setWeather({
        temp,
        condition,
      });

 

  • 상태 weather의 temp와 condition 값으로 화면에 오늘의 날씨를 표시한다. 
      <Text style={styles.weather}>
        오늘의 날씨: {weather.temp + "°C  | " + weather.condition}{" "}
      </Text>

오늘의 날씨 변경


파이어베이스(firebase)

서버리스 

: 서버를 직접 구현, 구성할 필요 없이 필요한 서버 기능을 제공하는 곳에서 서비스를 사용하는 것 

 

앱에서 필요한 기능들 

  1. 꿀팁을 서버로부터 가져오기 
    서버에 데이터를 저장해두고, API 형태로 불러와서 사용하면 실시간 업데이트 현황이 앱에 반영이 된다.
  2. 꿀팁 찜하기 
    다른 기기에서 로그인했을 때 해당 유저의 데이터를 확인하려면, 데이터가 관리되어야 어디서든 사용자 데이터를 관리하고 보여줄 수 있다. 

 

파이어베이스 연결하기 

 

Firebase

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

firebase.google.com

- 파이어베이스 회원가입/로그인 하고 새 프로젝트를 생성한다. 

$ expo install firebase

▼ myapp/firebaseConfig.js 

import firebase from "firebase/compat/app";
import "firebase/compat/database";
import "firebase/compat/storage";

const firebaseConfig = {
  apiKey: "your key",
  authDomain: "myfirstreactnativeapp-7f436.firebaseapp.com",
  projectId: "myfirstreactnativeapp-7f436",
  storageBucket: "myfirstreactnativeapp-7f436.appspot.com",
  messagingSenderId: "470138618125",
  appId: "1:470138618125:web:9034eb4c1408046a04f044",
  measurementId: "G-1PT0EF2JWX",
};

if (!firebase.apps.length) {
  firebase.initializeApp(firebaseConfig);
}

export const firebase_db = firebase.database();

파일 저장소 스토리지(Storage)

- 파이어베이스에서 메뉴 -> 빌드 -> storage에 images 폴더 생성하고 해당 폴더에 이미지 업로드하기


리얼타임 데이터베이스 - 설정 / 가져오기 / 쓰기 

리얼타임 데이터베이스

: 파이어베이스에서 JSON 데이터를 실시간으로 바로바로 조회하고, 수정 변경할 수 있게 존재시킨 데이터베이스 저장소 

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

리얼타임 데이터베이스 - 설정하기

- firebaseConfig.js 파일 내에 firebaseConfig에 databaseURL 추가하기 

- databaseURL은 실시간 데이터베이스에 들어갔을 때 보이는 주소로 입력한다. 

databaseURL
databaseURL

- 규칙 read와 write 모두 true로 설정하기

- JSON 가져오기로 myapp 디렉토리 안에서 사용중인 data.json 파일 가져오기 


리얼타임 데이터베이스 - 전체 데이터 읽기

- 전체 조회 함수 

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

- 조회한 데이터는 snapshot 부분에 담겨서 {} 내부에서 사용 가능

- 그 중 실제 우리에게 필요한 데이터는 snapshot.val()로 가져와 변수에 담아 사용 가능 

▼ myapp/pages/MainPage.js

  • firebaseConfig.js에서 export한 firebase_db를 import하기
  • useEffect 내에서 firebase_db에서 tip 데이터를 가져와 setState와 setCateState해 준다.
import { firebase_db } from "../firebaseConfig";
  useEffect(() => {
    navigation.setOptions({
      title: "나만의 꿀팁",
    });
    setTimeout(() => {
      firebase_db
        .ref("/tip")
        .once("value")
        .then((snapshot) => {
          let tip = snapshot.val();

          setState(tip);
          setCateState(tip);
          getLocation();
          setReady(false);
        });
    }, 1000);
  }, []);
  • 사용자들 마다 네트워크 상태가 모두 다르기 때문에, 무조건 몇 초 뒤에 실행 시키는 setTimeout 함수 기능보다, 파이어베이스 API의 실행 완료 시간에 맡겨두는 것이 더 낫다. 
  • 파이어베이스로부터 데이터를 가져와 준비할 때까지 로딩 화면을 보여줬다가, 데이터가 준비가되면 실제 본 화면을 보여주는 자연스러운 서비스 환경을 사용자들에게 제공해 줄 수 있다. 


리얼타임 데이터베이스 - 특정 데이터 읽기

  • 현재는 전체 데이터를 상세페이지로 넘겨주어 화면에 출력해주고 있다. 
  • 상세페이지의 데이터들이 실시간으로 변경될 수 있는데, 전체 데이터를 넘겨주는 방식에서는 변경된 데이터가 적용되지 않는다. 
  • 따라서 그때 그때 변경된 데이터가 항상 반영되는 파이어베이스 데이터베이스로부터 가져와야 한다. 
  • 큰 데이터들이 이동하는 것은 앱 퍼포먼스 저하의 원인이다. 

 

-> 팁 idx 번호만 넘겨준 다음 DetailPage.js 에서 idx로 상세 꿀팁 정보를 서버로부터 조회해서 출력해 줄 것이다. 

 

▼ myapp/components/Card.js

  • DetailPage로 이동할 때, content 전체를 넘겨주지 않고 해당 idx 값만 넘겨준다. 
    <TouchableOpacity
      style={styles.card}
      onPress={() => {
        navigation.navigate("DetailPage", { idx: content.idx });
      }}
    >

▼ myapp/pages/DetailPage.js 

  • firebaseConfig.js 로부터 firebase_db를 import한다.
  • useEffect 에서 route.params.idx 값으로 해당 데이터를 파이어베이스에서 가져온다.
    가져온 데이터를 setTip 해준다. 
import { firebase_db } from "../firebaseConfig";
  useEffect(() => {
    navigation.setOptions({
      title: route.params.title,
      headerStyle: {
        backgroundColor: "#000",
        shadowColor: "#000",
      },
      headerTintColor: "#fff",
    });
    const { idx } = route.params;
    firebase_db
      .ref("/tip/" + idx)
      .once("value")
      .then((snapshot) => {
        let tip = snapshot.val();
        setTip(tip);
      });
  }, []);

리얼타임 데이터베이스 - 데이터 수정하기 

- 특정 사용자가 꿀팁 찜 버튼을 눌렀을 때, 사용자마다 고유한 정보를 관리하려면, 사용자 고유 ID값 데이터가 있어야, 어떤 사용자의 데이터인지 구분이 가능하다. 

- expo는 사용할 사용자들의 고유 아이디를 생성해서 알려주므로, 이를 통해 사용자들마다 고유한 ID 값으로 데이터를 관리할 수 있다. 

expo-application

 

Application

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

▼ expo-application 사용방법

  • id값은 운영체제가 ios인지, android인지에 따라 달라지므로, Platform.OS로 현재 os를 확인해야 한다. 
import * as Application from 'expo-application';
const isIOS = Platform.OS === 'ios';


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

console.log(uniqueId)

 

찜 데이터 저장하기

- 데이터를 수정하거나 저장할 때에는 원하는 ref에서 set 함수를 사용한다. 

 

▼ myapp/pages/DetailPage.js 

  • expo-application을 import 한다. 
    현재 os가 무엇인지 isIOS 변수에 저장한다. 
  • like 함수 작성 
    • isIOS에 따라 userUniqueId값을 받아온다.
    • firebase_db에 /like/useUniqueId/tip.idx 의 ref에 접근해서 해당 데이터를 set함수를 이용하여 현재 상세 페이지에서 보여주고 있는 tip으로 저장한다. 
    • 저장이 완료되었으면 "찜 완료!" 알림창을 띄우고, 에러 발생 시, 에러를 콘솔 창에 출력한다. 
  • like 함수를 팁 찜하기 버튼을 클릭했을 때 실행되도록 한다. 
import * as Application from "expo-application";
const isIOS = Platform.OS === "ios";
  const like = async () => {
    let userUniqueId = isIOS
      ? await Application.getIosIdForVendorAsync()
      : await Application.androidId;

    firebase_db
      .ref("/like/" + userUniqueId + "/" + tip.idx)
      .set(tip, (error) => {
        console.log(error);
        Alert.alert("찜 완료!");
      });
  };
          <TouchableOpacity style={styles.button} onPress={() => like()}>
            <Text style={styles.buttonText}>팁 찜하기</Text>
          </TouchableOpacity>

like 디렉토리 안에 찜 한 팁들이 저장되었다.


4주차 과제 - 찜 해제 누르면 찜 삭제

▼ myapp/pages/LikePage.js

더보기
import React, { useState, useEffect } from "react";
import { ScrollView, StyleSheet } from "react-native";
import { StatusBar } from "expo-status-bar";
import LikeCard from "../components/LikeCard";
import { firebase_db } from "../firebaseConfig";
import * as Application from "expo-application";
const isIOS = Platform.OS === "ios";

export default function LikePage({ navigation, route }) {
  const [tip, setTip] = useState([]);

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

  const getLike = async () => {
    let userUniqueId = isIOS
      ? await Application.getIosIdForVendorAsync()
      : Application.androidId;

    firebase_db
      .ref(`/like/${userUniqueId}`)
      .once("value")
      .then((snapshot) => {
        if (snapshot.val()) {
          let likeTip = Object.values(snapshot.val());
          setTip(likeTip);
        }
      });
  };
  return (
    <ScrollView style={styles.container}>
      <StatusBar style="auto" />
      {tip.map((content, i) => {
        return (
          <LikeCard
            content={content}
            key={i}
            navigation={navigation}
            tip={tip}
            setTip={setTip}
          />
        );
      })}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: "#fff",
    marginTop: 10,
    marginLeft: 10,
  },
});​

 

▼ myapp/components/LikeCard.js

더보기
import React from "react";
import {
  Alert,
  View,
  Image,
  Text,
  StyleSheet,
  TouchableOpacity,
} from "react-native";
import { firebase_db } from "../firebaseConfig";
import * as Application from "expo-application";
const isIOS = Platform.OS === "ios";

export default function LikeCard({ navigation, content, tip, setTip }) {
  const remove = async (cidx) => {
    let userUniqueId = isIOS
      ? await Application.getIosIdForVendorAsync()
      : Application.androidId;

    firebase_db
      .ref("/like/" + userUniqueId + "/" + cidx)
      .remove()
      .then(() => {
        Alert.alert("삭제 완료");
        let result = tip.filter((data, i) => {
          return data.idx !== cidx;
        });
        setTip(result);
      });
  };

  return (
    <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.buttonContainer}>
          <TouchableOpacity
            style={styles.button}
            onPress={() => {
              navigation.navigate("DetailPage", { idx: content.idx });
            }}
          >
            <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",
  },
  buttonContainer: {
    flexDirection: "row",
  },
  button: {
    width: 90,
    marginTop: 20,
    marginRight: 10,
    marginLeft: 10,
    padding: 10,
    borderWidth: 1,
    borderColor: "deeppink",
    borderRadius: 7,
  },
  buttonText: {
    color: "deeppink",
    textAlign: "center",
  },
});​

 

찜 해제

profile

DEVELOP

@JUNGY00N