20210731のiOSに関する記事は3件です。

React Native(Expo)とFirebaseでカップル向けアプリを作った

この記事の背景と目的 最近、業務で「社内のPOと一緒に二ヶ月間でMVPとなるアプリを一つ作ってリリースする」という貴重な機会があった。 実際の開発は自分一人だったため、使ってみたい技術を自由に試してみることができ、非エンジニア職からの出戻りの自分にとっては学べることがとても多かった この記事では技術選定や設計の際に考えたことや、実際やってみての感想や気づき、反省点の備忘録として投稿する 作ったもの 「OurTime」というカップル(夫婦)向けエンゲージメントアプリを作成 パートナーと二人でアプリを利用し、アンケートや日々の日記を投稿することで、アプリの方から二人の話し合いを促してくれて、二人の繋がりをより強くするというコンセプト 詳しくはLPを参照 リリースは現状iOSのみ iPhone持っている人はぜひDLお願いします!→ストアリンク 使った技術について 技術スタック React Native(Expo) TypeScript Jset ESLint prettier husky Firebase Authentication Cloud Firestore Storage React Native Navigation 選定理由や感想、反省点など React Native(Expo) 自分は主にSwiftで育ってきたが、Swift以外でもiOSアプリを書いてみたいと思ったため 社内ではReact Nativeを書いてる人が多かったため、Flutterではなくこちらを選択 この時点はReact Nativeの経験は0でReactも社内の研修で一ヶ月ちょっと触っただけだったが、関数型コンポーネントとカスタムhooksを理解できればあとはそんなに苦労しなかった。 二ヶ月と期間が短かかったことと、他にも技術的なチャレンジが多かったため、楽できるところは楽をしようと思いExpoを選択 結果、Expoを選択したのは正解だったと思う。ビルドに必要なあれこれは一切気にしなくてよいし、OTAは便利だし、公式ドキュメントが充実しており、基本的にこれを信じて実装すれば間違いはない。悩む必要がないのも時間が限られてる中では楽だった。info.plistをいじりたい時など、やや悩んだりしたが、調べれば情報はなくはなかった。ビルドに20分待たされることがあったりすることを除けばかなり快適。 TypeScript React Nativeで調べた情報がTypeScriptが多かったという理由でなんとなくTypeScriptを採用 TypeScriptを使うのも今回が初めてだったため、最初はかなり悩んだが、慣れてくると静的型付けということでSwiftと似た感覚で書けるのでとても快適だった。 ただし、まだ表面的な理解してしておらず、雰囲気で使っている Jset 自動テストに関してはJestくらいしか選択肢ないんじゃないか? 今回一番苦労したのはJestかもしれない。 この時点ではJest未経験というのもあり何度もつまづいた。 開発初期では気合を入れてテストを書いていたが、全体の作業時間のうち半分以上テスト書いてることに気付き、途中からテストの粒度を粗くし、最終的にテストを書くことをやめてしまった。 これまで業務でテストを書く経験がなかったこともあり、どの程度のプロダクトのどのフェーズでどれくらいのテストを書けば良いのかという肌感覚みたいなものがなく、必要以上にテストに固執したり、逆にテストを放棄してしまったりしたことが今回の大きな反省点。 ESLint 最低限のセットアップにとどめた。確か何かの記事を参考に設定しただけで、特にオリジナルの設定などはなし。チーム開発するならもっとちゃんと理解して設定する必要が出てきそう prettier 同上 husky git commitをフックして、lintとformatを実行、git pushでtestを実行するように設定した。その代わりcircle ciなどは導入しなかった。 プッシュの度にローカルでテスト通してるんだからci環境は必要ないだろ、でいいのかは不明 犬が可愛い Firebase 開発が一人でバックエンドは楽をしたかったためfirebaseを選択。なんでfirebaseかというと「モバイルアプリエンジアといったらfirebaseでしょ」みたいなイメージから。 この時点はfirebaseを使った開発の経験はなし(swiftプロジェクトにCrachlyticsを導入だけしたことはあった) firebaseの採用は良い判断だったと思う。authenticationは認証基盤をあっという間に用意してくれるし、firestoreはなんでもjsonで突っ込んでおけるし、簡単にリアルタイム同期できてアプリ側のデータ管理が楽だった。難点としてはちょっとでも複雑なクエリが書けないこと。 複数のカラムに対するwhereができなかったり、whereしたカラム以外でorder byできなかったりは苦労した。 storageはユーザーのプロフォール画像を保存するために利用したが、これも特に迷うことなく使えて楽だった。 React Native Navigation 一番スタンダートっぽいものを利用した。たしかExpoでもお勧めされてた。 スタックナビゲーションとタブナビゲーションの組み合わせがちょっと複雑だったり(特定の画面ではタブナビゲーションを非表示にしたいとかが難しい)、typescriptで使う時にちょっとよくわからなかったりしたけど、大きな不満はなかった。 タブのデザインも結構カスタマイズできるし、やってないけど多分上部のナビゲーションバーも結構いじれると思う 設計について ディレクトリ構成(一部抜粋) - __tests__ - components - containers - screens - hooks - navigator - assets - images - src - components - containers - screens - hooks - __mocks__ - repositories - __mocks__ - navigator - contexts - theme - .env - app.json - App.tsx - firebase.ts ディレクトリ構成の感想、反省点など src/ と __tests__/ 基本的にプロダクトコードはsrc以下に収めるようにした __tests__以下にはsrcと同じディレクトリ構造とし、テストコードを整理した assets images以外のリソースを入れるかもと思いassets/images以下に画像ファイルを置いたが、特に画像以外のリソースが必要になるタイミングがなかったため、とくにimagesを作る必要はなかった。フォントとか音声ファイルを使うタイミングでディレクトリ分ければ十分だった。 root/以下 上記のディレクトリ構成では省略しているが、package.josnやjest.config.tsなど設定ファイル系はroot直下に配置している。 やや煩雑としてしまった印象もあるが、プロダクトコードとは切り分けられているからまあいいかなという感想 .envとfirebase firbaseの環境変数は.envに記載して、firebase.tsで読み出して、ExpoのreleaseChannelで本番と開発を切り替えるようにした App.tsx App.tsxは極力なにも書かないようにした 認証の読み込みと、アプリ全体通して表示したいロード中のインジゲーターの表示コンポーネントだけを配置した。 開発初期段階ではfirebaseの認証ロジックや、ナビゲーションを書いていたのでかなり巨大だったが、それぞれカスタムhooksとナビゲーションだけをするコンポーネントNavigator.tsxに切り出すことでスリム化を実現した。 ロード中のインジゲーターコンポーネントも最終的にはApp.tsxに書かなくてもいける方法を思いついたので、やろうと思えばApp.tsxは認証用のカスタムhooksの呼び出しだけOKになりそう src以下の設計で感想、反省点など ビューに関するコンポーネントはcomponents, containers, screensで切り分けた 大きな方針としては、React Native Expressで紹介されていたコンテナコンポーネントとプレゼンテーションコンポーネントの考えにのっとて、ビジネスロジックを持つコンポーネント(Screens)と、propsを受け取ってイベントを返すだけのコンポーネント(componentsとcontiners)に分けることにした しかし一部誤解したまま進めていたため、continersの役割が間違っている。正しくはcontainersもビジネスロジックを持つことができる。ここら辺の正しい解説は本家のページを参照してもらいたい。 今思うと素直にAtomic Designを採用していればよかったと感じる。 components ButtonやText、TextInputなど、アプリ全体通して汎用的に使うコンポーネントはここに入れた。Atomic DesignでいうところのAtoms的なノリだが、別にcomponentsの中で他のcomponentsを参照することは禁じなかった。 たとえばボタンのラベル部分にはTextコンポーネントをimportして使っている containers 本来の役割は「ビジネスロジックを持った、画面全体ではないがある程度の塊を持った複数の画面で使いまわされるコンポーネント」を入れておくディレクトリ。 例えばユーザーのプロフィールのコンポーネントなど 今回は「ビジネスロジックを持たずに、画面全体ではないがある程度の塊を持った複数の画面で使いまわされるコンポーネント」として扱っていた。 とはいえ、後述するが結局ビジネスロジックは全てカスタムhooksに切り出していたため、カスタムhooksをscreensで読み出してpropsで渡すか、直接containersで読み出すかの違いぐらいしかなかったとは思う。 Atomic DesignでいうところのMoleculesやOrganismsに該当すると考えられるが、厳密なルールを決めていなかったため、componentsとcontainersの区切りが曖昧で感覚的なものになってしまった。 componentsよりは汎用性が低いがscreensではないものがここに入っている。 containersが他のcontainersをimportすることはあるが、componentsがcontainersをimportすることはない screens swiftのViewControllerの感覚で書いた。 いち画面いちScreen.tsx 必要なデータやビジネスロジックはカスタムhooksからもってきて、表示したいcomponentsとcontainersにpropsで渡す役目。また返ってきたイベントの処理もscreesで行う。 コンポーネントの表示非表示や、表示するためのデータの整形などもここで行う。 ビジネスロジックとビューの間を取り持つことになるのでimportするものが多くなりがちだった。 これを防ぐには、本来の意味でのconainersの使い方をすればロジックを分散させたりしてscreensをスリムにできたのかもしれない。あとは冗長なpropsのバケツリレーも防げたと思う。 hooks カスタムhooksはここにまとめた 実際に作ったカスタムhookはuseAuth(firebase authを用いて認証を行う)やuseCurrentUser(認証したユーザーのデータを取得したり、更新する), useDiary(ユーザーの投稿した日記一覧を取得したり、追加フェッチしたり、追加削除更新する)などなど 主な役割はrepositoryを操作してデータを取得し適切に加工しscreensに渡すことと、screensにデータの追加更新削除のメソッドを渡すことが多かった。 これによりscreensはデータのあれこれについて気にしないで済んだ。 逆にカスタムhooksはビューに関しての責務はない repository自体は原則データの永続化についてのみの責務しか持たせなかったので、どんなデータを要求するかといったロジックや、バリデーションロジックはカスタムhooksに集約されている。 ビジネスロジックはhooksに集約するという方針でなんでもかんでもカスタムhooksにしていったため、「それhooksじゃなくても良くない?」みたいなものも多い気はするが、責務の分離という観点では成功だったと思う。 __mocks__の中に同じ名前でuseHoge.tsを作成するとテストの時にいい感じにモックを使えて便利だった repositories データの永続化と、アプリないで使うTypeの定義を行なっている 例えばUserRepositoryではfirestoreからユーザを検索して取得するのと、type Userを定義して、useCurrentUserにUserを返したりしている。 保存先は今回はfirestoreか、ローカルストレージだが、repository以外では保存先を意識せずに済んでいる。 扱うリソース(UserとかDiaryとか)ごとにrepositoryの実装がそれぞれ特殊になってしまった。 この辺りはfirestoreの使い方が試行錯誤で慣れてなかったせいもある もっと抽象化したかっこいい実装にしたかった。 navigator Navigator.tsxを入れるためだけのディレクトリ。他と階層を合わせるために作成。 ナビゲーターを切り出したのは正解だった。すぐに大きくなるため。 今回は一つのNavigator.tsxに全てのナビゲーションを書いたが、アプリの規模に応じてナビゲーターも分割すると見通しが良くなりそうと感じた。 contexts ReactのContextを各所でimportして使うために用意。 多分使い方の問題だけどContextとReact Native Navigationとの相性がよくなかったため、あまりContextを活用しなかったのであまり感想がない。 theme Colors.tsとFontSize.tsを作り、アプリ内で使う色とフォントサイズを一元管理した。 その他の感想、反省点など ユーザーデータの管理はauthentication+firestore+storageの合わせ技で行った authenticationでアカウントを作成し、authenticationで発行されたuidをdoc名にしてfierstoreにusersを作成した メールアドレスも含めた全てのユーザ情報はfirestoreに保存した。authenticationでもユーザ名の保存などはできたが、authenticationに保存できるデータだけでは足りなかったため、authenticationは認証目的だけに絞った。 プロフィール画像はstorageにアップロードし、そのDLリンクをfirestoreにusersに保存した アプリ内でプロフィール画像を表示する時には毎回DLするとお金がかかりそうだったため、UserRepository内でキャッシュし、ローカルパスを使い回す方法で節約した 全体通した感想とまとめ 二ヶ月という限られた期間内に、新しい技術をいくつもキャッチアップしながらMVPとなるアプリのリリースまで達成できたのは大いに自信につながったし、達成感も味わえた。 責務の分離も概ね成功したし、初めてのReact Nativeアプリにしてはいい感じに作れたと思う。 複雑なことをしないアプリであればExpoとfirebaseの組み合わせは最速でリリースできる最強の組み合わせなんじゃないかと感じた(他をしらないだけだけど) 後半、リリースに間に合わせるために妥協が多くなってしまったことと、結局テストは書けなかったことが反省。 今回は実装に必死だったためあまりできなかったが、POのやりたいことの具現化やデザイナーとのデザインの調整など、もっと上流からPOをサポートできればよりよいものが作れたかもしれない。(POにはさらに上流の「どんな課題があって、ユーザーには何が必要なのか」みたいな部分に集中できる環境を提供したかった) だらだらと感想を書いてきたが、アプリは結構いいものができたと思うので、iPhone持っている人はぜひDLお願いします!(2回目)→ストアリンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPhoneでlocalStorageが消える

CodePen上で、localStorageを使って遊んでいたところ、パソコンのChromeでは保持できてるのにiPhoneでは保存したlocalStorageのデータがたびたび消える現象が起きた。 試した感じだと、サイトを行き来するとすぐに消えてしまうっぽい。 (iPhoneの) Chromeアプリを落として再度開くと消える。 発生したversion: iOS版Chrome 92.0.4515.90 この辺の絡み? https://www.torimochi.jp/871/ 参考用 https://www.google.co.jp/search?q=javascript+localstorage+%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[2021/07/31/投稿]個人開発アプリにSwiftLintを導入する

投稿の経緯 機械的にコードチェックをおこない、コードの統一化、可読性の向上、、開発効率の向上を期待してSwiftUIで開発中の個人アプリにはじめてSwiftLintを導入したので記事にしました。 SwiftFormatを導入した記事はこちら? [2021/07/30更新]個人開発アプリにSwiftFormatを導入する 環境 Swift version 5.4.2 Xcode version 12.5.1 SwiftLintとは SwiftLintとはSwiftの静的解析ツールで、コンパイラよりも詳細にソースコードの解析をすることができます。こちらのGitHubのルールに沿ってコードを解析します。 SwiftLintの導入 同一プロジェクトに導入したSwiftFormatをCocoaPodsで導入したので 統一してCocoaPodsで導入します。 PodfileにSwifLintをセットしてpod installします。 Podfile. pod 'SwiftLint' コマンドラインからの使い方 ターミナルを開いて導入したアプリのディレクトリでコマンドを叩くと静的解析の結果が表示されます。 cd プロジェクトのルートディレクトリ Pods/SwiftLint/swiftlint ビルド時に解析させるように設定する コマンドラインで叩いたPods/SwiftLint/swiftlintをXcodeで実行できるようにしましょう。 プロジェクトのアプリ用ターゲットの左上の + ボタンをタップして、Build PhasesにNew Run Script Phaseを追加し、SwiftLintを実行できるように設定します。 "${PODS_ROOT}/SwiftLint/swiftlint" このようになればSwiftLintをXcodeで実行できます。 ルールをカスタム SwiftLintのデフォルトルールで発火する警告を発火させたくない場合などにルールをカスタムします。例えば、変数名の最小文字数や1行あたりの文字数などなど... チーム開発の場合はルールの設定内容をチーム内で議論した方が良さそうですね。個人開発の場合はお好みで。ちなみに私はあまりカスタムせず、デフォルトに従ってコードを書いています。 それではルールをカスタムします!今回は以下のルールを設定します。 ・Podsを対象外にする ・multiple_closures_with_trailing_closureの無効化 ・conditional_returns_on_newlineの有効化 ・conditional_returns_on_newlineの有効化 ・1行あたりの文字数制限を450に変更 ・変数名が1文字以上なら許可に変更 ・Type名が2文字以上なら許可に変更 SwiftLintではルールの設定に.swiftlint.ymlを作成し、そこに記述します。 ターミナル. touch .swiftlint.yml でファイルを作成して ターミナル. open .swiftlint でファイルを開いて、ルールを設定します。 ・SwiftLintの対象外とする場合はexcluded:を使う ・無効とするルールを設定する場合はdisabled_rules:を使う ・デフォルトで無効とされているルールを有効とする場合はopt_in_rules:を使う ・ルールをカスタムする場合はルール名:を使う 今回カスタムするルールと?こちらを組み合わせると。以下のようになります。 swiftlint.yml # SwiftLintの対象外とする excluded: - Pods # 無効とするルール disabled_rules: - multiple_closures_with_trailing_closur # デフォで無効とされたルールを有効とする opt_in_rules: - conditional_returns_on_newline # ルールをカスタム line_length: 450 # 1行あたりの文字数制限を450に変更 identifier_name: # 変数名が1文字以上なら許可に変更 min_length: 1 各ルールの詳細はこちらに記載されているのでざっと目を通すことをオススメします。 これでSwiftLintを導入できました。参考にしてください! お知らせ 現在、iOS開発案件を業務委託で募集中です(副業)。TwitterDMでご依頼をお待ちしています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む