はじめに
こんにちは。
私は入社2年目のクラウドソリューション開発部に所属している寺谷と申します。
今回は、React Nativeのライブラリ「Gifted Chat」を活用して、簡単なチャットアプリを実装する方法を紹介します。
さらに、Google Sheetsをデータベース代わりに使用し、Google Apps Script(GAS)を通じてデータの送受信も行っています。
ぜひ最後までご覧ください!
インストール
今回はタイトルで紹介した通り「react-native-gifted-chat」というライブラリを利用します。
react-native-gifted-chatとは?
react-native-gifted-chat は、React Nativeで簡単にチャットUIを実装できるライブラリです。ユーザーのメッセージ送受信、アバター表示、タイムスタンプの管理など、基本的なチャット機能を手軽に構築できるのが特徴です。このライブラリを使用することで、複雑なUIの実装をせずに、シンプルかつカスタマイズ可能なチャット機能をアプリに組み込むことができます。
インストール方法は下記になります。
1 2 3 |
yarn add react-native-gifted-chat 又は npm install react-native-gifted-chat |
また、下記のライブラリもGifted Chatで利用するためインストールしてください。
1 2 |
yarn add react-native-get-random-values npm install react-native-get-random-valuest |
チャット画面の実装
次に、react-native-gifted-chatを使用してチャット画面を実装します。以下のコードを参考にしてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import React, { useState, useCallback } from 'react'; import { GiftedChat, IMessage } from 'react-native-gifted-chat'; import { View, StyleSheet } from 'react-native'; const ChatScreen = () => { const [messages, setMessages] = useState<IMessage[]>([]); const onSend = useCallback((newMessages: IMessage[] = []) => { setMessages((previousMessages) => GiftedChat.append(previousMessages, newMessages)); }, []); return ( <View style={styles.container}> <GiftedChat messages={messages} // 現在のメッセージ一覧を表示するためのプロップ onSend={(messages) => onSend(messages)} // 送信ボタンを押したときの処理を指定 user={{ _id: 1, name: 'User' }} // 自分のユーザー情報を設定(_id はユーザー識別のために必要) placeholder="メッセージを入力..." // 入力フィールドのプレースホルダーを指定 showUserAvatar // ユーザーのアバターを表示する設定 /> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, }, }); export default ChatScreen; |
コードの説明
- useStateとuseCallbackの使用:messagesという状態変数でメッセージのリストを管理し、onSend関数で新しいメッセージを追加しています。
- GiftedChatコンポーネント:このコンポーネントがチャットUIを提供します。messagesのpropsにメッセージの配列を渡し、onSendプロップにメッセージ送信時の処理を指定しています。
上記コードをビルドすると下図のように簡単にチャットのUIが完成しました。
Google スプレッドシートとの連携
ライブラリを活用するだけでも簡単なチャットアプリが完成しました。
次にスプレッドシートの連携をしていきます。
詳細については、こちらのサイトを参考にしました。
上記サイトでは、React NativeアプリからGoogleスプレッドシートのデータを取得する方法が詳しく解説されています。
そのため、データの取得に関しては省略いたします。本記事では、チャットアプリで送信するメッセージをスプレッドシートに保存するための手順を紹介します。
まず、Googleスプレッドシートにデータを保存するために、Google Apps Script(GAS)を作成します。外部からPOSTリクエストを受け取り、送信されたデータをスプレッドシートに追加するGASについて、下記に示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function doPost(e) { try { if (!e.postData || !e.postData.contents) { throw new Error("No postData received"); } var sheetId = "your_sheet_id "; // あなたのスプレッドシートID var sheet = SpreadsheetApp.openById(sheetId).getSheetByName("chat"); if (!sheet) { throw new Error("Sheet not found"); } var data = JSON.parse(e.postData.contents); sheet.appendRow([data.id, data.username, data.message, data.createdAt, data.userID]); return ContentService.createTextOutput(JSON.stringify({ status: "success" })) .setMimeType(ContentService.MimeType.JSON); } catch (error) { return ContentService.createTextOutput(JSON.stringify({ status: "error", message: error.message })) .setMimeType(ContentService.MimeType.JSON); } } |
主にsheet.appendRowでスプレッドシートにデータを書き込んでいます。
このスクリプトをGASエディタに貼り付け、デプロイしてウェブアプリとして公開することで、外部からのPOSTリクエストを受け取ることが可能になります。
また、スプレッドシート上では以下のようになります。
チャットアプリから送信されたメッセージをGoogleスプレッドシートに保存できるようになりました。
次に、React Nativeのチャットアプリから、上記のGASにメッセージデータをPOSTする処理を追加します。以下は、メッセージ送信時にGASのエンドポイントにデータを送信するコードに関して下記に示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import axios from 'axios'; const onSend = useCallback(async (newMessages = []) => { setMessages((previousMessages) => GiftedChat.append(previousMessages, newMessages)); const message = newMessages[0]; const messageData = { id: message._id.toString(), username: message.user.name, message: message.text, createdAt: new Date().toISOString(), userID: message.user._id.toString(), }; try { const response = await axios.post('YOUR_GAS_ENDPOINT_URL', messageData, { headers: { 'Content-Type': 'application/json' }, }); console.log('Message saved to Google Sheets:', response.data); } catch (error) { console.error('Error saving message:', error); } }, []); |
これにより、ユーザーがメッセージを送信するたびに、その内容がGASを介してGoogleスプレッドシートに保存されます。この方法を活用することで、リアルタイムでメッセージを保存・管理できるチャットアプリを構築することが可能です。
Google スプレッドシートを利用したアプリ
最終的に、Googleスプレッドシート上に「user」と「chat」という2つのシートを作成しました。「user」シートは認証情報の管理に使用し、「chat」シートはメッセージの管理に使用しています。
各シートの構成
- user: (id, username, password)
- chat: (id, username, message, createdAt, userID)
これらのシートと連携するために、以下の3つのファイルを作成しました。
- SpreadsheetLoginScreen.tsx: ユーザーがログインするための画面
- ChatScreen.tsx: チャット画面
- config.ts: 設定情報を管理するファイル
SpreadsheetLoginScreen.tsx
このファイルでは、ユーザー名とパスワードを入力してログインする画面を実装しています。入力された情報をもとに、Googleスプレッドシートの「user」シートからユーザー情報を取得し、認証を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
import React, { useState } from 'react'; import { View, Text, TextInput, Button, StyleSheet, KeyboardAvoidingView, Platform, TouchableWithoutFeedback, Keyboard } from 'react-native'; import axios from 'axios'; import { Config } from '../../config'; const SpreadsheetLoginScreen = ({ navigation }: any) => { const dataUrl = Config.GOOGLE_SHEETS.DATA_URL('users'); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const fetchData = async () => { try { const res = await axios.get(dataUrl); const { values } = res.data; console.log('Fetched data:', values); return values.slice(1); } catch (e) { console.log('fetch data error', e); return []; } }; const handleLogin = async () => { const users = await fetchData(); const user = users.find((row) => row[1] === username && row[2] === password); if (user) { console.log('Login successful:', user); navigation.navigate('Chat', { username: user[1], userID: user[0], }); } else { console.log('Login failed: Invalid username or password'); alert('ユーザー名またはパスワードが間違っています'); } }; return ( <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.container}> <TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}> <View style={styles.inner}> <Text style={styles.label}>ユーザー名</Text> <TextInput style={styles.input} placeholder="名前を入力" value={username} onChangeText={setUsername} autoCapitalize="none" autoCorrect={false} keyboardType="default" /> <Text style={styles.label}>パスワード</Text> <TextInput style={styles.input} placeholder="パスワードを入力" value={password} onChangeText={setPassword} autoCapitalize="none" autoCorrect={false} keyboardType="default" secureTextEntry={true} /> <Button title="ログイン" onPress={handleLogin} /> </View> </TouchableWithoutFeedback> </KeyboardAvoidingView> ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', padding: 20, }, inner: { flex: 1, justifyContent: 'center', }, label: { fontSize: 18, marginBottom: 8, }, input: { height: 40, borderWidth: 1, borderColor: '#ccc', padding: 10, marginBottom: 20, borderRadius: 5, backgroundColor: '#fff', }, }); export default SpreadsheetLoginScreen; |
ChatScreen.tsx
このファイルでは、ユーザー間のメッセージの送受信を行うチャット画面を実装しています。送信されたメッセージはGoogleスプレッドシートの「chat」シートに保存され、過去のメッセージも同シートから取得して表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
import React, { useState, useCallback, useEffect } from 'react'; import { StyleSheet, View, Text } from 'react-native'; import { GiftedChat, IMessage, Avatar } from 'react-native-gifted-chat'; import dayjs from 'dayjs'; import 'dayjs/locale/ja'; import weekday from 'dayjs/plugin/weekday'; import { MySafeAreaView } from '../../components/common/unit/MySafeAreaView'; import { CONTENT_PARTS_MARGIN_BOTTOM, SCREEN_MARGIN_HORIZONTAL } from '../../constants/constants'; import axios from 'axios'; import { Config } from '../../config'; dayjs.extend(weekday); dayjs.locale('ja'); const ChatScreen = ({ navigation, route }: any) => { // Google Sheets API(読み取り用) const dataUrl = Config.GOOGLE_SHEETS.DATA_URL('chat'); // Google Apps Script (POST用) const API_ENDPOINT = Config.GOOGLE_APPS_SCRIPT.API_ENDPOINT; const { username, userID } = route.params || {}; console.log('Received user data:', { username, userID }); const [messages, setMessages] = useState<IMessage[]>([]); useEffect(() => { fetchData(); }, []); const renderDay = (props: any) => { const { currentMessage, previousMessage } = props; const currentDate = dayjs(currentMessage.createdAt).startOf('day'); const previousDate = previousMessage?.createdAt ? dayjs(previousMessage.createdAt).startOf('day') : null; if (!previousMessage || !previousDate || !currentDate.isSame(previousDate)) { return ( <View style={styles.dayContainer}> <Text style={styles.dayText}>{currentDate.format('YYYY年MM月DD日(dd)')}</Text> </View> ); } return null; }; const formatMessagesForChat = (data: string[][]) => { return data.slice(1).map((row) => ({ _id: row[0], text: row[2], createdAt: new Date(row[3]), user: { _id: row[4], name: row[1], }, })); }; const fetchData = async () => { try { const res = await axios.get(dataUrl); const { values } = res.data; console.log('Fetched data:', values); const formattedMessages = formatMessagesForChat(values); setMessages(formattedMessages.reverse()); } catch (error) { console.log('fetch data error', error); } }; const onSend = useCallback(async (newMessages: IMessage[] = []) => { setMessages((prevMessages) => GiftedChat.append(prevMessages, newMessages)); const message = newMessages[0]; const messageData = { id: message._id.toString(), username: username, message: message.text, createdAt: new Date().toISOString(), userID: userID.toString(), }; console.log('Sending message to Google Apps Script:', messageData); try { const response = await axios.post(API_ENDPOINT, messageData, { headers: { 'Content-Type': 'application/json' }, }); console.log('Message saved to Google Sheets:', response.data); } catch (error) { console.error('Error saving message:', error); } }, []); const renderAvatar = (props: any) => { const { currentMessage } = props; if (!currentMessage) return null; return ( <View style={{ alignItems: 'center', justifyContent: 'center' }}> <Avatar {...props} /> <View style={{ flexDirection: 'row', alignItems: 'center', marginTop: 2 }}> <Text style={{ fontSize: 12, textAlign: 'center' }}>{currentMessage.user.name}</Text> </View> </View> ); }; const renderBubble = (props: any) => { const isCurrentUser = props.currentMessage.user._id.toString() === userID.toString(); return ( <View style={{ marginBottom: 5, alignItems: isCurrentUser ? 'flex-end' : 'flex-start' }}> <View style={{ backgroundColor: isCurrentUser ? '#DCF8C6' : '#FFF', padding: 10, borderRadius: 10, maxWidth: 'auto', minWidth: 'auto', flexShrink: 1, }} > <Text style={{ fontSize: 16, flexWrap: 'nowrap' }}>{props.currentMessage.text}</Text> </View> <Text style={{ fontSize: 10, color: 'gray', marginTop: 2, }} > {dayjs(props.currentMessage.createdAt).format('HH:mm')} </Text> </View> ); }; return ( <View style={{ flex: 1 }}> <Text style={styles.title}>{'Chat Screen'}</Text> <View style={styles.chatContainer}> <GiftedChat messages={messages} onSend={(messages) => onSend(messages)} user={{ _id: userID, name: username }} placeholder="メッセージを入力..." alwaysShowSend={true} showUserAvatar={true} renderDay={renderDay} renderAvatar={renderAvatar} renderBubble={renderBubble} /> </View> </View> ); }; const styles = StyleSheet.create({ chatContainer: { flex: 1, marginHorizontal: SCREEN_MARGIN_HORIZONTAL, marginBottom: CONTENT_PARTS_MARGIN_BOTTOM, }, dayContainer: { alignItems: 'center', marginVertical: 5, }, dayText: { fontSize: 14, color: 'gray', }, title: { textAlign: 'center', fontSize: 20, padding: 5, color: '#4B4B4B', height: 40, backgroundColor: '#E0E3E1', }, }); export default ChatScreen; |
config.ts
アプリケーション内で使用する設定情報や定数を一元管理するためのファイルです。
1 2 3 4 5 6 7 8 9 10 11 |
export const Config = { GOOGLE_SHEETS: { API_KEY: 'YOUR_API_KEY', SHEET_ID: 'YOUR_SHEET_ID', DATA_URL: (sheetName: string) => `https://sheets.googleapis.com/v4/spreadsheets/${Config.GOOGLE_SHEETS.SHEET_ID}/values/${sheetName}?valueRenderOption=FORMATTED_VALUE&key=${Config.GOOGLE_SHEETS.API_KEY}`, }, GOOGLE_APPS_SCRIPT: { API_ENDPOINT: 'YOUR_API_ENDPOINT', }, }; |
ビルドをすると、下記のように動作します。
kenでログインすると、ken側のメッセージを右に寄せて表示されます。
他のユーザーでログインしても自分側のメッセージは右に寄せて表示してくれるように実装しました。
スプレッドシート上では、下図のように保存されています。
まとめ
いかがだったでしょうか?React Nativeのライブラリ「react-native-gifted-chat」を活用し、Googleスプレッドシートと連携することで、簡単にチャットアプリを作成することができました。興味のある方は、是非試してみてください。
エコモットでは、一緒にモノづくりをしていく仲間を募集中です!弊社に少しでも興味がある方、ぜひ下記の採用ページをご覧ください!