
【12/16】ReactNative+Expoで作るマップアプリ開発入門
はじめに
はじめまして。Marvel株式会社で主にフロントエンドの開発を行っているノッツと申します。
社内での勉強会を通じて React に興味を持ち、個人開発で Expo を用いたアプリ開発を始めたのですが、その作りやすさ・手軽さに感動しました。
この記事では、導入や基本的な React に関する情報は少し省略しつつ Expo + マップアプリ機能の開発に焦点をあて、開発の楽しさを知ってもらえたらと思います。
対象読者
React の基本的な開発環境や仕様を理解している
開発環境
Windows
Android Studio
typescript 5.7.2
React 18.3.1
React Native 0.76.3
Expo 52.0.11
Expo Go
react-native-maps 1.18.0
react-native-maps-directions 1.9.0
そもそも Expo ってなに?
Expo は、React Native によるアプリ開発にあたって必要な機能を取り揃えた開発プラットフォーム(SDKとそれに連なるパッケージの集合体のようなイメージ)です。
なかでも 今回使用する Expo Go は、Expoで開発されているアプリを Expo Goアプリでシミュレーションできるようになっています。
デバッグ用の機能が揃えられていたりホットリロードができるなど、開発のイテレーションが早くなる機能が満載です。
Expo プロジェクトを作成する
まずは、expo をインストールしましょう。
npm install --global expo-cli
つぎに、expo コマンドからプロジェクトを作成します。
プロジェクトを作成したいディレクトリに移動したら、以下のコマンドでプロジェクトを作成することができます。
expo init {プロジェクトの名前}
マップを表示する
まずは、マップを画面に表示してみましょう。
以下のnpmコマンドで必要なパッケージを入手します。
npm install react-native-maps
次に、プロジェクトフォルダ内にある App.tsx に↓のコードを記述します。
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import MapView, {PROVIDER_GOOGLE} from 'react-native-maps';
export default function App() {
// 例として東京タワーを中央に表示
const origin = {latitude: 35.6585805, longitude: 139.742858};
return (
<View style={styles.container}>
<StatusBar style="auto" />
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: origin.latitude,
longitude: origin.longitude,
latitudeDelta: 0.0,
longitudeDelta: 0.0,
}}
>
</MapView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
map: {
width: '100%',
height: '100%',
},
});
エミュレーターで動作確認する(Android)
次に、エミュレーターを起動してマップが表示されることを確認してみましょう。
今回は、Android Studio でエミュレーターを立ち上げて動作確認してみます。
Android Studio を起動したら、最初に表示される画面で More Actions から
Virtual Device Manager を選択します。

Device Manager 左上の + ボタンを押してエミュレートするデバイスを新しく作成します

今回は 、Pixel 7 の設定をそのまま使います。
Pixel 7 を選択したら、そのまま Next 。

APIレベルは 33 の Tiramisu を選択して Next。

最後の画面では、特に何も選択することなく、Finishを押して作成完了で
す。
すると、Device Manager に今作成したデバイスが表示されているはずです。
右端にデバイスを起動するボタンがあるので、押して起動してみましょう。

エミュレーターが起動したら、プロジェクトのルートディレクトリで以下のコマンドを実行します。
yarn start
すると、アプリの動作確認用のQRコードなどが表示されます。

お手持ちのスマートフォンでQRコードを読み取ることで Expo Go をインストールして確認することもできますが、今回は Android エミュレーターで確認をするので、コマンド待機中になっているコンソールで ↓ のように a を打ち込みます。(エミュレーターのUSBデバッグを有効にするのを忘れずに!)

すると、

エミュレーターに Expo Go を自動的にインストールしてくれます。
その後、自動で Expo Go が立ち上がり、↓のようになれば成功です。

マーカーを表示し、ドラッグできるようにする
次にマーカー(ピン)をマップ上に表示してドラッグで動かしてみましょう。
↓のコードを適宜、追加します。
import MapView, {PROVIDER_GOOGLE, Marker} from 'react-native-maps';
// 1つめのマーカー、例として東京タワーに表示
const origin = {latitude: 35.6585805, longitude: 139.742858};
// 2つめのマーカー、例として富士山に表示
const destination = {latitude: 35.3606233, longitude: 138.7067638};
// ↓のコードをMapViewタグ内に追加
<Marker draggable
coordinate={origin} />
// draggable属性を指定するとドラッグ可能なマーカーになる
<Marker draggable
coordinate={destination} />
追加したらエミュレーター画面に戻り、Expo アプリ上で Ctrl + M でデバッグメニューを表示して Reload を選択しましょう。
画面が更新され、↓のような画面になるはずです。

マーカーを長押しし、指の位置にマーカーがついてきてドラッグできることを確認しましょう。

マーカー間の道のりを表示する
次にマーカー同士の経路をマップ上に線で表示してみましょう。
経路を取得するために GoogleMapsAPI の中にある DirectionsAPI を使用するのですが、そのためには APIキー を取得する必要があります。
取得する方法については、以下の記事にとても詳しく載っているのでご参照ください。
次に、標準の経路表示には Directions Renderer を使うのですが、使いやすいようにラップしてくれているパッケージがあるので、今回はそれを使います。
react-native-maps-directions をインストールします。
npm install react-native-maps-directions
↓のコードを適宜追加
import MapViewDirections from 'react-native-maps-directions';
// MapViewタグ内に追記
<MapViewDirections
origin={origin}
destination={destination}
apikey="hogehogehoge" // あなたのGoogle Maps APIキーを入力
/>
追加したら、再びエミュレーター画面に戻り、Ctrl + M を押して Reload を選択しましょう。
すると、↓のような画面になるはずです。
東京タワーと富士山の間の経路が線となって表示されています。

表示されている経路はデフォルトでは DRIVING(車両)になっているので、よく見ると高速道路上に線が引かれているのが分かるかと思います。
どういった移動手段を使っての経路を表示するかは mode で指定することができます。
<MapViewDirections
origin={origin}
destination={destination}
apikey="hogehogehoge"
mode='WALKING' // 移動手段を徒歩に変更
/>
WALKING(徒歩)を指定してみました。
エミュレーターに戻って Reload すると経路が変化したはずです。
mode は他にも、BICYCLING (自転車) , TRANSIT(公共交通機関)があります。
デフォルトの DRIVING(車両)、↑で指定したWALKING(徒歩)を含めると全部で4つですね。
ただ、今回のケースで TRANSIT を指定すると 富士山の頂上まで繋がっている交通機関はないので経路は得られません。
よくよく考えれば分かることなのですが、こういった結果が得られるのも面白いですよね。
ドラッグした後のマーカー間の経路を更新させる
現状、マーカーをドラッグして移動させることはできますが、経路は元のままになっています。
これをドラッグし終わったことを検知し、その都度マーカー間の経路を更新させるように手を加えてみましょう。
Marker コンポーネントには、ドラッグ終了のコールバックが用意されています(onDragEnd )。
このコールバックでマップ上でドラッグ後のピンが置かれた位置(緯度経度)が渡ってくるので、その値をそのままピンの位置に設定すればOKです。
詳しくは、以下のコードとコメントをご覧ください。
// 緯度経度を扱いやすくするために、LatLng(緯度経度を表す型)を追加
import MapView, {PROVIDER_GOOGLE, Marker, LatLng} from 'react-native-maps';
const origin = {latitude: 35.6585805, longitude: 139.742858}; // 例として東京タワー
const destination = {latitude: 35.3606233, longitude: 138.7067638}; // 例として富士山
// ピン位置更新時に再レンダーをフックさせるために開始ピンと終了ピン分を用意
const [originPin, setOriginPin] = useState<LatLng>(origin);
const [destinationPin, setDestinationPin] = useState<LatLng>(destination);
<Marker draggable
coordinate={originPin}
onDragEnd={(e) =>
// coordinateにはマップ上に置かれたピンの緯度経度が入っている
setOriginPin(e.nativeEvent.coordinate)
}
/>
<Marker draggable
coordinate={destinationPin}
onDragEnd={(e) =>
setDestinationPin(e.nativeEvent.coordinate)
}
/>
<MapViewDirections
origin={originPin} // 更新されるピンの座標を渡して再レンダー時に経路も更新されるようにする
destination={destinationPin}
apikey="hogehogehoge" // あなたのGoogle Maps APIキーを入力
/>
↑のコードへの変更が終わったら、エミューレーターに戻って Reload してみましょう。
すると、↓のようにマーカーをドラッグした後にマーカー間の経路が更新されるようになっているはずです。

終わりに
紹介したい機能はまだまだある(経路の移動にかかる時間の取得、特定の施設にピンを立てるなど)のですが、全部書いていると締め切りに間に合わなくなってしまいそうなので、このあたりで以上とさせて頂きます。
今回は楽しそう感が出るようにマップ上で動きがある機能を中心に紹介してみました。
この記事を通して ReactNative + Expo での開発のしやすさや楽しさが伝われば幸いです。
Marvelでは今年も『Marvelアドベントカレンダー2024』をやります🎄🎅Marvelのエンジニアがクリスマスまで記事のバトンを繋ぎます🦌🛷
是非毎日のお楽しみとしてご覧ください🎁
★Marvelのアドベントカレンダーはこちら
https://note.com/marvel_engineer/m/ma7e8d8ae4288
Marvelが少しでも気になった方は是非Wantedlyもご覧ください🙌