はじめに
個人開発でiOSアプリを作ってます。
丁度開発を始めてから1年位で、かつアドベントカレンダーの時期とも被っていたので記事にしてみました。
正直まだリリースしただけでコンテンツも機能も全然足りていないので、こうして記事として紹介するのは若干恥ずかしいんですが。。。
これから個人開発やってみようと思っている方に部分的に参考になる情報があるかもしれないので、どんなアプリをどうやって作っているかみたいなところをまとめました。
何を作っているのか
RockMapというクライマーのための外岩情報をシェアできる地図アプリを作っています。
レポジトリは公開しながら作っています。
このアプリが何をするためのアプリなのかを理解するためには、クライミングというアウトドアアクティビティについて知る必要があります。
世の中には岩や壁を登って楽しんでいる人達がいまして、彼らが登る外の岩を外岩(そといわ)と言ったりします。
RockMapではこの外岩の情報を各ユーザーが登録でき、他のユーザーもその岩の位置や情報を地図から確認することができます。
また、岩にはそれぞれ登るための課題が存在し、課題ごとにグレードが設定されています。
岩ごとに存在する課題とその完登記録もアプリで登録することができます。
岩と課題を登録できるようになった時点で一旦今年の夏にリリースしました。
現在総DL数は23です笑
まだまだなアプリなので、これからも開発していくぞという気持ちを込めて記事のタイトルを「〜を作っている話」という形式にしました。
なぜ作っているのか
もっと外岩を登るまでの工程を楽にしたかったからです。
外岩に登るまでのカスタマージャーニーは以下のようになると思っています。
(見様見真似で作ったので本職の人が見るとおかしいところが多々あるかもしれません。。。)
上の図でたくさん出てくるのがトポ(岩や課題がまとまっている本)とGoogleMapの2つです。
この二つを使って岩場に行ってから登りたい岩や課題を探すのですが、それぞれ外岩を探す際に辛いポイントがありました。
まずトポではマップ上のどこに自分がいるのか把握するのが難しいです。
トポにはエリアのマップとそのエリア内の岩が記載されているのですが、そのマップを見ながら岩にたどり着くためには常に自分がマップのどこにいるのか目印を確認しながら探す必要があります。
そしてその目印も経年変化で無くなっていなり様子が変わっていたりと、参考にならない場合があります。
GoogleMapに岩が登録してある場合は自分の居場所を地図で確認しながら岩に向かっていくことができるのでたどり着くのが簡単です。一方で、GoogleMapの場合は岩や課題について知るために十分な情報が登録されていないことも多く、課題の内容や周辺の様子を確認するには結局トポも交互に見比べる必要がありました。
岩場は足場が悪いことも多く、スマホとトポを両方見ながら歩くのも大変で、なんとかこれを一つのアプリに統一したいと思って考えたのがRockMapでした。
そのため、以下が想定しているメインのユースケースです。
- 岩場に行く
- 岩場でRockMapを開く
- 登りたい岩の場所を確認し、そこまで移動する
- 岩にたどり着いたら課題を確認する
- 登る!
- 完登を記録する
どうやって作っているのか
Backend
全体的にFirebaseを使っています。利用しているのは以下の機能達です。
- FirebaseUI
- ログインと認証に利用
- Firestore
- データ管理に利用
- Storage
- 画像の保存先に利用
- CloudFunction
- 一部のDocumentの操作に利用
- Storageの画像のurlを作成してDocumentに書き込むのに利用
- Analytics/Crashlytics
- 分析に利用
- RemoteConfig
- 強制アップデートのために利用(用意しただけでまだ使ってないけど)
データ構成
FirestoreやStorageは以下の構成になってます。
Firestore
Userを先頭のCollectionにしてUserの登録する岩、課題、完登記録をそれぞれSubCollectionにしています。
この構成にすることでセキュリティルールを記載する際に各ユーザーが自分の登録したデータのみ編集・削除するルールを書きやすくしています。
一方で、岩ごとにどの課題を持っているかなどは課題に岩のidを持たせることによって実現しており、そういった面では冗長な構成になっている一面もあります。
Storage
各Collectionごとにパスを用意しています。
CollectionごとにIcon、Header、Imagesなど、用途に応じてパスを用意し、そこに該当の画像を保存しています。
また、画像の保存をトリガーにCloudFunctionが発火し、画像のurlを生成してDocumentに書き込むようにしています。
これによってDocumentから直に画像を取得でき、Storageへのリクエストのロジックを減らしつつ画像の取得時間を短くしたりしています。
Functionやセキュリティルールは以下のレポジトリで公開しています
アーキテクチャ
以下の構成にしてます。
-
Presentation
- UIに最も近いレイヤー
- 画面ごとにView、ViewModel、Routerを持つ
- ViewModelからDomainレイヤーを利用する
-
Domain
- UIにもデータにも依存しないレイヤー
- UseCaseとEntityを持つ
- Dataレイヤーから取得したデータをアプリで利用したい形にMapするMapperを持つ
-
Data
- アプリが利用するデータを操作するレイヤー
- Repositoryを経由してFireStoreやローカルのデータにアクセスする。
あとは全体的に使いたい拡張をUtitlitiesというパッケージに閉じ込めたりしてます。
図にするとこんな感じです。
このような作りになった背景は色々あったんですが、
- データへの依存を一部のレイヤーに閉じ込めて、FirestoreからBackendを他のものに取り替えるときに一部だけ修正したら完了するようにしたくてDataレイヤーを用意した
- FireStoreのデータモデルをPresentation側で使うときにから余分なものを省きたくてDomain層を用意した。
- 画面遷移やUIに依存しない画面のロジックを切り出してたらViewModelとRouterができた。
みたいな感じで今の構成になりました。
けど今の構成だと依存解決の時にDomainもDataまで一気にimportする必要があって、それは本来Presentationには不要だと思います。
なのでDomainとPresentationは同じPackageに統一してDataだけimportする形に修正することも検討中です。
これからどうしていきたいか
色々あります。
機能面
- 全文検索や半径検索による検索機能をつけたい
- Homeタブを追加して地図以外からもコンテンツにアクセスできるようにしたい。(最近登録された岩と課題や近場の岩なんかをおすすめしたい)
- エリアの情報を岩と課題に持たせて、検索に含められるようにしたい
- うるさくない程度に広告を入れたい
- 不適切なコンテンツをユーザーが報告できるようにしたい
開発面
- CD環境を整備して、releaseブランチのビルドが自動的にAppStoreまでアップロードされるようにしたい。
- 画面全体をSwiftUIに作り替えたい
- Domainのパッケージはメインのターゲットと合体させたい。
- builderを用意して依存解決したい
デザイン面
- ダークモードに対応したい。
- 岩と課題の詳細ページのデザインが似通ってて分かりにくいので、どっちがどっちかぱっと見で分かるようなデザインに変えたい。
その他
- 岩と課題のコンテンツをもっと入力したい。御岳と小川山と瑞牆あたりの主要な岩と課題は載せたい。
- コンテンツが揃ってきたあたりでSNSで宣伝したい。
おわりに
記事を書いていてまだまだやりたいことができてない感じがして落ち込みました。。。
ただ昨年に開発を開始してからミニマムではあるけどリリースでき、現在も継続的にアップデートしているというのは個人的には大きな進歩かなと思います。
来年は上述した「これからやっていきたいこと」はもちろんのこと、継続的に改善していってもっとこのアプリをクライマーの人達にとって役立つものに仕上げていきたいです。