React Native/React Native_study

[React Native] #9 FlatList 로 데이터 나타내기

bocoder
728x90
반응형

본 게시글에서는 우리나라 주변 바다의 위치의 좌표를 저장 후, weather API 로 날씨 정보를 읽어와 FlatList 로 표현한다.

 

* 공공데이터 포털 : https://www.data.go.kr/index.do

* 기상청 일일예보 : https://www.weather.go.kr/w/ocean/forecast/daily-forecast.do

 

1. 날씨를 확인할 위치 설정 및 Data.js 파일을 생성하여 좌표값 저장

 

* 기상청 지도에서 대략적인 해양 경계범위를 확인하고, 구글 지도에서 좌표를 가져옴

< 기상청 지도 / 구글 지도 >

* 실제 프로젝트를 진행하면 DB를 별도로 구성하지만, react native 공부 목적으로 소스 내에 위치 정보를 저장함 

# screens > WeatherData > Data.js

export const locationData = [
  {
    id: "1",
    name: "동해북부",
    latitude: "38.9912933625489",
    longitude: "128.55904178990548",
  },
  {
    id: "2",
    name: "동해중부",
    latitude: "38.11561234581255",
    longitude: "129.37031290479666",
  },
  {
    id: "3",
    name: "동해남부",
    latitude: "36.24956575214533",
    longitude: "130.13587860476434",
  },
  {
    id: "4",
    name: "서해북부",
    latitude: "38.6351611420487",
    longitude: "124.23988007963995",
  },
  {
    id: "5",
    name: "서해중부",
    latitude: "36.97413313314397",
    longitude: "125.59961617361242",
  },
  {
    id: "6",
    name: "서해남부",
    latitude: "35.248056938755774",
    longitude: "125.62246888107414",
  },
  {
    id: "7",
    name: "남해서부",
    latitude: "33.93118631207173",
    longitude: "126.92507320639233",
  },
  {
    id: "8",
    name: "남해동부",
    latitude: "34.516946052213186",
    longitude: "129.06180135406336",
  },
  {
    id: "9",
    name: "제주도",
    latitude: "32.862780223120836",
    longitude: "126.42231364223444",
  },
];

 

2. 저장한 위치 정보를 읽어와 FlatList 로 데이터 출력

* FlatList 참고 : https://reactnative.dev/docs/flatlist

* css 는 대략적으로 꾸며보았고, 실제 프로젝트 진행 시 UI/UX 담당자가 기획함
* ListItem 은 향후 Item 클릭 시 action 을 주기위해 TouchableOpacity 로 구성함

* 데이터 있을 때 : waiting -> rederItem 출력

* 데이터 없을 때 : waiting -> ListEmptyComponent 출력

 

# screens > WeatherData > index.js

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

//components
import Header from 'components/Header'

//API request
import axios from "axios";

//data
import { locationData } from "./Data"

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 (
    <Wrapper>
      <Header title='날씨' />
      {isLoading || error
        ? <Text> Waiting.. </Text>
        : <Text> 날씨 : {currentWeather} , 온도 : {temp} </Text>
      }
      <List
        data={locationData}
        renderItem={({ item }) => (
          <ListItem>
            <TitleView>
              <Title>{item.name}</Title>
            </TitleView>
            <Contents>{item.latitude}    {item.longitude}</Contents>
          </ListItem>
        )}
        keyExtractor={item => item.id}
        ListEmptyComponent={() => (
          <EmptyView>
            <Text> 날씨 데이터가 없습니다.</Text>
          </EmptyView>
        )}
      />
    </Wrapper>
  );
};

export default index;

const Wrapper = styled.View`
  flex: 1;
  flex-direction: column;
  background-color: white;
`;

const List = styled.FlatList`
  margin-horizontal: 10px;
  margin-top: 10px;
`;

const ListItem = styled.TouchableOpacity`
  background-color: #E1F5FE;
  margin-bottom: 10px;
  height: 100px;
  borderRadius: 10px;
`

const TitleView = styled.View`
flex: 1;
  align-items: center;
  justify-content: center;
  background-color: #00B0FF;
  padding-horizontal: 10px;
  padding-vertical: 5px;
`
const Title = styled.Text`
  color: white;
  font-size: 17px;
  font-weight: bold;
  `
const Contents = styled.Text`
  flex: 3;
  font-size: 15px;
  padding-horizontal: 10px;
  padding-vertical: 5px;
`

const EmptyView = styled.View`
  align-items: center;
`

 

* 정상적으로 데이터 수신 및 FlatList 로 보여줌

< iOS / Android >

 

* 데이터 정보를 지우고 테스트 시, 설정한 화면이 정상적으로 나옴

< iOS / Android >

 

3. 위치별 좌표값으로 weather API 를 활용하여 각각 날씨 정보 가져오기

* const [data, setData] = useState(locationData ? locationData : []) 로 위치 정보 초기값 저장

* for () 을 통해 weather API 각각 호출 후, setData() 로 날씨 및 기온 정보 추가

 

# screens > WeatherData > index.js

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

//components
import Header from "components/Header";

//API request
import axios from "axios";

//data
import { locationData } from "./Data";

const index = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(false);
  const [data, setData] = useState(locationData ? locationData : []);

  const API_KEY = "{개인별 발급한 key}";

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

    const getWeather = async (data) => {
      try {
        for (let i = 0; i < locationData.length; i++) {
          const resWeather = await axios.get(
            `http://api.openweathermap.org/data/2.5/weather?lat=${data[i].latitude}&lon=${data[i].longitude}&appid=${API_KEY}&units=metric`
          );
          data[i].currentWeather = resWeather.data.weather[0].main;
          data[i].temp = resWeather.data.main.temp;
        }
        if (mounted) {
          setData(data);
          setIsLoading(false);
        }
      } catch (error) {
        Alert.alert("날씨 정보를 읽어올 수 없습니다.");
        setIsLoading(false);
      }
    };

    getWeather(data);

    return () => {
      mounted = false;
    };
  }, [data]);

  return (
    <Wrapper>
      <Header title="날씨" />
      {isLoading ? (
        <Text> Waiting.. </Text>
      ) : (
        <List
          data={data}
          renderItem={({ item }) => (
            <ListItem>
              <TitleView>
                <Title>{item.name}</Title>
              </TitleView>
              <Contents>
                {item.currentWeather} // {item.temp}
              </Contents>
            </ListItem>
          )}
          keyExtractor={(item) => item.id}
          ListEmptyComponent={() => (
            <EmptyView>
              <Text> 데이터 없음 </Text>
            </EmptyView>
          )}
        />
      )}
    </Wrapper>
  );
};

export default index;

const Wrapper = styled.View`
  flex: 1;
  flex-direction: column;
  background-color: white;
`;

const List = styled.FlatList`
  margin-horizontal: 10px;
  margin-top: 10px;
`;

const ListItem = styled.TouchableOpacity`
  background-color: #e1f5fe;
  margin-bottom: 10px;
  height: 100px;
  border-radius: 10px;
`;

const TitleView = styled.View`
  flex: 1;
  align-items: center;
  justify-content: center;
  background-color: #00b0ff;
  padding-horizontal: 10px;
  padding-vertical: 5px;
`;
const Title = styled.Text`
  color: white;
  font-size: 17px;
  font-weight: bold;
`;
const Contents = styled.Text`
  flex: 3;
  font-size: 15px;
  padding-horizontal: 10px;
  padding-vertical: 5px;
`;

const EmptyView = styled.View`
  align-items: center;
`;

 

* 정상적으로 모든 데이터가 잘 수신됨

< iOS / Android >

 

4. 날씨에 따른 icon 보여주기

* react native Image 참고 : https://reactnative.dev/docs/images

* weather API icon 참고 : https://openweathermap.org/weather-conditions

 

# screens > WeatherData > index.js

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

//components
import Header from "components/Header";

//API request
import axios from "axios";

//data
import { locationData } from "./Data";

const index = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(false);
  const [data, setData] = useState(locationData ? locationData : []);

  const API_KEY = "{개인별 발급한 key}";

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

    const getWeather = async (data) => {
      try {
        for (let i = 0; i < locationData.length; i++) {
          const resWeather = await axios.get(
            `http://api.openweathermap.org/data/2.5/weather?lat=${data[i].latitude}&lon=${data[i].longitude}&appid=${API_KEY}&units=metric`
          );
          data[i].currentWeather = resWeather.data.weather[0].main;
          data[i].temp = resWeather.data.main.temp;
          data[i].icon = resWeather.data.weather[0].icon;
        }
        if (mounted) {
          setData(data);
          setIsLoading(false);
        }
      } catch (error) {
        Alert.alert("날씨 정보를 읽어올 수 없습니다.");
        setIsLoading(false);
      }
    };

    getWeather(data);

    return () => {
      mounted = false;
    };
  }, [data]);

  return (
    <Wrapper>
      <Header title="날씨" />
      {isLoading ? (
        <Text> Waiting.. </Text>
      ) : (
        <List
          data={data}
          renderItem={({ item }) => (
            <ListItem>
              <TitleView>
                <Title>{item.name}</Title>
              </TitleView>
              <ContentsView>
                <Icon
                  source={{
                    uri: `http://openweathermap.org/img/wn/${item.icon}@2x.png`,
                  }}
                />
                <Contents>
                  {item.currentWeather} → {item.temp} ℃
                </Contents>
              </ContentsView>
            </ListItem>
          )}
          keyExtractor={(item) => item.id}
          ListEmptyComponent={() => (
            <EmptyView>
              <Text> 데이터 없음 </Text>
            </EmptyView>
          )}
        />
      )}
    </Wrapper>
  );
};

export default index;

const Wrapper = styled.View`
  flex: 1;
  flex-direction: column;
  background-color: white;
`;

const List = styled.FlatList`
  margin-horizontal: 10px;
  margin-top: 10px;
`;

const ListItem = styled.TouchableOpacity`
  background-color: #e1f5fe;
  margin-bottom: 10px;
  height: 80px;
  border-radius: 10px;
`;

const TitleView = styled.View`
  flex: 1;
  align-items: center;
  justify-content: center;
  background-color: #00b0ff;
  padding-horizontal: 10px;
  padding-vertical: 5px;
`;

const Title = styled.Text`
  color: white;
  font-size: 17px;
  font-weight: bold;
`;

const ContentsView = styled.View`
  flex: 2;
  flex-direction: row;
  align-items: center;
  justify-content: center;
`;

const Contents = styled.Text`
  font-size: 15px;
  padding-horizontal: 10px;
  padding-vertical: 5px;
`;

const Icon = styled.Image`
  width: 60px;
  height: 60px;
`;

const EmptyView = styled.View`
  align-items: center;
`;

 

* icon 이 잘 표현됨

* 실제 프로젝트에서는 휴대폰 기종별로 화면 크기가 다르기 때문에, css 작업 시 배율을 계산하여 상대적인 크기로 반영되도록 수정해야 함

< iOS / Android >

 

끝.

728x90
반응형