【個人開発】実務未経験者がFlutterを使ってスノーボード診断アプリをリリースしたときのまとめ
この記事を書こうと思った理由
- 約1年間プログラミングを学習してきて、まだアウトプットとして学習記事を残したことがなく、一度投稿してみたいと考えていたからです。
- zennを選択したのは、UIがとても見やすく学習の際によく活用していたからです。
- 今回、スノボセレクターというスノーボード診断アプリをリリースしたので、そのまとめとして記事を投稿します。
このアプリを開発しようと思った理由
- プログラミングを学習する当初の目的の一つに、自分で考えたアプリを開発したいとうことを考えていたからです。
- 今まで学習をしてきて、主に課題や教材などでアプリ開発をしており、1から自分で作ったといえるアプリがなかったからです。
- これまでvue.jsでwebアプリを中心に学習をしており、一度モバイルアプリを開発してみたいと思ったからです。
- 転職を考えているので、転職活動のポートフォリオとしても作成したいと思っています。
スノーボード診断アプリにしたきっかけ
- スノーボードが好きなので、スノーボードに関するアプリを一度作りたいと考えていたからです。
- 今回は特にスノーボード初心者のためのアプリとして開発しました。
- スノーボードは種類が多く、初心者の方が自分に合ったスノーボードを見つけるのは難しいと感じたのでスノーボード診断アプリを開発しようと思いました。
- 実際に友人からスノーボードを購入にあたり、相談を受けた経験もありました。
- スノーボードを初める方の自分に合ったスノーボードを見つける手助けができればという思いがありました。
- 今回は初めてのモバイルアプリの開発ということも考慮し、特にスノーボードの形状に注目した簡易的な診断アプリの開発を行いました。
リリースしたアプリ
AppStore:
Google Play: 現在、申請をリジェクトされ対応中...
このアプリでできること
自分に合ったスノーボードの情報を知ることができます
- 10問の簡単な質問に答えて診断結果を見るだけです
- 診断結果からあなたのライドスタイルとおすすめのボードを確認できます。
- ボードの情報に関しては、ボードの形状の画像とレーダーチャートを確認してもらうことでより詳細にボードを理解することができます。
診断結果は履歴として振り返ることができます
- 診断結果画面は履歴として保存されます。
- 診断結果をSNSなどに共有することができ、SNSを通してスノーボードについての相談などに使えます。
おまけ機能
- ダークモードに切り替えることができます。
-
BGMを流すことができます。
このアプリの使い方のリンク:
https://www.notion.so/9e3f8144690b465a9990911ade15e233
設計
学習中のアプリ開発でも設計しなかったり、アプリの概要を理解していないまま実装すると、つまずいた時に、今自分がどこの工程で止まっていて、何を調べれば良いのかが分からなくなり、モチベーションが下がってしまうことがありました。
この状況を未然に防ぎたく、かなり小さい規模のアプリですが、自分なりに画面とクラスの設計をしました。
画面設計
有名なアキネーターというアプリを参考にfigma作図しました。
実際すべての画面の実装がこの設計図通りの実装にはならず、ところどころ変更しながら実装したのでもっとしっかりとした画面を設計できるようになりたいと思いました。
クラス図
この設計の段階では後で記述するMVVMにならう、作図ができず、とりあえず思うように書いてみました。
実際の実装では診断結果の項目を減らしたり、そもそものflutterの書き方に慣れていない部分も合ったので適宜変更しました。
MVVMアーキテクチャ
- MVVMとは
- View UIを描画し、ユーザーからの入力データを受け取る
- ViewModel Viewから入力されたデータを変換しModelとして持ち、ModelのデータをViewに渡しで画面の更新を行う
- Model データを持って、View(UI)と直接のやりとりはしない
今まで学習してきた経験からView側には不要な処理を書きたくないと思っていました。
アーキテクチャは難しく、簡単にしか理解していませんがMVVMの構成にすることで、View側にデータの整形処理や更新処理、DBとの処理を記述することなく、関心の分離ができ、コードの可読性も保てるので今回採用しました(最近のフロントエンド開発+firebase or モバイル開発+ firebaseではMVVMで開発をすすめることが多いらしいです、このアプリではローカルのDBしか使用していません)。
参考記事
ディレクトリ構成
他のMVVMで開発している方の記事などを参考にして、ディレクトリを構成しました。
状態管理でriverpodを使っているのですが、自分はViewModelにproviderも定義しているのですが、providerの定義はproviderディレクトリを作成してそこに記述している方もいたのでどちらベストなのか判断ができませんでした。
※ご存知の方いれば、教えていただきたいです🙇
lib
├── app.dart
├── const
│ ├── bottom_bar_index.dart
│ ├── question.dart
│ ├── result.dart
│ ├── ride_type.dart
│ ├── setting.dart
│ ├── snowboard.dart
│ └── theme.dart
├── db
│ ├── result.dart
│ └── theme.dart
├── firebase
│ ├── firebase_analytics_config.dart
│ └── firebase_crashlytics_config.dart
├── firebase_options.dart
├── generated_plugin_registrant.dart
├── main.dart
├── model
│ ├── answer.dart
│ ├── answer.freezed.dart
│ ├── page_state.dart
│ ├── page_state.freezed.dart
│ ├── question.dart
│ ├── question.freezed.dart
│ ├── question.g.dart
│ ├── result.dart
│ ├── result.freezed.dart
│ ├── snowboard.dart
│ ├── snowboard.freezed.dart
│ ├── snowboard.g.dart
│ ├── theme_status.dart
│ └── theme_status.freezed.dart
├── view_model
│ ├── answer_view_model.dart
│ ├── diagnose_view_model.dart
│ ├── history_view_model.dart
│ ├── indicator_view_model.dart
│ ├── page_view_model.dart
│ ├── question_view_model.dart
│ ├── setting_view_model.dart
│ └── theme_view_mode.dart
├── views
│ ├── diagnose
│ │ ├── diagnose_content.dart
│ │ ├── diagnose_result.dart
│ │ └── diagnose_top.dart
│ ├── history
│ │ ├── history_detail.dart
│ │ └── history_top.dart
│ ├── settings
│ │ ├── error_page.dart
│ │ └── setting_top.dart
│ └── top_page.dart
└── widgets
├── board_tile.dart
├── bubble.dart
├── delete_dialog.dart
├── end_dialog.dart
├── history_list.dart
├── radar_chart.dart
├── result.dart
├── ridetype_tiles.dart
└── share_button.dart
参考記事:
アプリの内の画面一覧
診断トップ画面 |
---|
診断内容画面 |
---|
診断結果画面 |
---|
履歴トップ画面 |
---|
設定画面 |
---|
設定画面の項目でお問い合わせはGoogleForm、アプリの説明ページとプライバシーポリシーはNotionを使って作成しました。
- お問い合わせ: https://docs.google.com/forms/d/1OT6YSGMqR1C2TGJCo7YuQnCaQIMURccL-6dlrkpcgSs/edit
- アプリの説明: https://www.notion.so/9e3f8144690b465a9990911ade15e233
- プライバシーポリシー: https://www.notion.so/379101b2b6b149b980120cb870e8ca50
使用した技術とパッケージ
-
flutter
- 安易な理由かもしれないがflutterを学習しようと思ったきっかけは、一つのコードでiosとandroidの2つのプラットフォームにリリースできることに知りflutterを選択しました。
- flutterのコンセプトで「Focus on the user and all else will follow. 訳:ユーザーに焦点を当てれば、他のすべては後からついてくる」ということが公式にあります。これはflutterにとってのユーザーは開発者で、開発者のユーザーはflutterのエンドユーザーであり、flutterは、flutterアプリケーション開発者のユーザー = エンドユーザーを最も大切にし、Flutterアプリケーション開発者をその次に大切するという意味らしいです。 私は、アプリ(アプリという商品)を提供することに限らず、なにかの商品を提供するやり取り(商売)をする上で、1番大切なことはそれを使う人(ユーザー)目線を忘れないことだと考えているので、このflutterの考え方にとても共感しました。またユーザーだけでなく、開発者のことも考えているのでその点も良いなと思いました。
-
状態管理パッケージ
- riverpod
- StateNotifier
- freezed
MVVMのアーキテクチャと相性が良いと有名でこのパッケージ組み合わせで状態管理を行いました。
参考にした記事の多くもこの組み合わせで開発をしていました。
最近riverpodの公式サイトが日本語対応したみたいで、一番助けれました。
- ローカルDBパッケージ
診断結果の保存にsqflite
ダークモードの保存にshared_prefrerences
まだこのアプリを開発する前は、firebaseのColudFirestoreを使用したいと考えていましたが、自分の今回作りたいアプリでは無理に採用しなくてもローカルDBで実装ができると思ったので使いませんでした。
-
firebase
-
Analytics
どれくらいの人が使っているのかを知りたいと思ったので組み込みました。
※AppStoreConnectとGooglePlayConsoleでも似たような情報を取得できることは当初知りませんでした。 -
Crashlytics
アプリの使用中にクラッシュしてしまった場合のデータの取得に必要なため組み込みました。
-
Analytics
-
その他パッケージ
-
awesome_dialog
アプリ内で表示するdialogが通常のdialogでと簡素過ぎたので、こちらのパッケージの整ったdialogを使用しました。
スノーボードの特性をレーダーチャートとして可視化したかったので組み込み
診断結果をスクリーンショットする
スクリーンショットをSNSなどで共有できるようにする
アプリないでBGMを流したかったので組み込み
簡単なアプリレビューができるdialogの表示
アプリ説明ページやプライバシーポリシーをwebサイトで表示させるため
このアプリに組み込まれているパッケージの一覧とライセンスを表示させるwidgetの表示
スプラッシュ画面の作成(アプリ起動時の画面)
-
開発環境のみ使用
-
build_runner
freezedファイルの生成の際に使用 -
flutter_launcher_icons
スプラッシュ画面の画面 -
sqflite_common_ffi
sqfliteのunitテストで使用
テストの実装
テストは今まで実装したことがなく、経験も兼ねて今回自分なりに書いてみることしました。
※このアプリはTDD開発(テスト駆動開発)で作成していません。
Flutterのテストには、unitテスト・widgetテスト・インテグレーションテストの3つテストあります。
とりあえずunitテストから実装してみました。unitテストでクラスの状態とメソッドをテストし、
widgetテストで対象widgetが正しく描画をするか確かめるというものでした。
インテグレーションテストは実際にアプリを動かす流れを設定し、シミュレーターでアプリを動かし、期待した動作を行うかを確かめるものでした。
個人的な感想ですが、テストをしてみて小規模アプリということもあり、widgetテストとインテグレーションテストはどちらか片方で良い気がしました。
https://drive.google.com/file/d/1O54-ZXk9xUKCQvIBG_3lDoO3HABRYk-m/view?usp=sharing
インテグレーションテストの様子、画面はタップしていません。テストコードを記述することでこの様にシミュレーターでテストすることができます
他の方の技術記事などを読んでいるとTDD開発が実務では使われていることがあるらしく、今回テストを実装してみてこの開発方法で進めることができれば、安全なコードを手戻りがすくなく開発でき、少なからずですが実務で使われている理由がわかった気がしました。
大変だったこと
-
初めてのflutter
今までwebアプリの学習をしており、命令型vue.jsから宣言型のflutterへ変わり、画面の描画がhtmlとcssからwidgetになり、すべてwidgetクラスで画面構築をするので、初めはwidgetの使い方と宣言型の記述をなれるまで少し苦労しました。 -
設計について
画面とクラスの設計を行ったが、上手な設計ができていないため、何度も手戻りが発生しました。 -
アニメーション
アニメーションを多く盛り込み使っていて飽きない工夫をしたいと考えていたが、途中から優先度を見直し、まずはアプリのリリースすることを第一に考え、開発を進めました。リリース後のアニメーションは、ペンギンの画像が診断の進捗によって切り替わるぐらいの実装できなかったので、今後アップデートでその他にも付け加えたいです。 -
状態管理
状態管理の使い方になれるまでに時間がかかりました。
flutterの状態管理は様々な種類があり、また移り変わりのあり様々な状態管理についての様々な記事がある中でどれがベストな書き方なのかを理解するのに苦労した。
ex.) ref.readではなくref.watchを使うべきと公式には書いてあるが、ref.readを使っている記事も合ったのでこのような曖昧なところを明確にしていきたいです。
リリースコードを見てもviewModelをもっと細分化したりするのがよい気がするので、まだまだ学習してリファクタリングしたい。 -
シミュレーターと実機でのデバッグ
シミュレーターだけでのデバッグだけでなく、実機デバイスのデバッグも必要だったことに早めに気づきたいと思いました。
アプリがほとんど完成した段階で、実機デバイスのデバッグをしました。
すると、フォントサイズがシミュレーターと時とぜんぜん異なりレイアウトが崩れたので、そこをすべて修正するのに出戻りが発生しました。
iosの実機でデバッグする際には、app store connectでdevelop用の証明書を生成してアプリに組み込む作業も必要で、参考記事を読んでも理解が難しくデバッグできるようになるまで時間がかかってしましました。
andriodに関しては実機を持っていないので、シミュレーターのみのデバッグなので不安です。 -
CI/CDの導入
codemagicをつかったCI/CDを導入しようとしたが、試しにmasterブランチにプルリクをしたタイミングでCI/CDを設定すると、debug buildした時に発生するこの警告が理由でCI/CDが通らず、いろいろ試しましたが、解決方法が分からず導入を見送りました。
※この警告の解決方法を知っている方がいれば、教えていただきたいです🙇
: Warning: Operand of null-aware operation '?.' has type 'SchedulerBinding' which excludes null.
../…/lib/flare_render_box.dart:167
- 'SchedulerBinding' is from 'package:flutter/src/scheduler/binding.dart' ('../../flutter/packages/flutter/lib/src/scheduler/binding.dart').
package:flutter/…/scheduler/binding.dart:1
SchedulerBinding.instance?.cancelFrameCallbackWithId(_frameCallbackID);
^
: Warning: Operand of null-aware operation '?.' has type 'SchedulerBinding' which excludes null.
../…/lib/flare_render_box.dart:170
- 'SchedulerBinding' is from 'package:flutter/src/scheduler/binding.dart' ('../../flutter/packages/flutter/lib/src/scheduler/binding.dart').
package:flutter/…/scheduler/binding.dart:1
SchedulerBinding.instance?.scheduleFrameCallback(_beginFrame) ?? -1;
^
: Warning: Operand of null-aware operation '?.' has type 'SchedulerBinding' which excludes null.
../…/lib/flare_render_box.dart:272
- 'SchedulerBinding' is from 'package:flutter/src/scheduler/binding.dart' ('../../flutter/packages/flutter/lib/src/scheduler/binding.dart').
package:flutter/…/scheduler/binding.dart:1
SchedulerBinding.instance?.cancelFrameCallbackWithId(_frameCallbackID);
初心者は一度リリースしてからの導入でも良い気がしました。次回アップデートをする際は導入したいと思います。
-
開発期間が予定よりの大幅にかかった
設計を初めた段階では1ヶ月を予定としていました。初めてのflutterというフレームワークということと、リリースの申請の準備などわからないことが多々あり、約2ヶ月かかってしましました。 -
xcodeとandroidsutdio
今までエディターはvscodeしか使ったことがなく、flutterでも基本コードを書く場合は、vscodeでしたが、アプリ申請をする細かい設定などはxcodeとandroidSudioが必要だったので初めてで苦労しました。 -
アプリリリースについて
webアプリだと、個人開発で特にこだわったサーバーの構築などをしなければ、ホスティングサービスに上げるだけでリリースはできますが、モバイルアプリでは、AppStoreとGooglePlayへの申請の準備として証明書の準備やスクリーンショットの作成、アプリについての説明文、説明ページとうの作成や各ストアのdeveloperアカウントの登録なども必要で大変でした(お金も地味にかかりました)。特にAppStoreはかなり申請項目が多く、大変でした。スクリーンショットの作成とアプリアイコンの作成ではkeynoteや画像加工webサービスを利用して規定のサイズに揃えることが必要で初めて使うツールばかりで苦労しました。
良かったこと
- 規模の小さいアプリですが、開発からアプリのリリースまでの流れを経験できました。
- プログラミングを学習する当初の目的の一つで自分で考えたアプリを開発するということが達成できてよかったです。(これが1番大きいと思います)
- 今まで主にvue.jsを使ったwebアプリしか作ることができなかったが、Flutterでモバイルアプリついて知ることができました。
- 不慣れながら、設計から開発まで自分ひとりでやり遂げることができ、達成感がありました。
- 次は、ユーザー認証機能とfirebaseのColudFirestoreを使った実装し、今回のアプリよりも規模が大きなアプリの作成に挑戦したいと思いました。
- かなり個人的な意見ですが、webアプリの開発に比べ、モバイルアプリの開発の方が普段実用的に使うデバイスという点から、アプリ開発をしている実感が持ちやすいことが知れてよかったです。
- 自分での作成はできないと判断したペンギンの挿絵は、外注しました。ココナラとうサービスを初めて使用しましたが、とても簡単に取引ができ、便利でした。
https://coconala.com/ - テストの実装も今回の開発の目的の一つだったので、達成できて良かったです。
これからのこのアプリに追加したい機能
- 質問内容の振り返り機能
- スノーボードの専門用語の説明
- その他のギアについても診断(ブーツ、ビンディング)
- 中級者・上級者へ向けた、少し踏み込んだ質問内容の作成
- 診断結果の精度の向上
- ColudFirestoreを用いたアプリの実装でアプリ感をもっと出す
最後に
この記事を読んでいただきありがとうございます。
かなり季節外れになりましたが、簡単なアプリですので、試しにインストールし使っていただけたら幸いです!!
お問い合わせとフィードバック
このアプリに関するお問い合わせ・フィードバックについては、この記事のコメント機能またはTwitterアカウントまでお願いします。
GitHub
参考記事
Discussion