React Native/React Native_study

[React Native] #8 Open API를 활용한 날씨 데이터 가져오기

bocoder
728x90
반응형

본 게시글에서는 Open API 를 호출해 날씨 데이터를 읽어온다.

 

1. API request 를 위해 axios module 추가

* 참고 : https://www.npmjs.com/package/axios

yarn add axios

 

 

2. weather API 호출 규격 및 개인 key 확인

* weather API : https://openweathermap.org/current

* [Home > API > Current Weather Data] 확인 시, 좌표값으로 날씨 정보를 읽어 올 수 있는 규격 있음

* 회원가입 후 [Home > My API keys] 에서 개인 key 확인 가능

 

 

3. weather API 테스트

* isLoading || error = true 일 때, 별도로 <Text> 를 구성하여 'Wating...'  표시 

* API_KEY 는 개인이 발급 받은 key 값 입력

* latitude, longitude 임의값을 입력하여 API response sample을 화면에 display 

* response 값이 JSON 형식이므로  JSON.stringify(resWeather) 사용하여 text 로 변환

* useEffect 를 사용하여 WeatherData tab 으로 전환할 때마다 API 호출

 

# screens > WeatherData > index.js

import React, { useEffect, useState } from "react";
import { Text, View, Alert } from "react-native";

//components
import Header from 'components/Header'

//API request
import axios from "axios";

const index = () => {

  const [isLoading, setIsLoading] = useState(true);
  const [currentWeather, setCurrentWeather] = useState('');
  const [error, setError] = useState(false);


  const API_KEY = "{개인별 발급한 key}";
  const latitude = 38;
  const longitude = 128;

  const getWeather = async (latitude, longitude) => {
    try {
      const resWeather = await axios.get(
        `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
      );

      let res = JSON.stringify(resWeather);
      console.log('[LOG] resWeather : ' + res);
      setCurrentWeather(res);
      
    } catch (error) {
      Alert.alert("날씨 정보를 읽어올 수 없습니다.")
      setError(true);
      
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    getWeather(latitude, longitude);
  }, []);

  return (
    <View>
      <Header title='날씨' />
      {isLoading || error
        ? <Text> Waiting.. </Text>
        : <Text> 날씨 : {currentWeather} </Text>
      }
    </View>
  );
};

export default index;

 

* case 별로 테스트 시 모두 잘 동작함

 

* [LOG] 확인해보면 API response 값이 아래와 같이 나오고, 여기서 활용할 "main", "temp" 만 가져올 예정

 

4. 좌표값에 따른 '날씨', '온도' 값 출력

* Math.round() 로 정수만 출력

* return () 값을 하나의 요소로 감싸 주어야 하기 때문에 <> 로 감싸 주도록 함

 

# screens > WeatherData > index.js

import React, { useEffect, useState } from "react";
import { Text, View, Alert } from "react-native";

//components
import Header from 'components/Header'

//API request
import axios from "axios";

const index = () => {

  const [isLoading, setIsLoading] = useState(true);
  const [currentWeather, setCurrentWeather] = useState('');
  const [temp, setTemp] = useState('');
  const [error, setError] = useState(false);


  const API_KEY = "{개인별 발급한 key}";
  const latitude = 38;
  const longitude = 128;

  const getWeather = async (latitude, longitude) => {
    try {
      const resWeather = await axios.get(
        `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
      );

      let _main = resWeather.data.weather[0].main;
      let _temp = resWeather.data.main.temp;

      setCurrentWeather(_main);
      setTemp(_temp);

    } catch (error) {
      Alert.alert("날씨 정보를 읽어올 수 없습니다.")
      setError(true);

    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    getWeather(latitude, longitude);
  }, []);

  return (
    <View>
      <Header title='날씨' />
      {isLoading || error
        ? (<Text> Waiting.. </Text>)
        : (
          <>
            <Text> WEATHER is {currentWeather}</Text>
            <Text> TEMP is {Math.round(temp)} </Text>
          </>
        )
      }
    </View>
  );
};

export default index;

< iOS / Android >

 

* 메모리 누수 관련 Warning 발생 (Can't perform a React state update on an unmounted component.)

* 발생 : 천천히 Bottom tab 을 [날씨 > 설정] 으로 이동할 때는 발생하지 않았는데, 빠르게 이동하면 발생

* 이유 : 서버에서 API response 값을 받아오기 전에 Bottom tab 을 이동하게 되면, [날씨] 화면이 unmount 된 상태에서 API response를 받게 되고, 이후 setCurrentWeather(), setTemp(), setIsLoading() 이 동작하게 되면서 불필요한 메모리를 사용함

 

 

5. 메모리 누수 해결

* mounted = false 설정을 통해, API response가 늦을 시 setCurrentWeather(), setTemp() 동작 방지

* setIsLoading 위치 변경

* useEffect() 내부에 getWeather() 가 위치하도록 변경

 

# screens > WeatherData > index.js

import React, { useEffect, useState } from "react";
import { Text, View, Alert } from "react-native";

//components
import Header from 'components/Header'

//API request
import axios from "axios";

const index = () => {

  const [isLoading, setIsLoading] = useState(true);
  const [currentWeather, setCurrentWeather] = useState('');
  const [temp, setTemp] = useState('');
  const [error, setError] = useState(false);


  const API_KEY = "{개인별 발급한 key}";
  const latitude = 38;
  const longitude = 128;

  useEffect(() => {
    let mounted = true;

    const getWeather = async (latitude, longitude) => {
      try {
        const resWeather = await axios.get(
          `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`
        );

        if (mounted) {
          let _main = resWeather.data.weather[0].main;
          let _temp = resWeather.data.main.temp;

          setCurrentWeather(_main);
          setTemp(_temp);
          setIsLoading(false);
        }

      } catch (error) {
        Alert.alert("날씨 정보를 읽어올 수 없습니다.")
        setError(true);
        setIsLoading(false);
      }
    };

    getWeather(latitude, longitude);

    return () => {
      mounted = false
    }

  }, []);

  return (
    <View>
      <Header title='날씨' />
      {isLoading || error
        ? <Text> Waiting.. </Text>
        : <Text> 날씨 : {currentWeather} , 온도 : {temp} </Text>
      }
    </View>
  );
};

export default index;

 

* [지도] 화면에서도 동일한 메모리 누수 현상 발생하여 수정

 

# screens > Map > index.js

* mounted = false 설정을 통해, API response가 늦을 시 setLocation() 동작 방지

import React, { useState, useEffect } from "react";
import { StyleSheet } from "react-native";

//components
import Header from 'components/Header'

//google map
import MapView, { PROVIDER_GOOGLE } from 'react-native-maps';

//location
import * as Location from 'expo-location';

const index = ({ navigation }) => {

  const [initialRegion, setInitialRegion] = useState({
    latitude: 35.91395373474155,
    longitude: 127.73829440215488,
    latitudeDelta: 5,
    longitudeDelta: 5,
  })
  const [mapWidth, setMapWidth] = useState('99%');
  const [location, setLocation] = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);


  // Update map style to force a re-render to make sure the geolocation button appears
  const updateMapStyle = () => {
    setMapWidth('100%')
  }

  // Get current location information 
  useEffect(() => {
    let mounted = true;

    (async () => {
      let { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        setErrorMsg('Permission to access location was denied');
        return;
      }

      let location = await Location.getCurrentPositionAsync({});

      if (mounted) {
        setLocation(location);
      }
    })();

    return () => {
      mounted = false
    }

  }, []);

  let text = 'Waiting..';
  if (errorMsg) {
    text = errorMsg;
  } else if (location) {
    text = JSON.stringify(location);
    console.log('[LOG] current location : ' + text);
  }

  return (
    <Wrapper>
      <Header title='지도' />
      <MapView
        initialRegion={initialRegion}
        style={[styles.map, { width: mapWidth }]}
        provider={PROVIDER_GOOGLE}
        showsUserLocation={true}
        showsMyLocationButton={true}
        onMapReady={() => {
          updateMapStyle()
        }}
      />
    </Wrapper>
  );
};

export default index;

const Wrapper = styled.View`
  flex: 1;
  flex-direction: column;
`;
const styles = StyleSheet.create({
  map: {
    flex: 1,
    width: '100%',
    height: '100%',
  },
});

 

끝.

728x90
반응형