Table of Contents
はじめに
Amplify Gen2では簡単にバックエンド環境が構築できて便利です。 普段私が開発で使用しているReact Nativeもサポートされていますので、公式ドキュメントのQuickstartをやってみました。
ドキュメントではPrebuildをしてネイティブコードを吐き出す必要があると書かれていますが、それでは運用が辛くなってしまうので、Development Buildでできないかも今回検証してみました。
Expoプロジェクト作成
まずはExpoプロジェクトの作成です。
ここはドキュメントと同じコマンドを実行していくだけで良いです。
npm run start
でアプリが起動できればOKです。
npx create-expo-app expo-amplify-gen2-app -t expo-template-blank-typescript
cd expo-amplify-gen2-app
npm run start
Note
Quickstartではネイティブライブラリを呼ぶ関係で npx expo prebuild
を実行してネイティブコードをios/androidディレクトリに生成する必要があると書かれていますが、今回は後述するDevelopment Buildでのデバッグ手法を採用するため、Prebuildコマンドは実行不要です。
Amplifyバックエンド作成
アプリ側ができたら次はバックエンド環境を構築します。
ここもドキュメント通りで問題ないです。
npm create amplify@latest
Sandbox環境をデプロイします。
npx ampx sandbox
認証機能追加
開発環境が整ったら認証機能の実装に入ります。
必要なパッケージのインストール
認証機能に必要なAmplifyのUIライブラリなどインストールします。
npm add \
@aws-amplify/ui-react-native \
@aws-amplify/react-native \
aws-amplify \
@react-native-community/netinfo \
@react-native-async-storage/async-storage \
react-native-safe-area-context \
react-native-get-random-values \
react-native-url-polyfill
Development Buildアプリ作成
一通り依存関係をインストールしたところで、Quickstartの手順にはないDevelopment Buildアプリを作成します。
今回のように本来Prebuildしてネイティブコードを生成してデバッグする必要があるものでも、Development Buildアプリを一度作成しておくことでExpo Goのような開発体験のまま開発を進めることができます。
さらにPrebuildコマンドで吐き出されるios/androidディレクトリを持たず、ネイティブコードの管理が不要になる利点もあります。
作成方法は私が前に書いた以下記事の手順で進めていただければOKです。
コードを編集
ビルドの実行待ちの間にコードを編集しておきましょう。 基本的にQuickstartのものと同じコードですが、スタイルを少し調整しています。
App.tsx:
import React from "react";
import { Button, View, StyleSheet, SafeAreaView } from "react-native";
import { Amplify } from "aws-amplify";
import { Authenticator, useAuthenticator } from "@aws-amplify/ui-react-native";
import outputs from "./amplify_outputs.json";
Amplify.configure(outputs);
const SignOutButton = () => {
const { signOut } = useAuthenticator();
return (
<View style={styles.signOutButton}>
<Button title="Sign Out" onPress={signOut} />
</View>
);
};
const App = () => {
return (
<Authenticator.Provider>
<Authenticator>
<SafeAreaView>
<SignOutButton />
</SafeAreaView>
</Authenticator>
</Authenticator.Provider>
);
};
const styles = StyleSheet.create({
signOutButton: {
height: "100%",
justifyContent: "center",
alignItems: "center",
},
});
export default App;
Development Buildができたら端末へインストールして、npm run start
を実行、QRコードを読み取って動かしてみましょう。
無事にサインアップできて、サインインできればOKです。
Todoリスト作成
最後はTodoリストの作成です。
こちらも特に変わったことはせず、スタイル調整だけしてほぼQuickstartそのままのコードで動きました。
apmlify/data/resource.ts:
import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
const schema = a.schema({
Todo: a
.model({
content: a.string(),
isDone: a.boolean(),
})
.authorization((allow) => [allow.owner()]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: "userPool",
},
});
src/TodoList.tsx:
import { useState, useEffect } from "react";
import { View, Button, Text, StyleSheet, FlatList } from "react-native";
import { generateClient } from "aws-amplify/data";
import type { Schema } from "../amplify/data/resource";
import { GraphQLError } from "graphql";
const client = generateClient<Schema>();
const TodoList = () => {
const dateTimeNow = new Date();
const [todos, setTodos] = useState<Schema["Todo"]["type"][]>([]);
const [errors, setErrors] = useState<GraphQLError>();
useEffect(() => {
const sub = client.models.Todo.observeQuery().subscribe({
next: ({ items }) => {
setTodos([...items]);
},
});
return () => sub.unsubscribe();
}, []);
const createTodo = async () => {
try {
await client.models.Todo.create({
content: `${dateTimeNow.getUTCMilliseconds()}`,
});
} catch (error: unknown) {
if (error instanceof GraphQLError) {
setErrors(error);
} else {
throw error;
}
}
};
if (errors) {
return <Text>{errors.message}</Text>;
}
const renderItem = ({ item }: { item: Schema["Todo"]["type"] }) => (
<TodoItem {...item} />
);
return (
<View style={{ flex: 1 }}>
<FlatList
data={todos}
renderItem={renderItem}
keyExtractor={(item) => item.id}
ItemSeparatorComponent={() => <View style={styles.listItemSeparator} />}
ListEmptyComponent={() => <Text>The todo list is empty.</Text>}
style={styles.listContainer}
></FlatList>
<Button onPress={createTodo} title="Create Todo" />
</View>
);
};
const TodoItem = (todo: Schema["Todo"]["type"]) => (
<View style={styles.todoItemContainer} key={todo.id}>
<Text
style={{
...styles.todoItemText,
textDecorationLine: todo.isDone ? "line-through" : "none",
textDecorationColor: todo.isDone ? "red" : "black",
}}
>
{todo.content}
</Text>
<Button
onPress={async () => {
await client.models.Todo.delete(todo);
}}
title="Delete"
/>
<Button
onPress={() => {
client.models.Todo.update({
id: todo.id,
isDone: !todo.isDone,
});
}}
title={todo.isDone ? "Undo" : "Done"}
/>
</View>
);
const styles = StyleSheet.create({
todoItemContainer: {
flexDirection: "row",
alignItems: "center",
padding: 8,
gap: 12,
},
todoItemText: { flex: 1, textAlign: "center" },
listContainer: { flex: 1, alignSelf: "stretch", padding: 8 },
listItemSeparator: { backgroundColor: "lightgrey", height: 2 },
});
export default TodoList;
App.tsx:
import React from "react";
import { Button, View, StyleSheet, SafeAreaView } from "react-native";
import { Amplify } from "aws-amplify";
import { Authenticator, useAuthenticator } from "@aws-amplify/ui-react-native";
import outputs from "./amplify_outputs.json";
import TodoList from "./src/TodoList";
Amplify.configure(outputs);
const SignOutButton = () => {
const { signOut } = useAuthenticator();
return (
<View style={styles.signOutButton}>
<Button title="Sign Out" onPress={signOut} />
</View>
);
};
const App = () => {
return (
<Authenticator.Provider>
<Authenticator>
<SafeAreaView style={styles.container}>
<SignOutButton />
<TodoList />
</SafeAreaView>
</Authenticator>
</Authenticator.Provider>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 8,
paddingVertical: 60,
},
signOutButton: {
alignSelf: "flex-end",
},
});
export default App;
おわりに
今回はQuickstartそのまま実施しただけで、あまり内容はありませんでしたが公式ドキュメントがPrebuildのみの手順だけ紹介していて、Development Buildでも良いのでは?と疑問に思っていたのがきっかけで検証メモのような内容でした。
結果としてDevelopment Buildでも開発は進めていけそうなことがわかったので、実務でも採用できそうです!