2019年12月25日水曜日

1人 Flutter Advent Calendar 2019:#25 onActivityResult


Androidでは一般的な問題ではありますが、OSが勝手にActivityをkillすることがあります。
Androidであれば、状態を保存しておき、再構築するための仕組みが用意されていますが、Flutterにおいてはそれがありません。

OSにActivityがkillされた場合にFlutterのinstanceも開放されてしまいます。stateの管理をどうすればよいのか?
これについては、すでにissueが上がっていて
https://github.com/flutter/flutter/issues/6827
ここで、議論はされていますが、3年前から続いています。。。
特にこれといったスタンダートになっている解決策はないというのが現状だと思います。

ただ、基本的にFlutterでアプリケーションを作成して動作させていて、問題になることは、ほぼないと言って良いと思います。アプリが裏にいって、また最初からでもそんなに困るケースはまれですし、そういうもんだという前提で作ればどうにかなるかと思います。

問題になるのはFlutterアプリケーションがほかアプリとアプリケーション間連携を行うような場合などだけかと思います。
カメラで撮った画像を取り込むとか何かのSDKを組み込んで値をonActivityResultで取得するのを前提とした仕組みの場合でしょうか。

すでにissueが上がっていることは示しましたが、解決策がないので自分でなんとかするしかないです。

onActiivtyResultがcallされたタイミングではFlutterのインスタンスがまだないので、値を通知することも出来ません。
Flutterの準備が出来たタイミングでFlutter側から値を取得しに行くほうが良さそうです。
そうすればタイミングを考える必要もほとんどないので。

とりあえずサンプルを作ってみました。いつものmy_appで数字をcount upして、中央のbuttonを押すとFlutterとは関係のないAcitivytが立ち上がります。このActivityで数字を編集して、Buttonを押すともとのFlutterのActivityに値を渡すというものになります。

普通に実行すると

I/flutter android(15154): enter onCreate
I/flutter android(15154): exit onCreate
I/flutter (15154): enter main
Syncing files to device Pixel 4 XL...
I/flutter (15154): before getActivityResult
I/flutter (15154): exit main
I/flutter (15154): after getActivityResult
I/flutter (15154): before openOtherScreen
W/ActivityThread(15154): handleWindowVisibility: no activity for token android.os.BinderProxy@e3ade16
I/flutter android(15154): enter onActivityResult
I/flutter android(15154): exit onActivityResult
I/flutter (15154): after openOtherScreen
I/flutter (15154): before getActivityResult
I/flutter (15154): after getActivityResult

こんな感じです。
openOtherScreenをcallするとActivityが立ち上がるのですが、それの結果が戻ってくるのをawaitで待っています。
で、onActivityResultが終わった後にopenOtherScreenのreturnが来ているのが分かると思います。
FlutterとAndroidのActivityの間でデータのやり取りがスムーズに出来ています。

端末の設定でDon't keep activityを有効にして同じことをしてみると。

I/flutter android(15383): enter onCreate
I/flutter android(15383): exit onCreate
I/flutter (15383): enter main
Syncing files to device Pixel 4 XL...
I/flutter (15383): before getActivityResult
I/flutter (15383): exit main
I/flutter (15383): after getActivityResult
I/flutter (15383): before openOtherScreen
W/ActivityThread(15383): handleWindowVisibility: no activity for token android.os.BinderProxy@189e8ec
W/ActivityThread(15383): handleWindowVisibility: no activity for token android.os.BinderProxy@75ebc8e
I/flutter android(15383): enter onCreate
I/flutter android(15383): exit onCreate
I/flutter android(15383): enter onActivityResult
I/flutter android(15383): exit onActivityResult
I/flutter (15383): enter main
I/flutter (15383): before getActivityResult
I/flutter (15383): exit main
I/flutter (15383): after getActivityResult

openOtherScreenをコールして他のActivityが立ち上がって、裏に回ったFlutterのActivityが一旦destroyされているので、またonCreateから始まっているのが分かると思います。
ここで、「exit onActivityResult」の後に「enter main」が来ていることからもonActivityResultで取得した値を単純にFlutterに受け渡せないのが分かると思います。

なので、Flutterが起動し終わった後にgetActivityResultをFlutterからcallして結果を取得するようにしています。
こうすることで、外部アプリとの連携なのでデータのやり取りが可能かと思います。
まあ、platformのつなぎこみとかでタイミング問題が出る時には、だいたい待ち合わせを頑張るよりかは、値をpullしにいく仕組みにしておいた方が、見通しが良い気がしています

サンプルは以下です。
https://github.com/matsuhiro/on_activity_result_test

2019年12月24日火曜日

1人 Flutter Advent Calendar 2019:#24 無限scroll list + checkbox


FlutterのLIstViewは予めitemの数がわからなくても作成できます。
nullを返すまでは作り続けることができます。

checkboxをつけた時にどういった動作になるのかなどが気になりました。
内部ではWidgetがリサイクルされているので、checkboxのcheckがスクロールアウトしてもう一度表示したときには消えてしまいます。
checkされていたというデータをどこかに保存する必要があります。

例ではsharedPreferenceに入れてみました。
https://github.com/matsuhiro/scrollable_list_radio_test

適当にitemに表示する文字列をユニークキーにしていますが、ちゃんとItem用のデータ構造を作って、unique keyはindexにするとか決めたほうが良いとは思います。

ListViewでもFutureBuilderは、きちんと使えそうなので、DBから表示すべきデータを読み込んでから出すとか、通信の結果として得られたものを表示するとかも問題なく出来そうです。

FutureBuilderを利用しなくても、asyncでデータが得られたあとでsetStateすれば同じことはできますが、その場合にはcheckされていないものがcheckされた状態になる時に、checkboxのところだけ少しちらつく感じがするので、FutureBuilderを利用して全体的に差し替えた方が結果的にはきれいに表示される印象です。

Listに関しては、これが良い記事でした。実際に直面した課題の解決策は読んでいて面白いです。
https://note.com/kitoko552/n/n52cb06e79d49

2019年12月23日月曜日

1人 Flutter Advent Calendar 2019:#23 flare&lottie


Effectを再生するにあたって、AndroidやiOSをやっていた人ならば、Lottieを思い浮かべるのではないでしょうか?Flutterの場合はFlareというのがあります。

https://www.2dimensions.com/
再生用のlibraryによって使い方が異なるので、どっちが良いかと言うのは言えないですが、どちらも良く出来ています。

どっちも、再生用に利用されるファイルはJsonです。
サンプルを記載しておきますが、AndroidのSimulatorではLottieの再生は出来ませんでしたが、実機であれば問題なく再生されました。

Lottieについては、FlutterのPlatformVIewを利用しているようです。
そのため、iOSでは再生できるけどAndroidでは再生できないとかありそうです。
Flareの方は、dartで処理しているようだったのでそのようなことは無さそうです。

とはいえ、Flutterは内部ではSkiaを利用されています
https://skia.org/dev/flutter
skiaには
https://skia.org/user/modules/skottie
といった感じで、Lottie用のI/Fが用意?されているようです。(ただしフルサポートしていいるのはまだAndroidのみ)
そのため、こちらで再生する形のものを作った方が良いかも?

以下にサンプル上げておきます。
https://github.com/matsuhiro/flutter_flare_lotie

2019年12月22日日曜日

1人 Flutter Advent Calendar 2019:#22 animation


Animationをする場合に、途中で止めたくなることあるとありますかね。
ゲームだと当たり前に途中で止めることあるんですが、Flutterが想定しているようなアプリの場合はあまりないかも。

でも気になったので調べました。
stopで簡単に止まりました。再開しようと思ったら、forwardを呼び出せば再開します。
簡単でした。




レイアウトも動的に変わるし、とても簡単です。
コメント入れてありますが、試すときにはsdkのversionを2.2.2以上にしてください。
https://github.com/matsuhiro/animation_test

floatingボタンで開始/一時停止が行えます。

2019年12月21日土曜日

1人 Flutter Advent Calendar 2019:#21 通信状態監視


通信状態を監視して、状態を変えて、ユーザーに操作を促すとか普通にやりたくなると思います。
Snackbarだしたりとかとか。

それもFlutterだと簡単に出来ます。
Flutterのいいところは、誰かが作ったものがたくさんあるところですね。Androidとかも同じなんですが、導入までのハードルが低すぎて引きます。

https://pub.dev/packages/connectivity
というのを使ってみました。

これ使うと通信状態監視できます。
サンプルは以下です。
https://github.com/matsuhiro/flutter_connectivity_test
airplane modeとかにすれば、snackbar出てくると思います。

ちょっと工夫したところとしては、Snackbarを表示するところでしょうか?
setStateでUIが変わるんですが、そこでSnackbarを表示しようとするとエラーになるため、Delay 0秒で、UIの構築の次の処理に回しています。

2019年12月20日金曜日

1人 Flutter Advent Calendar 2019:#20 delayedで逐次処理


DartはSingle threadで操作しているので、どこかで重たい処理が走ったらCPUを専有してUIの更新が止まります。

ネットワーク通信とかでも止まりそうに思いますが、Socketからデータを読み出すまでは、待ち状態になってCPUが動いていないのでUIがブロックはされないようです。

でも、重たい処理をしたいこともあると思います。
Fileのコピーや動画のEncodeなどは、for 文で繰り返し行ったりすると思います。
そんな時には、Event loopに少しづつ処理をしてもらう感じにしたいこともあると思います。
そうすれば
「少しencode」→「UI更新」→「少しencode」→「UI更新」...
のようになって、UIをblockすることもないです。

AndroidだったらrunOnUiThreadとか便利なmethodがありますが、dartのIsolateにはそういった関数は無さそうでした。

似たようなことは、
Future.delayed(Duration(seconds: 0), doSomething);
のようにすれば、実現できそうです。
https://github.com/matsuhiro/flutter_async_test
サンプルです。
いつものやつですが、floatingボタンを押すと延々とcount upしていきます。

サンプルを動かしてもらえればわかりますが、duration = 0でひたすら動いていますが、timerも動いているので、UIがBlockされていません。

これが何をやっているかというとdoSomethingの処理をひらすらMain loopのqueueに入れていますが、TimerもMain loopのqueueに処理を入れているので、どちらも逐次処理されている感じです。

とはいえ、重い処理の場合はcomputeとかを素直に使うのが良いのかなと思います。
現実には、そこまで重い処理はC++で書くServer側に処理を任せるなど、dartにこだわらない選択をすることになるのかなと思ったりします。

2019年12月19日木曜日

1人 Flutter Advent Calendar 2019:#19 DevTool


FlutterはDevToolが充実していています。
AndroidやiOSならMarketでアプリの売上につながるから開発環境を整えるモチベーションもわかりますが、なぜFlutterでこんなに整っているのか。しかも無料。

Performanceを見るツールもありますが
https://flutter.dev/docs/development/tools/devtools/inspector
のにLayoutを調べる機能もあります。
Android Studioを利用しているならば⇧⌘A(Shift + command + a)で、「flutter inspector」で検索すると出てきます。

大体触れば何かが起こるので、だいたい分かると思いますが「Enable Select widget mode」はちょっと何が起こるのかすぐにはわからないと思います。
リンクをたどっていくと動画にたどりつくので見ればすぐに分かりますが、マウスで選んだ場所のWidgetがすぐに分かります。
https://youtu.be/JIcmJNT9DNI?t=361

AndroidでもLayout Inspectorがありますが、同じような機能です。

動画でやっているようなズルズルカーソルを動かすと選択されるWidgetが変わるようなことも実機でもできました。
指を置きっぱなしにして移動すれば実機上でどこに何があるのかわかります。

こんな機能、どこで使うんだろう。自分で作ったやつなら把握しているし必要ないだろうと思うかも知れないですが、人が作ったものをメンテする時には非常に役に立つと思います。
このUIを表示しているコードどこにあるんだろう。見つからない。。。とか、既存のアプリをメンテしている人ならよくあると思います。

Androidの場合、Layout Inspectorからidを見つけて、そのidから検索してとかを僕はしますが、FlutterならClick一つでcodeまでjumpするので、codeにたどり着くのが非常に早いですし、多少Layoutのcodeのネストが深くてもツールで追いかけられるのはすごいです。