wheatandcatの開発ブログ

技術系記事を投稿してます

Expoアプリ最新化①:SDKアップデートとexpo-routerへの移行

概要

過去に開発していたmemoirを、最新のコードベースにリニューアル中。その途中経過をまとめる。

github.com

アプリはExpoで開発。最新化に伴い以下を修正中。

  • Expo SDK 48 → 52にバージョンアップ
  • ナビゲーションをReact Navigationexpo-routerに移行
  • ディレクトリ構造をAtomic DesignFeature型に変更
  • Linter & FormatterをESLint & PrettierBiomeに移行

PR

github.com

最初に試したこと

Expoのバージョンアップは以下のコマンドで可能。

$ npx expo install expo@latest

これでバージョンアップを試みたが、EAS Buildが通らなかった。

docs.expo.dev

EAS Buildが通らないとデバッグができないため、一時的にeas.jsonを削除してExpo Goで起動できるように対応を進めた。

Expo Goでは問題なく起動したため、コードの最新化を兼ねてまずexpo-routerへの移行から実装を開始。

expo-routerとは

docs.expo.dev

Expo Routerは、React Navigationを基盤にしたExpo向けのナビゲーションライブラリで、Next.jsのようなファイルベースのルーティングシステムを採用。

基本的なディレクトリ構造は以下の通り。

./app
  ├── _layout.tsx  // 画面全体の共通処理
  ├── index.tsx  // ホーム画面
  ├── items
  │   └── [id].tsx  // アイテム詳細画面(URLパラメータ対応)
  ├── my-page
  │   ├── index.tsx  // マイページ画面
  │   └── update-profile  // プロフィール更新画面
  └── search
      └── index.tsx // 検索画面

実装

認証周りの実装

expo-routerでは、認証関連の構造もファイルベースで以下のように表現可能。

./app
├── (app)
│   ├── _layout.tsx // ログイン後の共通処理
│   ├── index.tsx  // ログイン後のTOP画面
│   ├── items
│   ├── login
│   ├── memoir
│   ├── my-page
│   ├── search
│   └── setting
├── _layout.tsx
└── sign-in.tsx  // ログイン画面

以下に各ファイルを示す。

app/_layout.tsx

import { SessionProvider } from "@/ctx";
import makeApolloClient from "@/lib/apollo";
import { ApolloProvider } from "@apollo/client";
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
import { Slot } from "expo-router";
import { RecoilRoot } from "recoil";

export default function Root() {
  const client = makeApolloClient();

  return (
    <ActionSheetProvider>
      <ApolloProvider client={client}>
        <RecoilRoot>
          <SessionProvider>
            <Slot />
          </SessionProvider>
        </RecoilRoot>
      </ApolloProvider>
    </ActionSheetProvider>
  );
}

app/sign-in.tsx

import Page from "@/features/top/components/Connected";
import useUser from "@/hooks/useUser";
import { NotoSansJP_700Bold } from "@expo-google-fonts/noto-sans-jp";
import { RobotoCondensed_700Bold } from "@expo-google-fonts/roboto-condensed";
import * as Font from "expo-font";
import { useCallback, useState } from "react";

export default function SignIn() {
  const { user, onSaveWhenNotLogin } = useUser();
  const [create, setCreate] = useState(false);

  const onSkip = useCallback(() => {
    setCreate(true);
    onSaveWhenNotLogin();
  }, [onSaveWhenNotLogin]);

  const [fontsLoaded] = Font.useFonts({
    "RobotoCondensed-Bold": RobotoCondensed_700Bold,
    "NotoSansJP-Bold": NotoSansJP_700Bold,
  });

  if (!fontsLoaded) {
    return null;
  }

  return (
    <Page
      onSkip={onSkip}
      setCreate={setCreate}
      create={create}
      isExistUser={!!user.id}
    />
  );
}

app/(app)/_layout.tsx/_layout.tsx)

import { useSession } from "@/ctx";
import { Redirect, Stack } from "expo-router";
import { Text } from "react-native";

export default function AppLayout() {
  const { session, isLoading } = useSession();

  if (isLoading) {
    return <Text>Loading...</Text>;
  }

  if (!session) {
    return <Redirect href="/sign-in" />;  // 未ログイン時にリダイレクト
  }

  return <Stack />;
}

URLパラメータ、GETパラメータの取得

memoirでは、アイテム詳細画面を以下のように設定。

./app
└── (app)
    └── items
        └── [id].tsx

アイテム詳細画面では、URLパラメータid以外にも、dateを画面に渡す仕様。

以下のコードで遷移先を指定。

features/home/components/Connected.tsx

const onItem = useCallback(
  (itemID: string) => {
    const date = dayjs(homeDate.date).format("YYYY-MM-DD");
    router.push({
      pathname: `/items/${itemID}`,
      params: {
        date,
      },
    });
  },
  [router, homeDate.date],
);

以下でパラメータを受け取る。

features/items/components/index.tsx

import { useLocalSearchParams } from "expo-router";
import type { FC } from "react";
import { memo } from "react";
import Connected from "./Connected";

const ItemDetail: FC = () => {
  const { id, date } = useLocalSearchParams<{ id: string; date: string }>();

  return <Connected date={date} itemID={id} />;
};

export default memo(ItemDetail);

まとめ

ここまでの移行作業により、大半の画面は以下の画像のように移行が完了。

残作業として以下を進行予定。

  • EAS Buildの復活
  • Googleログインの移行
  • Deep Linkの実装

Expoは便利だが、最新化を怠るとデバッグもできなくなるため、継続的な対応が必要だと感じた。