Open76

Flutterで日記アプリを作ろう

ken1flanken1flan

はじめに

会話メモアプリを作ってて、少し作り方がまとまってきたから、かんたんなものを最初から作り直してみよう、そんな動機です。

やっぱり、かんたん、といえば日記アプリでしょう?

ken1flanken1flan

こんなアプリ

日記の一覧、編集画面があって…
日記には画像を一枚添付できる感じ。

ken1flanken1flan

データの保存方法

  • 日記はSQLiteに保存 サンプル通りにDBを初期化しようとしてうまくいかなかった…。不安定な部分もありそうなので、やめる…。ObjectBoxにしてみようかな…。
  • 添付画像はアプリケーションのドキュメントディレクトリに年/月/日.png のように保存
ken1flanken1flan

CI

flutterはバージョンが頻繁に上がるし、自分も大きな変更をしたときに何処かが壊れることに気づけると助かるので、やっぱりCIは入れておきたい。

いくつかサービスがありそうだけど、flutter公式が採用しているCirrus CIがとりあえず安心そう。

  • flutter向けドキュメント https://cirrus-ci.org/examples/#flutter
    • 設定例でコンテナのバージョンがlatestになってるけど…アップデートが頻繁で面倒なので、ベストプラクティスかも。
    • 困ったらバージョン指定して、直したら戻す運用でよさそう。
    • 例の通りに記述してもダメだった…。

静的解析

初心者の自分がイマイチな書き方で放置しないようにするのにとても有用だと思っています。

https://github.com/ken1flan/f_diary/pull/1

ken1flanken1flan

アプリケーションのベースになる画面を作成

  • 将来的に日記エントリの一覧になる予定
  • タイトルとアクションボタンがついてる。

タイトルの変更

アイコンの変更

https://api.flutter.dev/flutter/material/Icons-class.html

カウントアップ削除とステートレス化

ボタンを押すとカウントアップするようになってますが、当然不要なので取り払います。
それによって、状態が不要になるのでステートレスウィジェットに変更します。

https://github.com/ken1flan/f_diary/pull/3
https://github.com/ken1flan/f_diary/pull/4

ken1flanken1flan

sqfliteを使おうとしたら、なぜかデータベースのオープンもできなかった…。
疲れ切ったので、別のを探していました。

ObjectBox

ObjectBoxはモバイルデバイスやIoT機器のためのNoSQLデータベース。
ObjectBox Syncでサーバとの同期もあるようなので、案外よさそうな雰囲気。Firebaseの対抗馬?
Dart/Flutter以外にもJava、Kotolin、Swift用のライブラリもある模様。
会社化されているっぽい。

https://objectbox.io/
https://pub.dev/packages/objectbox

ken1flanken1flan

ああ…うまくできてなかった、sqfliteのほうもWidgetsFlutterBinding.ensureInitialized();を入れたらうまくいった…。
main() はflutterの実行開始位置ではなくて、dartのものなんだというのが唐突にわかった。そりゃ動かないよなぁ…。

sqfliteに戻ろうかなぁ…悩む…。

ken1flanken1flan

sqfliteで実装中…。

しかし、hivedbがラクすぎて、そっちに逃げたくなる…。
Navigatorで戻るやったら、リスト更新してくれなくて泣いてる><

ken1flanken1flan

…シンプルに作りたいと思っているのに、ついリポジトリパターンをみてしまう。

ken1flanken1flan

仕事で使うならfirebaseが一番よさそうだけど…
いっぱいサンプルあるし、自分が書かなくてもよさそう。
だいたい、自分が作りたいのは小さくて可愛い、サーバの不要な個人的なアプリなので…ちょっとなぁ…。

ken1flanken1flan

インストールできてるかわからんので、サンプルコードがローカルで動くか確認中…。

pubspec.yamlのSDKバージョンを2.12以上を指定しなかったので、最初使えず四苦八苦…。
flutterだとpubspec.yaml準備してくれるから、考えたことなかった…。

environment:
  sdk: '>=2.12.0 <3.0.0'
ken1flanken1flan

インストールできてるっぽい。
ちゃんと保存して読めた…!

自分のテストコードにバグがあるはず!!

ken1flanken1flan

お… getApplicationDocumentsDirectory が失敗してる。
どっかでみたな…。

~/src/f_diary[Add_objectbox(;´▽`A]
22:45 $ flutter test test/widgets/my_home_page_test.dart
00:02 +0 -1: (setUpAll) [E]
  MissingPluginException(No implementation found for method getApplicationDocumentsDirectory on channel plugins.flutter.io/path_provider)
  package:flutter/src/services/platform_channel.dart 154:7  MethodChannel._invokeMethod

00:02 +0 -1: Some tests failed.
~/src/f_diary[Add_objectbox(;´▽`A]
ken1flanken1flan

ファイル書き込みのテストの中に書いてあった、MethodChannelが気になった。
FlutterはUIのフレームワークなので、UIでないプラットフォームの機能を使おうとすると、プラットフォーム側に実装が必要になる。その実装とのやり取りで使うもの…のよう。

ファイル書き込みはモロにそれにあたってるからなのね。

https://qiita.com/kurun_pan/items/db6c8fa94bbfb5c0c8d7

ken1flanken1flan

あー…どうしてもうまくいかない…。
単体でテストを動かすと通るのに、まとめて動かすと、なぜか変なレコードがうまれちゃっててダメ…。

このブランチ、破棄して、また作り直そう…。

https://github.com/ken1flan/f_diary/pull/10

ken1flanken1flan

hivedbに戻ろうとしたらisarがリリースされてる!
こっちにしようかなぁ…。

テストが書けるか調べよう。

ken1flanken1flan

作り始めた。
https://github.com/ken1flan/f_diary/pull/15

モデルの定義のあとにgeneratorを動かしたが、エラーで動かず…。(サンプルにimportがないので想定してたけど…。)

$ flutter pub run build_runner build
[INFO] Generating build script...
[INFO] Generating build script completed, took 594ms
  # 省略
line 1, column 1 of package:f_diary/models/article.dart: Could not resolve annotation for `class Article`.
  ╷
1 │ @Collection()
  │ ^^^^^^^^^^^^^
  ╵
[INFO] Running build completed, took 4.0s

[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 33ms

[SEVERE] Failed after 4.0s
pub finished with exit code 1

サンプルを見て解決。
hivedbと同じ。

lib/models/article
import 'package:isar/isar.dart';

part 'article.g.dart';

()
class Article {
  ()
  int? id;

  late String title;
  late String body;
  late DateTime createdAt;
  late DateTime updatedAt;
}
    
ken1flanken1flan

…マイナーバージョンアップされてる!
何が変わったのかな…?

ken1flanken1flan

isar_connectに統合されましたisar

isar_connectがなくなってる…。バージョンアップでいきなり変わる、そういう時期のライブラリだよね。

機能強化

  • 古い生成ファイルのチェックを追加
  • 分離全体で変更されたスキーマのチェックを追加
  • 追加したIsar.openSync()

同期的にオープンするの、入ってくれた!

  • 追加col.importJsonRawSync()、、、、col.importJsonSync()_ query.exportJsonRawSync()_query.exportJsonSync()
  • クエリのパフォーマンスが向上
  • ffiメモリの処理の改善
  • その他のテスト
ken1flanken1flan

isarをアップグレードしなければ…。

$ flutter pub outdated
Showing outdated packages.
[*] indicates versions that are not the latest available.

Package Name       Current  Upgradable  Resolvable  Latest

direct dependencies:
isar               *2.0.0   *2.0.0      *2.0.0      2.1.0
isar_flutter_libs  *2.0.0   *2.0.0      *2.0.0      2.1.0

dev_dependencies:
isar_generator     *2.0.0   *2.0.0      *2.0.0      2.1.0

transitive dependencies:
path               *1.8.0   *1.8.0      *1.8.0      1.8.1

transitive dev_dependencies:
js                 *0.6.3   *0.6.3      *0.6.3      0.6.4
source_span        *1.8.1   *1.8.1      *1.8.1      1.8.2
test_api           *0.4.3   *0.4.3      *0.4.3      0.4.9
You are already using the newest resolvable versions listed in the 'Resolvable' column.
Newer versions, listed in 'Latest', may not be mutually compatible.

アップグレードしました。
https://github.com/ken1flan/f_diary/pull/16

ken1flanken1flan

次はホーム画面にとりあえず保存したレコードを表示できるようにしよう。
あと、これはテストも書く。

ken1flanken1flan

テストでisarのライブラリが読み込めない…。
どうやってやるんだ…?

  :
Invalid argument(s): Failed to load dynamic library 'libisar.dylib': dlopen(libisar.dylib, 1): image not found
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following IsarError object was thrown running a test:
  IsarError: Could not initialize IsarCore library. If you create a Flutter app, make sure to add
isar_flutter_libs to your dependencies.
  :
ken1flanken1flan

↑のsetup_tests.shを動かすと、 .dart_tool 以下に各OS用のライブラリが置かれた。
Failed to load dynamic library 'libisar.dylib': dlopen(libisar.dylib, 1)というエラーだったので、プロジェクトのルートにlibisar.dylibという名前でコピーして、テストを実行。

ライブラリは読んでるようだけども…。

$ flutter test test/widgets/my_home_page_test.dart
00:02 +0: エディットボタンを押したときに記事ページが表示されること                                                                     00:03 +0: エディットボタンを押したときに記事ページが表示されること                                                                     00:03 +0: エディットボタンを押したときに記事ページが表示されること
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following ArgumentError was thrown running a test:
Invalid argument(s): Failed to lookup symbol 'isar_get_instance': dlsym(0x7fc5e5522ea0,
isar_get_instance): symbol not found
(省略)
ken1flanken1flan

ふと思い立って、androidエミュレータ上でテストを実行してみた。
同じエラーになったので、テスト環境のセットアップのどこかが悪そう…。

ken1flanken1flan

このエラー、ライブラリ内にisar_get_instanceという名前の関数がみつからねーぞっていう意味だと思うんだが…

The following ArgumentError was thrown running a test:
Invalid argument(s): Failed to lookup symbol 'isar_get_instance': dlsym(0x7fd7a7507870,
isar_get_instance): symbol not found
ken1flanken1flan

テストの順番を入れ替えても最初に実行されるテストで失敗する…。

ken1flanken1flan

なんか変。
article_page_test と my_home_page_test が同時に動こうとしている雰囲気を感じる。

ken1flanken1flan

成功した…!
えー…勝手に並列実行していたのか…。

ken1flanken1flan

「簡単な日記アプリ」としてあとほしいものは…

  • 日付
  • 一枚画像添付
  • アイコン
  • 起動画面(スプラッシュ)
ken1flanken1flan

ImagePickerで画像を取得し、表示するようにしました。
https://github.com/ken1flan/f_diary/pull/24/commits/aa9f0d4c075b8a962d074358700119a973e434b7

最初、Future<File?> _imageFrom〜() のように画像を返すメソッドにしたら、setState() にasyncな無名関数を書くなと言われ…

void setImageFrom〜() として、インスタンス変数のimageFileにセットするだけの関数にし、これをsetState()でくるんだら…画像が最初表示されず、何かしらほかの操作で更新が入ってから表示されるようになってしまい…

今のようになりました。

むずい…。

ken1flanken1flan

Fileに対してコンバータを書いたのに、機能しない…。

Fileを継承して作ってみるか…。

ken1flanken1flan

ふと思い立って、File??を取り去ったら、うまくいきそう…。
article.g.dartにimageFileが記載された。

ken1flanken1flan

コンバーターにちゃんとFile?を指定しなきゃアカンのでした…。