React Nativeを使ってモバイルデバイスから現在地の位置情報を取得しその場所での天気の情報を取得するシンプルアプリケーションの作成方法について確認していきます。

天気を画面上に表示するアプリを作成していく中で以下のことを学ぶことができます。

  • React Native環境でのTailwind CSSの設定方法
  • デバイスからの位置情報の取得(expo-location)
  • .envファイルを利用して環境変数の設定方法
  • APIを使った天気情報の取得方法(OpenWeatherサービス)
  • React NativeでのReact Hookの使い方(useState, useEffect)

Reactを使って作成した天気アプリを参考に本文書を作成しています。

React Nativeのアプリの作成にExpoを利用します。macOSでのExpoの設定方法については下記の文書を参考に行ってください。

Expoコマンドが実行できる環境を前提に本文書は進めさせてもらいます。

React Nativeプロジェクトの作成

Expoを利用してReact Nativeプロジェクトの作成を行います。expo initコマンドを実行するとテンプレートの選択を行うことができますが本文書では”blank”を選択します。プロジェクト名は任意なので好きな名前をつけてください。ここではreact-native-weatherという名前をつけています。


 % expo init react-native-weather

プロジェクトの作成後、作成したプロジェクトに移動してexpo startコマンドを実行します。expo startコマンドを実行後に”Run on iOS simiulator”をクリックしてiOSシュミレーターを起動します。

管理画面
管理画面

Tailwind CSSの設定

React NativeのStyleSheetを利用する代わりにTailwind CSSを利用してスタイルの設定を行なっていきます。React NativeでTailwind CSSを利用するためのパッケージがいくつかあるようですが本書ではTailwind React Native Classnamesをインストールします。


% npm install twrnc

デフォルトのstylesheetで設定されている内容をTailwind CSSへ変更します。Tailwind CSSを設定する場合はtwのバックスラッシュの中にTailwind CSSのutility Classを設定することになります。


import React from 'react';
import { Text, View } from 'react-native';
import tw from 'twrnc';

export default function App() {
  return (
    <View style={tw`flex-1 items-center justify-center`}>
      <Text>Open up App.js to start working on your app!</Text>
    </View>
  );
}

iOSのシュミレーターで確認すると画面の中央に文字列が表示されます。

Tailwind CSSで中央揃えに設定
Tailwind CSSで中央揃えに設定

React Nativeで現在の位置情報を取得

モバイルアプリケーションの場合はモバイル機器の持ち運びが前提になっているため移動先の位置情報を取得することができます。

位置情報を取得するためexpo-locationを利用します。expo-locationを利用するためにはインストールを行う必要があります。


 % expo install expo-location

インストールが完了したら位置情報の取得方法を確認していきます。

useEffect Hookの中でexpo-locationからimportしたLocationを利用して現在の位置情報を取得します。位置情報は自由に取得できるのものではなく位置情報を取得するためにはrequestForegroundPermissionsAsync関数でユーザに許可を得る必要があります。ユーザが画面上から許可を行い、Location.requestForegroundPermissionsAsyncから戻されるstatusの値が”granted”の場合のみ位置情報の取得を行います。


import React, { useEffect } from 'react';
import { Text, View } from 'react-native';
import tw from 'twrnc';
import * as Location from 'expo-location';

export default function App() {
  useEffect(() => {
    const getLocation = async () => {
      let { status } = await Location.requestForegroundPermissionsAsync();

      if (status !== 'granted') {
        console.log('許可がないため位置情報を取得することはできません。');
        return;
      }
      let location = await Location.getCurrentPositionAsync({});
      console.log(location);
    };
    getLocation();
  }, []);

  return (
    <View style={tw`flex-1 items-center justify-center`}>
      <Text>Open up App.js to start working on your app!</Text>
    </View>
  );
}

iOSのシュミレーターを開くと”Expo Go”に位置情報を取得する許可を与えるかどうかの確認画面が表示されます。Don’t Allowを選択した場合は許可を与えないので位置情報を取得することができません。

Expoに位置情報の取得を許可するかどうかの確認画面
Expoに位置情報の取得を許可するかどうかの確認画面

Allow OnceまたはAllow While Using Appを選択した場合はReact Nativeからデバイスの位置情報を取得しコンソールに下記の情報が取得されます。


Object {
  "coords": Object {
    "accuracy": 5,
    "altitude": 0,
    "altitudeAccuracy": -1,
    "heading": -1,
    "latitude": 37.785834,
    "longitude": -122.406417,
    "speed": -1,
  },
  "timestamp": 1638170852190.5452,
}

latitude(緯度)が37.78534、longitude(経度)が-122.406417であることがわかります。この値はシュミレーターのメニューのFeaturesのLocationのCustom Locationで設定が行われており変更することができます。

緯度、経度の設定値を確認
緯度、経度の設定値を確認

デフォルトではサンフランシスコに設定されているようなので変更を行います。緯度と経度を取得したい場合はGoogle Mapなどを利用してください。ここで設定した場所は後ほどわかります。

緯度と経度の変更
緯度と経度の変更

変更後にiOSのシュミレーターのリロードを行うと変更した値に変わっていることが確認できます。


Object {
  "coords": Object {
    "accuracy": 5,
    "altitude": 0,
    "altitudeAccuracy": -1,
    "heading": -1,
    "latitude": 35.71026,
    "longitude": 139.81073,
    "speed": -1,
  },
  "timestamp": 1638171517931.7952,
}

Expo Goを利用して作成したコードを実機のiPhoneで確認すると現在地の場所が位置情報の取得の許可画面が表示されます。ここまでの動作確認でReact Nativeにおける位置情報の取得方法を理解することができました。

現在地の天気情報を取得

天気情報は外部から取得する必要があるためOpenWeatherサービスのAPIを利用します。無料で利用することができますがOpenWeatherのAPIを利用するためにはアカウントの作成が必要です。

アカウントの作成

OpenWeatherのサイトにアクセスします。ページの右上にある”Sing in”ボタンをクリックしてください。

OpenWeatherのトップページ
OpenWeatherのトップページ

サインインの画面が表示されますがアカウントの作成ができていないので”Create an Account”をクリックしてください。

Sign inページ
Sign inページ

アカウント作成ページが表示されるので必要な項目を入力して”Create Account”ボタンをクリックしてください。

アカウント作成ボタン
アカウント作成ボタン

アカウント作成後に会社名と目的の入力フォームが表示されますが”Cancel”を選択することも可能です。

アカウント作成後の質問
アカウント作成後の質問

登録したメールアドレス宛にメールアドレスの確認メールが来ているのでメールを開き、”Verify your email”をクリックしてください。

メールアドレスの確認
メールアドレスの確認

APIのKeyの確認

アカウントが作成できるとデフォルトで設定されているAPI Keyを利用することができます。デフォルトのAPI Keyが表示されていますが利用できるまでに少し時間がかかる場合があります。利用ができない時にAPI keysでアクセスすると401 unauthorizedエラーが表示されます。

API keysのタグをクリックすると名前がDefaultのKeyを利用することができます。Keyは追加することも可能です。

APIキーを確認する
APIキーを確認する

このDefaultのKeyを利用してOpenWeatherから天気の情報を取得します。

APIの利用方法の確認

APIを利用して外部のサービスから情報を取得するためには主に3つの情報が必要となります。1つは先ほど取得したAPIキー、2つ目はアクセスを行うURLで3つ目はパラメータです。URLとパラメータの情報はドキュメントに記載されているのでドキュメントを確認する必要があります。

OpenWeaterの上部にあるAPIタグをクリックすると利用できるWether APIが表示されます。今回はCurrent Weather Data(現在の天候データ)を利用するので”API Doc”をクリックしてください。

ドキュメントの確認
ドキュメントの確認

ページを開くとすぐにAPIのURLとパラメータを確認することができます。緯度と経度を利用するためBy geographic coordinatesを確認してください。URLと3つのパラメータが必要なことがわかります。


api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}

ブラウザからの天気の取得

latitude, longitudeとAPIキーを設定してどのような情報が取得できるのか確認してみましょう。URLとパラメータ、APIキーがわかっているのでブラウザからも天気の取得することができます。ドキュメントに記載されたルールにしたがってURLを作成し、ブラウザのURL欄に記述します。


https://api.openweathermap.org/data/2.5/weather?lat=35.71026&lon=139.81073&appid=88b2XXXXXXXXXXXXX
APIキーを作成したばかりの場合は401エラーが発生するのでしばらく時間をおいてから実行してください。

下記のようにブラウザ上にはOpenWeatherから戻されたJSONデータが表示されます。東京スカイツリーの緯度と経度をGoogleMapから取得して設定を行っています。現在の天気は曇りだということがわかります。


{
"coord": {
"lon": 139.8107,
"lat": 35.7103
},
"weather": [
{
"id": 803,
"main": "Clouds",
"description": "broken clouds",
"icon": "04n"
}
],
"base": "stations",
"main": {
"temp": 284.91,
"feels_like": 283.86,
"temp_min": 281.96,
"temp_max": 285.18,
"pressure": 1026,
"humidity": 66
},
"visibility": 10000,
"wind": {
"speed": 1.54,
"deg": 60
},
"clouds": {
"all": 75
},
"dt": 1638173651,
"sys": {
"type": 1,
"id": 8074,
"country": "JP",
"sunrise": 1638135012,
"sunset": 1638170888
},
"timezone": 32400,
"id": 1857140,
"name": "Minamisenju",
"cod": 200
}

緯度と経度が分かればその場所の天気の情報が取得できることがわかりました。

React Nativeから天気情報を取得

モバイルデバイスからの位置情報の取得とブラウザからOpenWeatherのAPIを利用して天気の情報が取得することができたので一連の処理をすべてReact Nativeで行えるように更新していきます。

.envファイルの設定

OpenWeatherのAPIキーを環境変数として.envファイルに保存できるようにreact-native-dotenvライブラリをインストールします。


 % npm install react-native-dotenv

インストール完了後、babel.config.jsファイルにreact-native-dotenvに関する設定を追加します。設定値はreact-native-dotenvのgithubのページを参考に設定しています。


module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: [
      [
        'module:react-native-dotenv',
        {
          envName: 'APP_ENV',
          moduleName: '@env',
          path: '.env',
          blocklist: null,
          allowlist: null,
          blacklist: null, // DEPRECATED
          whitelist: null, // DEPRECATED
          safe: false,
          allowUndefined: true,
          verbose: false,
        },
      ],
    ],
  };
};

babel.config.jsを更新したらサーバを再起動する必要があるのでexpo startを停止し再度実行してください。

.envファイルをプロジェクトフォルダ直下に保存してOpenWeatherのURLとAPI KEYを.envファイルに保存します。


WEATHER_URL=https://api.openweathermap.org/data/2.5/weather
WEAHTER_API_KEY=88b2f81XXXXXXXXXXXXXXXXXXXXXX

.envファイルに記述した環境変数は下記のようにコンポーネントからimportすることで利用することができます。


import { WEATHER_URL, WEAHTER_API_EKY } from '@env';

天気情報の取得

位置情報から取得したデータ(location)の中からlatitude, longitudeを取り出します。ブラウザで確認したURLの形式を環境変数とlatitude, longitudeで作成してfetch関数の引数にしています。取得したデータをsetWeather関数でweatherの値に設定します。


import React, { useState, useEffect } from 'react';
import { Text, View } from 'react-native';
import tw from 'twrnc';
import * as Location from 'expo-location';
import { WEATHER_URL, WEAHTER_API_KEY } from '@env';

export default function App() {
  const [weather, setWeather] = useState('');
  useEffect(() => {
    const getLocation = async () => {
      try {
        let { status } = await Location.requestForegroundPermissionsAsync();

        if (status !== 'granted') {
          console.log('許可がないため位置情報を取得することはできません。');
          return;
        }

        let location = await Location.getCurrentPositionAsync();

        const { latitude, longitude } = location.coords;

        const response = await fetch(
          `${WEATHER_URL}?lat=${latitude}&lon=${longitude}&appid=${WEAHTER_API_KEY}`
        );

        const result = await response.json();
                console.log(result)

        setWeather(result);
      } catch (error) {
        console.log(error.message);
      }
    };
    getLocation();
  }, []);

//略

動作確認を行うとコンソールに取得したデータ(result)が表示されます。天気の状態はObjectのweatherの配列の1番目の要素のmainに入っていることがわかります。


Object {
  "base": "stations",
  "clouds": Object {
    "all": 12,
  },
  "cod": 200,
  "coord": Object {
    "lat": 35.7103,
    "lon": 139.8107,
  },
  "dt": 1638192166,
  "id": 1857140,
  "main": Object {
    "feels_like": 282.71,
    "grnd_level": 1027,
    "humidity": 41,
    "pressure": 1027,
    "sea_level": 1027,
    "temp": 284.45,
    "temp_max": 284.45,
    "temp_min": 284.45,
  },
  "name": "Minamisenju",
  "sys": Object {
    "country": "JP",
    "sunrise": 1638135012,
    "sunset": 1638170888,
  },
  "timezone": 32400,
  "visibility": 10000,
  "weather": Array [
    Object {
      "description": "few clouds",
      "icon": "02n",
      "id": 801,
      "main": "Clouds",
    },
  ],
  "wind": Object {
    "deg": 349,
    "gust": 2.73,
    "speed": 1.83,
  },
}

weatherに値が入っている場合のみ画面上に天気を表示させるように設定を行います。


return (
  <View style={tw`flex-1 items-center justify-center`}>
    {weather ? (
      <Text>{weather.weather[0].main}</Text>
    ) : (
      <Text>天気情報が取得できませんでした。</Text>
    )}
  </View>
);

シュミレータで確認すると最初に”天気情報が取得できませんでした。”が表示されます。その後天気の情報に画面の内容が置き換わります。現在の天気はCloudsであることがわかります。

画面上に天気の情報が表示
画面上に天気の情報が表示

Loadingの設定

OpenWeatherのサービスから天気情報を取得するまでweatherの変数が””(blank)のため条件分岐により”天気情報が取得できませんでした。”が表示されます。新たにloading変数を追加して天気情報が取得できるまで画面上にLoainding…が表示されるように設定します。


import React, { useState, useEffect } from 'react';
import { Text, View } from 'react-native';
import tw from 'twrnc';
import * as Location from 'expo-location';
import { WEATHER_URL, WEAHTER_API_KEY } from '@env';

export default function App() {
  const [weather, setWeather] = useState('');
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    const getLocation = async () => {
      try {
        let { status } = await Location.requestForegroundPermissionsAsync();

        if (status !== 'granted') {
          console.log('許可がないため位置情報を取得することはできません。');
          return;
        }

        let location = await Location.getCurrentPositionAsync();

        const { latitude, longitude } = location.coords;

        const response = await fetch(
          `${WEATHER_URL}?lat=${latitude}&lon=${longitude}&appid=${WEAHTER_API_KEY}`
        );

        const result = await response.json();

        setLoading(false);
        setWeather(result);
      } catch (error) {
        setLoading(false);
        console.log(error.message);
      }
    };
    getLocation();
  }, []);

  if (loading) {
    return (
      <View style={tw`flex-1 items-center justify-center`}>
        <Text>Loaindg...</Text>
      </View>
    );
  }

  return (
    <View style={tw`flex-1 items-center justify-center`}>
      {weather ? (
        <Text>{weather.weather[0].main}</Text>
      ) : (
        <Text>天気情報が取得できませんでした。</Text>
      )}
    </View>
  );
}

設定後、画面には一瞬”Loading…”の文字が表示されますがその後天気の情報が表示されます。

コンポーネント化

ここまでに記述した内容をコンポーネント化して再利用できるようにしましょう。プロジェクトフォルダ直下にcomponentsを作成しWeather.jsファイルを作成します。


import React, { useState, useEffect } from 'react';
import { View, Text } from 'react-native';
import tw from 'twrnc';
import * as Location from 'expo-location';
import { WEATHER_URL, WEAHTER_API_KEY } from '@env';

const Weather = () => {
  const [weather, setWeather] = useState('');
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    const getLocation = async () => {
      try {
        let { status } = await Location.requestForegroundPermissionsAsync();

        if (status !== 'granted') {
          console.log('許可がないため位置情報を取得することはできません。');
          return;
        }

        let location = await Location.getCurrentPositionAsync();

        const { latitude, longitude } = location.coords;

        const response = await fetch(
          `${WEATHER_URL}?lat=${latitude}&lon=${longitude}&appid=${WEAHTER_API_KEY}`
        );

        const result = await response.json();

        setLoading(false);
        setWeather(result);
      } catch (error) {
        setLoading(false);
        console.log(error.message);
      }
    };
    getLocation();
  }, []);

  if (loading) {
    return (
      <View style={tw`flex-1 items-center justify-center`}>
        <Text>Loaindg...</Text>
      </View>
    );
  }
  return (
    <View style={tw`flex-1 items-center justify-center`}>
      {weather ? (
        <Text>{weather.weather[0].main}</Text>
      ) : (
        <Text>天気情報が取得できませんでした。</Text>
      )}
    </View>
  );
};

export default Weather;

作成したWeather.jsはApp.jsファイルからimportします。


import React from 'react';
import Weather from './components/Weather';

export default function App() {
  return <Weather />;
}

スタイルの設定

画面の中央に現在の天気が表示されているだけなのでTailwind CSSを利用してスタイルを設定していきます。

デフォルトのままでは気温が華氏で表示させるので摂氏に表示されるようにOpenWeatherへのURLの最後に&units=metricを追加します。天気に合わせてアイコン画像を取得することができるので.envにICON_URLのICON_URL=https://openweathermap.org/img/wを追加しています。


<View style={tw`flex-1 items-center justify-center bg-blue-100`}>
  {weather ? (
    <>
      <Image
        source={{ uri: `${ICON_URL}/${weather.weather[0].icon}.png` }}
        style={tw`w-32 h-32`}
      />
      <Text style={tw`text-2xl font-bold`}>{weather.name}</Text>
      <Text style={tw`text-6xl font-bold mt-4 mb-2`}>
        {Math.floor(weather.main.temp)}°
      </Text>
      <Text style={tw`text-xl font-bold`}>{weather.weather[0].main}</Text>
      <View style={tw`flex flex-row items-center mt-4`}>
        <Text style={tw`text-lg font-bold`}>
          最高:{Math.floor(weather.main.temp_max)}°
        </Text>
        <Text style={tw`text-lg font-bold ml-1`}>
          最低:{Math.floor(weather.main.temp_min)}°
        </Text>
      </View>
    </>
  ) : (
    <Text>天気情報が取得できませんでした。</Text>
  )}
</View>

設定後画面を確認すると現在の天気と温度、最高気温と最低気温、場所情報が表示されます。

画面上に現在の天気情報を表示
画面上に現在の天気情報を表示

実機のiPhoneを利用すると現在地付近の天気が表示されます。