関東梅雨明けは8月からだそうです。こんにちは柏木です。まずは完成したものをご覧ください。
AWS Amplifyとは?
(以下Amplify)
”Amplify とはサーバーレスなバックエンドをセットアップするための CLI、フロントエンドで利用できる UI コンポーネント、 CI/CD やホスティングのためのコンソールを含む Web およびモバイルアプリ開発のためのフレームワークです。”
CLIでコマンドをぽちぽち打てば簡単にバックエンドがセットアップすることが可能なサービスですね。
概要
この記事ではAWS AmplifyとReactNative(Expo)を使用してリアルタイムチャットアプリ作成の簡単な流れを紹介できたらなと思います。
チャット機能を作成する上で用件があると思いますが、今回はログイン認証した後、それぞれにチャット部屋があり、そこで複数人がメッセージを送信できるようにする。こんな用件で作成したいと思います。
準備
Amplifyのチュートリアルに沿って進めて行きます。
次の物がインストールされていることを確認してください。
- Node.js v10.x or later
- npm v5.x or later
- git v2.14.1 or later
NPMを使用しますがYarnもオッケーです。
Expoのセットアップ
Expoコマンドを使用しアプリケーションを作成し、必要なモジュールを追加していきます。
$ npm install -g expo-cli
Expoがない場合
$ expo init chatAppAmplify
❯ tabs (TypeScript) several example screens and tabs using react-navigation and TypeScript
画面遷移が欲しいのでtabs (TypeScript)を選択します。
$ cd chatAppAmplify
$ npm install aws-amplify aws-amplify-react-native
$ expo install @react-native-community/netinfo
チャットのUIですがGitHubでスター9.5k(記事作成時点)のreact-native-gifted-chatを使用します。
$ npm install react-native-gifted-chat
AWS Amplify のセットアップ
Amplify CLIのインストール
$ npm install -g @aws-amplify/cli
$ amplify configure
※CLIの詳しい設定は以下の動画で確認してください
プロジェクトの初期化
$ amplify init
? Enter a name for the project: chatappamplify
? Enter a name for the environment: dev
? Choose your default editor: Visual Studio Code<Your favorite text editor>
? Choose the type of app that you're building: javascript
? What javascript framework are you using: react-native
? Source Directory Path: /
? Distribution Directory Path: /
? Build Command: npm run-script build
? Start Command: npm run-script start
? Do you want to use an AWS profile? Y
? Please choose the profile you want to use: YOUR_USER_PROFILE
Amplifyのプロジェクトが作成されました。
認証機能の追加
$ amplify add auth
? Do you want to use the default authentication and security configuration? Default configuration
? How do you want users to be able to sign in? Username
? Do you want to configure advanced settings? No, I am done.
GraphQLの追加
$ amplify add api
? Please select from one of the below mentioned services:
# GraphQL
? Provide API name:
# chatappamplify
? Choose the default authorization type for the API:
# Amazon Cognito User Pool
Use a Cognito user pool configured as a part of this project.
? Do you want to configure advanced settings for the GraphQL API
# No, I am done.
? Do you have an annotated GraphQL schema?
# No
? Do you want a guided schema creation?
# Yes
? What best describes your project:
# Single object with fields (e.g., “Todo” with ID, name, description)
? Do you want to edit the schema now?
# Yes
すると自動でエディターが開くので以下の用に書き換えます。
amplify/backend/api/schema.graphql
type Room @model {
id: ID!
title: String!
avatar: String!
messages: [Message]
@connection(name: "RoomMessages", keyField: "roomId", sortField: "when")
}
type Message
@model
@auth(
rules: [
{
allow: owner
ownerField: "owner"
operations: [create, update, delete]
}
]
) {
id: ID!
content: String!
when: String!
roomId: ID
owner: String
room: Room @connection(name: "RoomMessages", keyField: "roomId")
}
@modelなどのディレクティブの詳細はこちらから確認してください。
設定をAWSのクラウドにデプロイ
$ amplify push
? Are you sure you want to continue?
# Yes
? Do you want to generate code for your newly created GraphQL API
# Yes
? Choose the code generation language target
# typescript
? Enter the file name pattern of graphql queries, mutations and subscriptions
# src/graphql/**/*.ts
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions
# Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested]
# 4
? Enter the file name for the generated code
# src/API.ts
Amplify の設定は一通り終了です。
チャットの実装
チャットの箇所はこのような実装にします。
Chat.tsx
import React, { useState, useEffect } from "react";
import { API, graphqlOperation } from "aws-amplify";
import { StackScreenProps } from "@react-navigation/stack";
import { GiftedChat, IMessage } from "react-native-gifted-chat";
import Observable from "zen-observable-ts";
import { AuthContextValue } from "../hooks/useAuth";
import * as mutations from "../graphql/mutations";
import * as queries from "../graphql/queries";
import * as subscriptions from "../graphql/subscriptions";
import { TabTwoParamList } from "../types";
import {
OnCreateMessageSubscription,
GetRoomQueryVariables,
CreateMessageMutationVariables,
} from "../API";
type Props = StackScreenProps<TabTwoParamList, "Chat">;
type MessageSubscriptionEvent = {
value: { data: OnCreateMessageSubscription };
};
export default function ChatScreen({ navigation, route }: Props) {
const { roomId } = route.params;
const [messages, setMessages] = useState<IMessage[]>([]);
const user = AuthContextValue();
useEffect(() => {
fetchMessages();
const subscription = (API.graphql(
graphqlOperation(subscriptions.onCreateMessage)
) as Observable).subscribe({
next: ({ value: { data } }) => {
const m = data.onCreateMessage;
const message = {
_id: m.id || "",
createdAt: m.createdAt || 0,
text: m.content,
user: { _id: m.owner, name: m.owner },
};
setMessages((previousMessages: IMessage[]) =>
GiftedChat.append(previousMessages, [message as IMessage])
);
},
});
return () => subscription.unsubscribe();
}, []);
function fetchMessages() {
const variables: GetRoomQueryVariables = {
id: roomId,
};
(API.graphql(graphqlOperation(queries.getRoom, variables)) as Promise<
any
>).then((res) => {
const messages = res.data.getRoom.messages;
if (messages.items) {
setMessages(
messages.items.map((m) => ({
_id: m.id,
text: m.content,
createdAt: new Date(m.when),
user: {
_id: m.owner,
name: m.owner,
},
})) as IMessage[]
);
}
});
}
async function onSend(messages: IMessage[]) {
const variables: CreateMessageMutationVariables = {
input: {
content: messages[0].text,
roomId: roomId,
when: String(new Date()),
},
};
await API.graphql(graphqlOperation(mutations.createMessage, variables));
}
return (
onSend(messages)}
user={{
_id: user.username,
}}
/>
);
}
誰かしらがメッセージを作成すると以下の箇所が発火されます。
const subscription = (API.graphql(graphqlOperation(subscriptions.onCreateMessage)) as Observable).subscribe({
next: ({ value: { data } }) => {
const m = data.onCreateMessage;
const message = {
_id: m.id || "",
createdAt: m.createdAt || 0,
text: m.content,
user: { _id: m.owner, name: m.owner },
};
setMessages((previousMessages: IMessage[]) =>
GiftedChat.append(previousMessages, [message as IMessage])
);
},
});
return () => subscription.unsubscribe();
リアルタイムチャットの箇所ですが、なんと驚異の15行で実装できます。
最後に
いかがでしたしょうか?
簡単にリアルタイムチャットアプリが作成できたと思います。
掲示板のような機能しかないので、一対一のメッセージ交換、ユーザー情報の追加(アバター画像等)など機能として追加できたらいいな思います。