クロスプラットフォーム使って動画プレイヤーをつくろう

やること
- React Native / Flutterで簡単な動画プレイヤーつくって比較してみる
- React NativeはExpoを使う
仕様
- ユーザーは端末の動画をアプリ上にアップロードできる
- アップロードした画像は一覧表示されて、タップしたら再生
- ローカルストレージに保存(サンプルなので)
- 動画プレイヤー画面にはシークバーがあって、巻き戻しボタン・再生ボタン・早送りボタンがある

React Native Expo
npx create-expo-app react-native-app
npm run android
npm run ios
npm run web

ドキュメントを読んでいく

Expo Application Servicesはクラウド型のfastlaneみたいなサービス
CI/CD向けにビルドしたりストア提出したりが可能

出力したiOSプロジェクトが見たかったが、フォルダがなかった。
Expoのデフォルトだと、Expo Managed Workflowになっているらしく、iOS/Androidの出力は吐き出されない設定。
このコマンドで出力できた。
npx expo prebuild
// revert
npx expo prebuild --clear

clearコマンドは効かなかった……
# ネイティブフォルダを削除
rm -rf ios android
# キャッシュもクリア
rm -rf node_modules
npm install

React NativeまだCocoaPodsなんだ

- AppDelegateでRCTReactNativeFactoryを呼び出して、あとはライブラリ任せ
- 中身はこのObjective-C++コード
@UIApplicationMain
public class AppDelegate: ExpoAppDelegate {
var window: UIWindow?
var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
var reactNativeFactory: RCTReactNativeFactory?
public override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
let delegate = ReactNativeDelegate()
let factory = ExpoReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
reactNativeFactory = factory
bindReactNativeFactory(factory)
#if os(iOS) || os(tvOS)
window = UIWindow(frame: UIScreen.main.bounds)
factory.startReactNative(
withModuleName: "main",
in: window,
launchOptions: launchOptions)
#endif
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

- Yoga Layout Engineというのが大事らしい
- Flexbox記法 → Yoga → iOS/Android座標 → 画面描画
- またNext.jsとReact Nativeを併用するフレームワークもあるっぽい

Expoのドキュメントより先にReact Nativeのdocs読むべきだった

こんな対応関係らしい。わかりやすい
React Native UI Component | Android View | iOS View | Web Analog | Description |
---|---|---|---|---|
<View> |
<ViewGroup> |
<UIView> |
A non-scrolling <div>
|
A container that supports layout with flexbox, style, some touch handling, and accessibility controls |
<Text> |
<TextView> |
<UITextView> |
<p> |
Displays, styles, and nests strings of text and even handles touch events |
<Image> |
<ImageView> |
<UIImageView> |
<img> |
Displays different types of images |
<ScrollView> |
<ScrollView> |
<UIScrollView> |
<div> |
A generic scrolling container that can contain multiple components and views |
<TextInput> |
<EditText> |
<UITextField> |
<input type="text"> |
Allows the user to enter text |

Android TVやtvOSのサポートはこちららしい

App StoreへのリリースもExpoで便利になっているらしい
CLIツール入れて、App Store向けバイナリつくってな雰囲気
Expo Application Servicesというサービスで、無料ユーザーは30回/月までビルド可能
CDをまるっとやってくれる

Expo Goを使うとアプリのビルドなしで試せる
(WebViewかと思ったら一応アプリとして動作している模様)

Webだと動画データの保存に失敗するなと思ってたら、ローカルストレージにそんなに大きなデータ入れられないかららしい
(Google Chromeだと10MB上限)
逆にモバイル環境ってその縛りないんだな

MapKitを使いたいときはreact-native-mapsを使う
この手のライブラリが各SDK向けに存在する模様

状態管理とテストライブラリがあんまりわかってない
- ZustandかJotaiがいいらしいけど、標準のReact hooksではダメなんだろうか
- グローバルな状態管理、useContextでもいけるので、それだと何がまずいのかわからない
- テストはjestというのが組み込まれているらしいけど、どう使うんだろ
- ↑組み込まれてはいなかった……jest-expoというのがあるらしい

jest-expoを試す

よく見たら動画保存のコードめちゃくちゃ汚いな

公式ドキュメント見ながら動くテストコードをとりあえず書けた
あまり意味のないテストだけど、npm run test
で動く
CIに組み込むとかはまあやればできるでしょう
testing-library/react-nativeのインストールがReactのバージョンが合わなくてできなかったので、
npm install --save-dev @testing-library/react-native --legacy-peer-deps
で無理やり入れた

React Nativeやったこと
- React Native Expoの環境構築
-
動画プレイヤーの作成
- ダブルタップで巻き戻し・早送り
- Webブラウザ / iOSシミュレーターでの動作確認
- API callの手法調べた
- 状態管理ライブラリの選択肢調べた
- iOS/AndroidのSDK使う方法調べた
- テストコードの作成
やってないこと
- iOS/AndroidのSDK使って動作を調整する(あんまり細かいこと出来なさそう)
- CI構築
- デリバリー(Expo使ってもなおめんどくさそう)
- 状態管理ライブラリ何がいいか
- 可読性高いコードにする

確かにiOS向けビルド遅いかもしれない

ここからFlutter

ターミナルでやるなら
flutter create video_player_app
以前試したときはVSCodeのコマンドパレットからやっていて、こっちの方が楽かも
- Flutterのextensionインストール
- Flutter: New Project
- SDKを落とす
- パスを通す(一応コマンド書いとく)
vim ~/.zshrc
- パスを追加
export PATH=$HOME/development/flutter/bin:$PATH
source ~/.zshrc
which flutter
flutter --version

Webでビルド
flutter config --enable-web
flutter run -d chrome

VSCodeでのビルド、iOSがデフォになってたけど、VSCode右下で環境を変えられた
.vscode/launch.json
を書いてもいいらしい

info.plistの許可書かないでも初回起動時は写真にアクセスできる気がする
React Nativeもそうだった気がするけど、なんでなんだろう

モバイル環境は再生できるようになったけども、Web環境のローカルストレージ化がなかなか大変なので、Webは捨てる

昨日iOSシミュレーターでビルドできなくなってClaudeに聞いても解決できなくて苦しんだんだけど、revertしたらなんとかなった
pubspecに追加したdependencyで、何かビルドのときに引っかかるものがあったのだと思われる

Flutterもできたので閉じ