2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

viviONAdvent Calendar 2024

Day 23

初めてのExpoで東京のクリスマスイベントマップを表示してみる 🎄

Last updated at Posted at 2024-12-22

viviONグループでは、DLsiteやcomipoなど、二次元コンテンツを世の中に届けるためのサービスを運営しています。
ともに働く仲間を募集していますので、興味のある方はこちらまで。

普段はVue3(Nuxt3)をメインでWeb開発をしています。
今回はReact NativeをベースとしたExpoというフレームワークを試してみます。
(Web / Androidでの動作を確認します)

:lollipop: プロジェクトの作成

今回はクリスマスイベントスポット(東京)の表示を試してみます(申し訳程度のクリスマス要素)
イベント情報はただ力技で調べました 🙈

npx create-expo-app@latest example-expo-chiristmas-app
npx expo start

これだけでAndroid + Webの開発が可能な状態になりました :clap:

確認できたところでひとまずnpm run reset-projectをしておきます

📱 Android 実機で確認する

Expo Goというアプリを利用してQRコードを読み込むだけで開発中の環境にアクセスできます
https://expo.dev/go
Expo Goは、Expo専用のサンドボックス環境で、よく使用されるライブラリは予めプリインストールされているので、開発を素早く始めることができます :dash:

このページでExpo Goの ✅ が入っているものがプリインストールされているようです。
https://reactnative.directory/

暫くの開発はこれで十分そうです!
ここまで確認出来たらnpm run reset-projectを実行します。
(最初のappディレクトリをapp-exampleに移動することができます)

:christmas_tree: マップを表示する

webとネイティブアプリで別の方法を用いて実装します。
Expo Routerの機能でwebとネイティブアプリ用に実装を分けることができます。

components
├── map.native.tsx
└── map.web.tsx

.native.tsx.web.tsxのような拡張子をつけることでプラットフォームを元に出し分けが可能です。
appディレクトリでは、この拡張子はされてないため、別途componentsディレクトリを作成します。
実際切り分けて表示するにはレイアウト側でのエクスポートが必要です。

app/(tabs)/map.tsx
export { default } from './components/map';

:globe_with_meridians: Webの表示

お手軽にGoogleマイマップを利用して、iframeで埋め込みます。

マイマップでイベントスポットのピンを挿したら、「自分のサイトに埋め込む」からiframeタグを取得します。

そのままiframeを設置したのみでは、初期位置が好ましくありませんでした。
初期位置の指定として、navigator.geolocationを利用して、現在地を中心にします。
navigator.geolocationを呼び出すとブラウザから位置情報の取得権限がリクエストされるはずです)
位置情報の取得が許可されなかった場合は、皇居を中央にするとします。
中心の緯度/経度をllパラメータで指定できるので、セットします。
また、縮尺についてはzパラメータで指定できるようなので、併せて微調整します。

component/map.web.tsx
import { useState, useEffect } from "react";

// 皇居の緯度、経度
const INITIAL_LATITUADE = 35.68546261882921
const INITIAL_LONGITUDE = 139.75286760095705

export default function Map() {

  const [location, setLocation] = useState<{latituade: number, longitude: number} | null>(null);
  const [iframeUrl, setIframeUrl] = useState(`https://www.google.com/maps/d/u/0/embed?mid=1BxutSC-ayz-IgADpmBl7WJfnqHsJiTU&ehbc=2E312F&noprof=1&z=14&ll=${INITIAL_LATITUADE},${INITIAL_LONGITUDE}`);

  useEffect(() => {
    if ("geolocation" in navigator) {
      navigator.geolocation.getCurrentPosition((position) => {
      // 現在地をセットする
      setLocation({ latituade: position.coords.latitude, longitude: position.coords.longitude })
      })
    }
  }, [])

  useEffect(() => {
    const url = new URL(iframeUrl);
    const searchParams = url.searchParams;
    if (location) {
      // 緯度、経度をパラメータに再設定する
      searchParams.set('ll', `${location.latituade},${location.longitude}`);
    }
    setIframeUrl(url.toString())
  }, [location])

  return (
    <div style={styles.container}>
      <iframe style={styles.map} src={iframeUrl}></iframe>
    </div>
  );
}

const styles = {
  container: {
    width: '100%',
    height: '100%',
    backgroundColor: '#fff',
  },
  map: {
    width: '100%',
    height: 'calc(100% + 46px)',
    marginTop: -46, // 見栄えのためにiframeにヘッダーを隠す
  },
};

📱 ネイティブアプリの表示

マップの表示には、react-native-mapviewを利用しました。
基本的には<MapView>コンポーネント内で地図を表示、<Marker>コンポーネントに緯度/経度をセットすることで、イベントスポットの表示ができました。
(ちなみにウェブで作成したマイマップをCSVでexportして緯度/経度を取ってきました)
初期位置の指定も同様に行いたいので、expo-locationを用いることにします。
位置情報の取得権限をリクエストして、<MapView>コンポーネントのregionで利用する形にしています。

component/map.native.tsx
import { useState, useEffect } from 'react';
import { View, StyleSheet } from "react-native";
import MapView, { Marker } from 'react-native-maps';

import * as Location from 'expo-location';

const INITIAL_LATITUADE = 35.68546261882921;
const INITIAL_LONGITUDE = 139.75286760095705;
const LATITUADE_DELTA = 0.04;
const LONGITUDE_DELTA = 0.04;

const POSITIONS = [
  {longitude: 139.7292319, latitude: 35.6607397, name: '六本木ヒルズ'},
  {longitude: 139.7308747, latitude: 35.6659803, name: '東京ミッドタウン'},
  {longitude: 139.7182668, latitude: 35.67543990000001, name: '明治神宮外苑'},
  {longitude: 139.7638027, latitude: 35.6810403, name: '丸の内ビルディング'},
  {longitude: 139.8115747, latitude: 35.7102333, name: '東京ソラマチ'},
  {longitude: 139.7734312, latitude: 35.7147557, name: '上野恩賜公園'},
  // ※省略します
]

export default function Map() {

  const [location, setLocation] = useState<Location.LocationObject | null>(null);

  useEffect(() => {
    async function getCurrentLocation() {
      
      let { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        return;
      }

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

    getCurrentLocation();

  }, []);

  return (
    <View style={styles.container}>

      <MapView
        style={styles.map}
        provider={PROVIDER_GOOGLE}
        initialRegion={{
          latitude: INITIAL_LATITUADE,
          longitude: INITIAL_LONGITUDE,
          latitudeDelta: LATITUADE_DELTA,
          longitudeDelta: LONGITUDE_DELTA,
        }}
        region={{
          latitude: location?.coords.latitude ?? INITIAL_LATITUADE,
          longitude: location?.coords.longitude ?? INITIAL_LONGITUDE,
          latitudeDelta: LATITUADE_DELTA,
          longitudeDelta: LONGITUDE_DELTA,
        }}
      >
        {POSITIONS.map((pos, index) => 
          <Marker
            key={index}
            coordinate={{
              latitude: pos.latitude,
              longitude: pos.longitude,
            }}
            title={pos.name}
          />
        )}
      </MapView>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    width: '100%',
    height: '100%',
    backgroundColor: '#fff',
  },
  map: {
    width: '100%',
    height: '100%',
  },
});
web アプリ(Android)

💭 感想

  • なんとなくネイティブアプリは実装も検証も大変そう...な印象でしたが、Expo Goでの開発体験は革命的でした :smiley:
  • Androidアプリの開発ビルドも試しましたが、EAS Buildでお手軽でした(ただし無料枠だと月30回までのようです)

参考

https://docs.expo.dev/router/advanced/platform-specific-modules/
https://docs.expo.dev/versions/latest/sdk/map-view/
https://docs.expo.dev/versions/latest/sdk/location/

一緒に二次元業界を盛り上げていきませんか?

株式会社viviONでは、フロントエンドエンジニアを募集しています。

また、フロントエンドエンジニアに限らず、バックエンド・SRE・スマホアプリなど様々なエンジニア職を募集していますので、ぜひ採用情報をご覧ください。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?