Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
React Native + Cloudflare Worker で個人開発アプリを作っている話
Search
Kyosuke Hiraizumi
August 07, 2024
0
120
React Native + Cloudflare Worker で個人開発アプリを作っている話
React Native Meetup #17 の登壇資料です。
Kyosuke Hiraizumi
August 07, 2024
Tweet
Share
Featured
See All Featured
Designing Experiences People Love
moore
138
23k
RailsConf 2023
tenderlove
28
810
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.2k
Web development in the modern age
philhawksworth
204
10k
Building Flexible Design Systems
yeseniaperezcruz
325
37k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
28
1.6k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
502
140k
4 Signs Your Business is Dying
shpigford
179
21k
The Pragmatic Product Professional
lauravandoore
31
6.2k
Building Better People: How to give real-time feedback that sticks.
wjessup
359
18k
The Cost Of JavaScript in 2023
addyosmani
42
5.5k
GraphQLとの向き合い方2022年版
quramy
43
13k
Transcript
React Native + Cloudflare Worker で 個人開発アプリを作っている話 2024/08/06 React Native
Meetup #17
自己紹介 平泉 京祐 / @hirai_kyo / github.com:HiraiKyo 所属: CA技研 技術:
仕事で ROS / Python / C++ 趣味・副業で Typescript / Node.js / C# 趣味: ポーカーやってます!
アプリ名: Hashigo コンセプト • テニスの対戦アプリ • ワンボタンで適切な対戦相手 が見つかる
LT趣旨 • 完成まで来たので、とりあえず自慢したかった!! • 技術的な深堀りポイントは特にない… ◦ 企画 ~ 設計 ~
実装 の俯瞰的LTができれば • ビジネスモデルから詳細設計まで質問対応するLTは珍しい(?) • (おまけ) バックエンドにCloudflare Workerを採用した話
アプリ企画
Hashigoで解決する課題 既存テニスアプリの調査 顕在ニーズ 実力が自己申告制 Lv1 ~ 10 を自分で設定 同レベルでも結構差がある ⇒
ELOレーティング導入 潜在ニーズ 相手を探すのが手間 場所, 日時, 参加者, レベル, etc… Tinderくらい手軽に探したい ⇒ マッチメイキング方式を採用 ELOベースのマッチメイキング方式テニス対戦アプリ
Hashigo システム概要 クライアント マッチメイキングへ参加 マッチ成立の通知受け取り マッチ成立後のコミュニケーション サーバ マッチメイキングの定期実行 マッチメイクアルゴリズム マッチ成立のプッシュ通知
アプリ設計
Hashigoの技術スタック・構成 フロント Expo (React Native) Typescript バック Cloudflare Worker Typescript
フロント技術選定 ビジネス要件 • マッチ成立通知 • 位置情報 • サービス開始時にユーザーがある程度必要 技術要件 •
Dartつらいかも、TS, React はたくさん触ってる
アプリ実装
Hashigo クライアント側の機能 機能要件 位置情報 expo-location 地図表示 react-native-maps ユーザプロフィールの編集 react-hook-form プロフィール画像のアップロード
expo-image-picker プッシュ通知 expo-notifications WebSocketでマッチメイキングサーバと通信 標準ライブラリ 非機能要件 認証 Firebase JS SDK UIライブラリ React Native Paper エラーログ収集 Sentry
Expo SDK が提供するライブラリを 使っただけ。
None
Thank you.
None
Q. 営業どうしてるの? A. 私がテニスしに行って宣伝してます。 暑すぎて熱中症2回なりました…。
Q. Cloudflare Worker 良い? A. とても良い。 ホットスタートなのに安い(コンテナベースではないので) DurableObjectsが直観的で使いやすい WebSocket Hibernation
APIでWebSocketのコストダウン狙える Cloudflare といえば WAF がある!
マッチメイキングサーバ OpenMatch • ゲーム向けのOSSマッチメイクフ レームワーク • マッチメイクロジックをどこまで弄れ るか分からなかった • ドキュメントが整っていない
自作 • コアであるマッチメイクロジックを 外部に依存したくなかった • Cloudflare DurableObjectsが 使いやすい! • クラウド上に存在するシングルトン クラス • WebSocket Hibernationでコス トダウンも可能
サーバサイド : Cloudflare DurableObjects の実装例 export class MatchmakingDurableObject extends DurableObject
{ private _queues: QueueType[] = []; constructor(readonly state: DurableObjectState, readonly env: Env) { super(state, env); this.state.blockConcurrencyWhile(async () => { let stored = await this.state.storage.get<string>('queue'); this._queues = stored ? JSON.parse(stored) : []; }); } async addQueue(participant: Participant): Promise<void> { const q: QueueType = { queueId: uuidv4(), participant, createdAt: new Date(), }; this._queues.push(q); await this.state.storage.put('queue', JSON.stringify(this._queues)); } } • “マルチユーザーのシングルトンク ラス”のように書ける • サーバーレスでありながら、一意 のクラスインスタンスのように振る 舞う • DBやKVStoreではなく In-memory stateにデータ保持 • 衝突はCloudflare側でキュー管 理してくれる
認証 • Firebase JS SDKを利用 ◦ Authentication, Firestore, Realtime Database,
Storageのみ 利用可能 ◦ react-native-firebase の利用も検討したが、prebuildで苦労した ◦ Firebase Crashlyticsを使わずにSentryを使うことにした • Cloudflare Access を導入してみたい
位置情報 • expo-location を利用
地図表示 • react-native-maps を利用 ◦ GoogleMapsAPIの設定が必要 ◦ Web対応していないので、パッチ処理を行う ▪ https://stackoverflow.com/questions/76629674/unable-to-resolve-utilities-platform-error-with-metro-bundler
• Expo SDK 51ではGoogle MapsがiOSでサポートされず、Apple Mapsに置換された ◦ provider={PROVIDER_GOOGLE} を削除する ◦ https://github.com/expo/expo/issues/28705
ユーザプロフィールの編集 • react-hook-form を利用 ◦ register を使って書けない(?) control を使う export
default function App() { const { control, handleSubmit, formState: { errors }, } = useForm({ defaultValue : { firstName: "", }, }) const onSubmit = (data) => console.log(data) return ( <View> <Controller control ={control} rules ={{ required: true, }} render ={({ field: { onChange, onBlur, value } }) => ( <TextInput placeholder ="First name" onBlur ={onBlur} onChangeText ={onChange} value ={value} /> )} name ="firstName" /> {errors.firstName && <Text>This is required. </Text>} </View> ) }
画像のアップロード • expo-image-picker を利用 ◦ blobだと送信できず、 右記のような形で送信する らしい ◦ 詳細は、
https://www.simpletraveler.jp/2021/09/30/%E3%80%90reactnative%E9%96%8B%E7% 99%BA%E3%80%91formdata%E3%81%A8%E3%81%9D%E3%82%8C%E3%81%AB%E9%9 6%A2%E3%82%8F%E3%82%8B%E6%8A%80%E8%A1%93fetchxhrmultipart-form-data- %E3%82%92%E7%90%86%E8%A7%A3/ const permissionResult = await ImagePicker .requestMediaLibraryPermissionsAsync (); const result = await ImagePicker .launchImageLibraryAsync ({ mediaTypes: ImagePicker .MediaTypeOptions .Images, allowsEditing: true, aspect: [1, 1], quality: 1, }); const uri = result.assets[0].uri; const mimeType = result.assets[0].mimeType; const fileExt = uri.split(".")[uri.split(".").length - 1]; const formData = new FormData(); formData.append("file", { uri, name: `image.${fileExt}`, type: mimeType, }); const res = await fetch(uploadUri, { method: "POST", body: formData, });
プッシュ通知 • expo-notifications を利用 • expo-server-sdk-node でバックエンド実装 ◦ Cloudflare Worker
の Node.js Compatible モードがうまく機能 せず、Node.js API でエラー ▪ assert は自分で書いた ▪ zlib は pako に置換
WebSocket通信 • 標準ライブラリのWebSocketを利用 ◦ バックグラウンドからの復帰で接続切れを起こす ▪ react-native の AppStateを監視して、フォアグラウンドに戻っ た時に再接続を実行
• Cloudflare DurableObjects に WebSocket Hibernation API があ り、WebSocketがお得に使える!
UIライブラリ選定 Expo SDK 51 で使えるもの • React Native Paper •
Tamagui 使えなかったもの • RNUILib • React Native Base ⇒ ビルドまで含めてテストすべき