【React Native】簡単なAPI連携のアプリケーションを作成する　④天気情報表示の実装

# はじめに

本記事は下記の記事の続編になります。
[【React Native】簡単なAPI連携のアプリケーションを作成する　①準備編](https://qiita.com/kazuki_yoshida/items/d5f47e4d39a9e54f091d)
[【React Native】簡単なAPI連携のアプリケーションを作成する　②住所検索の実装](https://qiita.com/kazuki_yoshida/items/cb7df8d51dc0e0c2f8de)
[【React Native】簡単なAPI連携のアプリケーションを作成する　③ESLint+Prettierの導入(寄り道)](https://qiita.com/kazuki_yoshida/items/4365b69cf80f1f65692b)
実践的なアプリを作成するというよりは、ハンズオン的にReact Nativeを触ってみるといった趣旨になっています。
今回で最終回の予定です。それではやっていきましょう。

## 本記事で説明すること
* axiosの利用(②の記事から引き続き利用します)
* APIへのリクエスト(天気情報の取得)

## 今回使用するAPI
* [Current weather data(天気情報API)](https://openweathermap.org/current)
<br>以下の記事も参考にさせていただきました:pray:<br>
[無料天気予報APIのOpenWeatherMapを使ってみる](https://qiita.com/nownabe/items/aeac1ce0977be963a740)

## githubリポジトリ（追記：削除済み）
https://github.com/pbyoshida/postal-app

# 本編
まずは上記APIを使用するために、会員登録とAPIキーの発行を行います。

## 会員登録・APIキーの発行
[Sign Upページ](https://home.openweathermap.org/users/sign_up)で必要事項を入力し、会員登録を行います。
会員登録後、確認メールが届きますので、emailを有効化します。
その後、マイページ内の「API keys」のページにAPIキーがありますので、そちらを使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289995/542f9ae6-a8e9-4a72-f734-4f8440029f1c.png)

## APIの仕様
詳細は[ドキュメント](https://openweathermap.org/current)を参照ください。
今回は郵便番号で検索し、現在の天気を表示しますので「By ZIP code」の項目を参照します。
URIは以下です。

```
api.openweathermap.org/data/2.5/weather?zip={zip code},{country code}&lang=ja&appid={API key}
```
なお、天気の詳細は日本語で表示したいので、`&lang=ja`のパラメータを付与しています。

## APIキー準備編
早速今回の修正版を載せていきます。
まずは、APIキーはハードコーディングしたくないので、env.jsというファイル内に記載します。

```js:env.js
export const weatherAPIKey = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
```
githubのリポジトリには上記ファイルはアップしていませんが、env.js.sampleという名前で
それっぽいファイルを作成しています。試したい場合には自身でAPIキーを発行後、env.js内に記載してください。

## アプリ本体の修正
続いてアプリの本体です。

```jsx:App.jsx
import React, { useState } from 'react';
import { StyleSheet, Text, View, TextInput, Button, Alert } from 'react-native';
import axios from 'axios';

import { weatherAPIKey } from './env';

// 郵便番号検索APIのURL
const zipcloudURL = 'https://zipcloud.ibsnet.co.jp/api/search';

// OpenWeatherAPIのURL
const weatherURL = 'http://api.openweathermap.org/data/2.5/weather';

export default function App() {
  // 各種値を保存しておくstate
  const [postalCode, setPostalCode] = useState('');
  const [address, setAddress] = useState('');
  const [weather, setWeather] = useState('');

  // axiosのGETメソッドを使った住所検索
  const fetchAddress = async () => {
    try {
      const response = await axios.get(`${zipcloudURL}?zipcode=${postalCode}`);
      const { data } = response;
      if (!data.results) {
        return '該当する住所はありませんでした。';
      }
      switch (data.status) {
        case 200:
          // 今回はテストなので、同じ郵便番号で2件以上存在する場合は除きます
          return `${data.results[0].address1}${data.results[0].address2}${data.results[0].address3}`;
        case 400:
          return data.message;
        case 500:
          return data.message;
        default:
          return '予期しない動作です';
      }
    } catch (error) {
      return '検索失敗';
    }
  };

  // axiosのGETメソッドを使った天気情報取得
  const fetchWeather = async () => {
    try {
      const postalCode1 = postalCode.substr(0, 3);
      const postalCode2 = postalCode.substr(3, 4);
      const response = await axios.get(
        `${weatherURL}?zip=${postalCode1}-${postalCode2},JP&lang=ja&appid=${weatherAPIKey}`
      );
      const { data } = response;
      return data.weather[0].description;
    } catch (error) {
      return '天気情報の取得に失敗しました。';
    }
  };

  // 送信ボタンを押した時に実行される関数
  async function handlePress() {
    // 7桁の数字を正規表現で置きます
    const pattern = /^[0-9]{7}$/;
    if (pattern.test(postalCode)) {
      const searchedAddress = await fetchAddress();
      setAddress(searchedAddress);
      const currentWeather = await fetchWeather();
      setWeather(currentWeather);
    } else {
      // 想定していない文字列の場合
      Alert.alert('正しい郵便番号ではありません', 'もう一度入力してください');
    }
  }

  // アプリに描画する内容
  return (
    <View style={styles.container}>
      <Text style={styles.description}>
        郵便番号を入力してください
        {'\n'}
        (ハイフンなし7桁)
      </Text>
      <TextInput
        value={postalCode}
        style={styles.inputPostalCode}
        onChangeText={(text) => {
          setPostalCode(text);
        }}
        maxLength={7}
        keyboardType="numeric"
        placeholder="郵便番号"
      />
      <Button title="送信" color="#AAAAAA" onPress={handlePress} />
      {address.length > 0 && (
        <View style={styles.addressContainer}>
          <View style={styles.addressLabel}>
            <Text style={styles.addressLabelText}>住所</Text>
          </View>
          <View style={styles.address}>
            <Text style={styles.addressText}>{address}</Text>
          </View>
        </View>
      )}
      {weather.length > 0 && (
        <View style={styles.weatherContainer}>
          <View style={styles.weatherLabel}>
            <Text style={styles.weatherLabelText}>現在の天気</Text>
          </View>
          <View style={styles.weather}>
            <Text style={styles.weatherText}>{weather}</Text>
          </View>
        </View>
      )}
    </View>
  );
}

// 各要素のスタイル
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  description: {
    fontSize: 18,
    color: '#666666',
    paddingBottom: 10,
  },
  inputPostalCode: {
    textAlignVertical: 'center',
    width: 120,
    fontSize: 24,
    marginBottom: 10,
    paddingLeft: 5,
    borderWidth: 1,
    borderColor: 'rgba(0, 0, 0, 0.2)',
  },
  addressContainer: {
    paddingVertical: 10,
    width: 280,
  },
  addressLabel: {
    paddingBottom: 10,
  },
  addressLabelText: {
    fontSize: 18,
    color: '#666666',
  },
  address: {
    paddingLeft: 5,
  },
  addressText: {
    fontSize: 16,
    color: '#000000',
  },
  weatherContainer: {
    paddingVertical: 10,
    width: 280,
  },
  weatherLabel: {
    paddingBottom: 10,
  },
  weatherLabelText: {
    fontSize: 18,
    color: '#666666',
  },
  weather: {
    paddingLeft: 5,
  },
  weatherText: {
    fontSize: 16,
    color: '#000000',
  },
});
```
追加したポイントは以下になります。

* APIキーのインポート
* OpenWeatherAPIのURL
* 天気の情報を格納しておくuseState
* APIにリクエストし天気情報を取得するfetchWeather関数
* 送信ボタンを押した後にfetchWeatherを呼び出す処理
* 取得した後の現在の天気を表示する処理

前回の郵便番号検索と比較するとエラー処理とかは結構手抜きです。。
また、`data.weather[0].description`の箇所で天気の詳細のみ取得していますが、レスポンス自体には気温(K)、湿度、気圧など今回使用した以外の項目も多彩に含まれておりますので、気になった方はそちらの表示も試してみてください。
また、今回のAPIでは郵便番号はハイフン有りで指定しなければいけないので、そこだけ文字列をゴニョゴニョしています。
前回のAPIも実はハイフン有りの郵便番号に対応していたので、最初からTextInputを2つにしても良いかと思いました。

## 動作確認
さて、実機で動作を確認していきます。
<img width="400" alt="1送信ボタン押下前.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289995/13ae33e3-0f03-cfbe-59db-459606c73c27.png">
　　　　　　↓送信ボタン押下
<img width="400" alt="2結果表示後.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289995/404a7b0c-198a-6e7a-43a5-0c43af86b618.png">

はい、上記のように天気も表示することができました。

## 問題点
上記のAPIなんですが、実はそんなに日本の郵便番号が網羅されているわけではなさそうです。
生まれた県である秋田県秋田市内の郵便番号を指定してみます。
<img width="400" alt="3取得失敗時.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289995/94f51eeb-6ad8-42b3-2146-1f90e1f0e93b.png">
住所の検索は行えていますが、天気情報は取得できていません。
上記の解決法ですが、[OpenWeatherMapで郵便番号が使えないときのアプローチ](https://sho0126hiro.hatenablog.com/entry/2019/08/01/131532)の記事で紹介されているように、他のAPIを使って緯度経度を調べてから、緯度経度の指定で天気情報を取得する方法があるようです。
尻すぼみですが、ハンズオン的な内容なので今回は色々と目をつぶろうと思います。

## 最後に
今回で一旦React Nativeのハンズオンとしては終了になります。
シリーズを通じてReact Nativeでの開発環境の構築と、APIを通じた情報取得といった部分を中心に解説しました。
ただReactとかの仕様やビルド周りはあんまり解説できていないので、その辺りは機会があれば記事にしようと思います。
本記事ではネット上で自由に使えるAPIを使用しましたが、実際に開発する際には自分でバックエンドのAPIを用意したり、Firebaseを使ってサクッとバックエンド周りを実装するなどあると思います。
次は勉強としてRuby on RailsとかでバックエンドのAPIを作成して、スマホアプリと繋いでみるということもしてみたいですね。