ガイド

React Native ではじめてのアプリ

iPhone と Android 向けの ToDo リストです。ユーザーはサインアップして、自分の ToDo だけを見られます。Expo と @espobase/client を使い、データベースとキーはダッシュボードから用意します。

← すべてのガイド

1. プロジェクトとテーブルを作成する

ダッシュボードでプロジェクトを作成し、SQL エディタを開いて次を実行します:

create table todos (
  id bigint generated by default as identity primary key,
  owner_id uuid not null default espobase.user_id(),
  title text not null,
  done boolean not null default false,
  created_at timestamptz not null default now()
);

-- Row level security is already on. Each signed-in user sees and changes only their own todos:
create policy "Users manage their own todos" on todos
  for all
  using (owner_id = espobase.user_id())
  with check (owner_id = espobase.user_id());

2. 設定とキー

  • API: 公開可能キーを作成し、今すぐコピーします。キーは作成時にしか表示されません。シークレットキー(eb_sk_…)は絶対にアプリに入れないでください。アプリは誰でも展開して中身を読めます。
  • 認証 › 設定: 許可するリダイレクト URL にアプリのリンクを追加します。例:myapp://auth/callback(myapp は app.json の scheme)。確認メールとソーシャルログインはここに戻ってきます。

3. インストール

npm install @espobase/client
npx expo install @react-native-async-storage/async-storage react-native-get-random-values react-native-url-polyfill expo-web-browser expo-linking

React Native にはブラウザにある機能が少し足りません。小さな 2 つのパッケージで補います。何よりも先にインポートしてください:

# .env (Expo reads EXPO_PUBLIC_ variables; the PUBLISHABLE key is safe in an app)
EXPO_PUBLIC_ESPOBASE_URL=https://api.espobase.com/project/<your project ref>
EXPO_PUBLIC_ESPOBASE_KEY=eb_pk_…

4. クライアント

スマートフォンには localStorage がないため、クライアントにセッションの保存先を渡します。セッションは自動で更新され、アプリを再起動してもサインインしたままです。

// lib/espobase.ts
import "react-native-get-random-values"; // crypto.getRandomValues
import "react-native-url-polyfill/auto"; // a complete URL class
import AsyncStorage from "@react-native-async-storage/async-storage";
import { createClient } from "@espobase/client";

export const espobase = createClient(
  process.env.EXPO_PUBLIC_ESPOBASE_URL!,
  process.env.EXPO_PUBLIC_ESPOBASE_KEY!,
  { auth: { storage: AsyncStorage } },
);

5. サインアップとサインイン

import * as Linking from "expo-linking";
import { espobase } from "@/lib/espobase";

// myapp://auth/callback (the scheme comes from app.json). Add it under Authentication › Settings.
const redirectTo = Linking.createURL("auth/callback");

await espobase.auth.signUp({ email, password, options: { redirectTo } });
await espobase.auth.signInWithPassword({ email, password });

// The confirmation email opens the app at redirectTo#type=signup&token=…
Linking.addEventListener("url", async ({ url }) => {
  const token = new URLSearchParams(url.split("#")[1] ?? "").get("token");
  if (token) await espobase.auth.verifyEmail({ token });
});

// Re-render when the user signs in or out.
espobase.auth.onAuthStateChange((_event, session) => setSession(session));

メール確認が有効な場合、ユーザーには、redirectTo に渡したアドレスでアプリを開くリンクが届き、トークンは # の後ろに付きます。espobase.auth.verifyEmail({ token }) で確認します。

6. Google または GitHub でサインイン

import * as WebBrowser from "expo-web-browser";
import * as Linking from "expo-linking";
import { espobase } from "@/lib/espobase";

export async function signInWithGoogle() {
  const redirectTo = Linking.createURL("auth/callback");
  const { data } = await espobase.auth.signInWithOAuth({
    provider: "google",
    options: { redirectTo, skipBrowserRedirect: true },
  });
  const result = await WebBrowser.openAuthSessionAsync(data!.url, redirectTo);
  if (result.type !== "success") return; // cancelled
  const code = new URL(result.url).searchParams.get("code");
  if (code) await espobase.auth.exchangeCodeForSession(code);
}

先に 認証 › プロバイダー でプロバイダーを有効にします。アプリはシステムのブラウザでプロバイダーを開き、アプリのリンクでユーザーを戻します。exchangeCodeForSession がサインインを完了します。完了できるのはこのアプリだけです。使い捨ての秘密情報はスマートフォンの中に残ります。

7. データの読み書き

import { useEffect, useState } from "react";
import { espobase } from "@/lib/espobase";

export function useTodos() {
  const [todos, setTodos] = useState<{ id: number; title: string; done: boolean }[]>([]);
  useEffect(() => {
    espobase.from("todos").select("*").order("created_at").then(({ data }) => setTodos(data ?? []));
  }, []);
  return todos;
}

// owner_id fills itself in (its default is espobase.user_id()).
await espobase.from("todos").insert({ title: "Buy milk" });

すべてのリクエストにサインイン中のユーザーが付き、データベースがルールを適用します。サインインしていないユーザーには空のリストが返り、API を直接呼んでも他の人の ToDo を読んだり変更したりすることはできません。

8. 写真をアップロードする

ストレージでバケットを作成し、各ユーザーが自分のフォルダに書き込めるポリシーを追加します(ファイルアップロードのガイドを参照)。ファイルのバイト列(ArrayBuffer)を upload に渡します: ファイルのアップロード

import * as ImagePicker from "expo-image-picker";
import { espobase } from "@/lib/espobase";

const picked = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ["images"] });
if (!picked.canceled) {
  const asset = picked.assets[0]!;
  const bytes = await (await fetch(asset.uri)).arrayBuffer();
  const { data: session } = await espobase.auth.getSession();
  const userId = session.session!.user.id;
  await espobase.storage.from("photos").upload(`${userId}/photo.jpg`, bytes, { contentType: "image/jpeg" });
}

知っておくと便利なこと

  • サインインのリンクには、Expo Go ではなく開発ビルド(npx expo run:ios または run:android)を使ってください。Expo Go のアドレスはネットワークによって変わるため、一度だけ登録することができません。
  • ほかの React Native 構成でも同じです。同じパッケージをインストールし、2 つのポリフィルを最初にインポートして、ストレージオブジェクトを createClient に渡します。
  • リアルタイムも使えます。React Native には WebSocket が組み込まれているので、espobase.realtime に追加の準備は要りません。