はじめに
iPhone/androidアプリ 「おかたづけタイマー」 をリリースしました。
(バグ改修もあったので現在は1.0.2です)
前作「かんたん旅行記」の開発にご興味を示していただきまして、今回はデザイナーの方にも入っていただき2人での開発となりました。
UIデザイン、自分だけでやるとユーザビリティのカケラもないものになってしまいがちですが、やはりプロが入るとすごい。
構想から1年、終業後の平日夜と休日しか使えないのでかなーり遅ーい開発でしたが、とりあえずリリースできてよかったです。
どんなアプリ?
3〜8歳くらいのお子様をお持ちの方向けのタイマーアプリです。
おもちゃで遊んだ後おかたづけが中々できない…という悩みを解消する目的で、ゲーム感覚で時間内におかたづけができるタイマーアプリとして仕上げています。
タイマーは時計の概念がわかりやすいように円形としており、時間が経つとオレンジの部分が減っていくようにしています。
時間内に終えると3つ、時間を過ぎてしまっても1つハートが貰えて、ハートを集めると新しいおもちゃが手に入るようになっています。
キャラクターは切り替え可能+追加購入可能なので、1つのキャラをコンプリートしても長く遊べる仕様になっています。
背景
前作「かんたん旅行記」の開発にご興味を示していただきまして、今回はデザイナーの方にお声がけ頂き2名で開発しました。
こちらが今回ご協力頂きました合同会社StorycolorのHPになります。アプリもこちらの会社名義で出しております。
僕もデザイナーさんも小さい子供がいることが共通点で、育児のストレスを和らげるアプリができたらいいよね!という発想から着想を得ました。同じようなタイマーアプリは既に世の中にありますが、
- キャラクターが音声で応援してくれる
- ご褒美がもらえる
- お片付けを頑張った記録を残せる
アプリはあんまりないという話になり、それらを実現する目的で開発を始めました。
開発期間
お声がけ頂いたのが2021/12で、1.0.0リリースが2022/9初めだったので、大体9ヶ月間でした。そして細々バグ解消をしていたら1月になってしまいました。この辺りは個人開発の限界で、使える時間が土日しかなかったのでどうしても亀の歩みですね。しかしUIデザインまで自分でやってたら倍はかかっていたでしょう…
使用技術
前作に味をしめて今回もflutter×firebase×revenuecatです。色々チャレンジしてみたい気もあるんですが、時間の限られる個人開発者としては一度flutterのエコシステムに触れてしまうとなかなか抜け出せなくなりますね。
工夫したこと/バグと解決
タイマーの円を減らしていく処理
stackとdrawer、riverpod providerを駆使してなんとか形にできました。なおタイマー自体の処理は以下リンクに詳しいです。先人の知恵ありがたや、、、
Zen:Flutter でバックグラウンドでも動くタイマーアプリを作った
購入画面での操作ロック
購入処理が進行している間はStackでレイヤーを1枚重ねることで、二重タップやバックを含めたあらゆる操作を禁止しました。
と思ったらAbsorbPointerなんて便利なものがあるんですね・・・今度はこちらを使おうと思います。
StackOverflow:Flutter disable touch on the entire screen
File書き込み時のiOS/android間挙動差異の吸収
Firebaseからダウンロードした画像とかをApp Directoryに保存しようとする際、iOSだと途中のディレクトリまで自動で作ってくれますが、Androidだとダメっぽいですね。「そんなディレクトリねぇよ」と怒られてしまいます。
File.create(recursive:true) にしてあげるとうまくいきます。
import 'package:path_provider/path_provider.dart';
import 'package:firebase_storage/firebase_storage.dart';
Future<void> saveImages() async{
final appDocDir = await getApplicationDocumentsDirectory();
FirebaseStorage storage = FirebaseStorage.instance;
//画像を全部引っ張ってくる
ListResult result = await storage
.ref()
.child('imgs')
.list();
for (int i = 0; i < result.items.length; i++) {
Reference ref = result.items[i];
File downloadToFile = await File(
"${appDocDir.path}/path/to/${ref.name}") //Androidだとエラーになる
await ref.writeToFile(
downloadToFile);
}
}
import 'package:path_provider/path_provider.dart';
import 'package:firebase_storage/firebase_storage.dart';
Future<void> saveImages() async{
final appDocDir = await getApplicationDocumentsDirectory();
FirebaseStorage storage = FirebaseStorage.instance;
//画像を全部引っ張ってくる
ListResult result = await storage
.ref()
.child('imgs')
.list();
for (int i = 0; i < result.items.length; i++) {
Reference ref = result.items[i];
File downloadToFile = await File(
"${appDocDir.path}/path/to/${ref.name}")
+ .create(recursive: true); //recursiveをONにする
await ref.writeToFile(
downloadToFile);
}
}
無限ハートゲットバグ
デザイナーさんの息子さんが発見してハートを荒稼ぎしていました。流石着眼点が違いますね!!!!
ハートをゲットした後の画面には「戻る」ボタンは設置しなかったのですが、左右スワイプで戻れる仕様になっていたので、それを利用して前画面に戻ってハートタップ・・・という操作ができてしまっていました。
解決策としてWillPopScopeでWidgetを囲みました。前画面に絶対戻したくない場合は必須ですね。
Qiita:Flutterで前の画面に戻らせたくない時に使うwillPopScopeを紹介する
最後に
個人開発に若干自信がついてきました。コードも前回よりもスッキリまとまった気がします。
しかし作り続けないと技術にキャッチアップできないので、今後もアイデアを形にしていけたらと思います。
最後に、子育て中の皆様の助けになることを願って↓