2019年12月25日水曜日

Flutter advent calendar 2019 まとめ

やり始めたから最後までやったけど、25個はネタが無かったです。
むりやり作った感のあるものも何個かあったなと。。。
とはいえ、調べたことの供養の意味もあったのでまあ良かったかなと。
https://adventar.org/calendars/4869

他には多言語対応や画面回転時の挙動とか調べたいなと思っていたりはします。
Nativeアプリをつくるなら、BLEやNFCをどうするのかとかも思いますが、そこまでやるならNativeのActivityなりを別途起動して対応したほうが現実的な気もしています。

まとめはFlutterについてちょっと感じたことをメモ程度にまとめようかと思います。

Flutterの良いところは、いろんなところで記述があるので、あえて良くないところを上げてみようかと。技術的な良くないところというよりは、将来的な運用も考えたときの不安ですね。

Dartが人気ないなーと感じてます。
Dartの言語としての人気は、お世辞にもあるとは言えないと思います。言語自体が良いかどうかは僕にはわかりませんが、人気がないということは、使っている人がいないということです。
https://redmonk.com/sogrady/2019/07/18/language-rankings-6-19/
ランキング的には圏外のようですね。。。Advent calendarも過疎ってましたし。
Flutterで利用したアプリが売上がたって、人を増やそうとした時に、人員の増加は他の言語などよりも厳しいのではないかと思います。人が少ないですから。
Dart自体は、独自の文法ということは無いです。個人的にはObjective-Cの方が難しい。。。実際に触ってみれば特に困る人は少ないと思いますが、過疎っているものに自分の時間を使うというのは、なかなか勇気がいりますしね。

Flutter自体の普及に関してもまだまだな感じはします。
Androidが出始めのころも、いまのFlutterとちょっと似ていて、Productionでの開発経験がある人は殆どいないけど、趣味で触ってみている人たちがたくさんいる感じでした。
実際に業務で、いろんな端末でいろんなバグを踏みながら開発をしていた人というのは、運良くAndroidの開発の案件が社内であって、運良く担当していたといった具合だったと思います。そのため、Androidの開発経験がある人を中途採用しようとしても、趣味でやってい人は見つかっても、業務経験がある人は殆ど見つからなかったと記憶しています。
Flutterの現状も、ちょっと似ているかなと感じるところがあります。
10年前くらいもBlog書いたり勉強会で発表したりとかのエンジニア文化はありましたが、今はもっとそれが活発なので、すごい流行っているように見えても、実はProduction環境ではそんなに使われていない。というのが現状かなと、特に定量的根拠はありませんが感じるところです。

開発元のGoogleがFlutterで儲ける気がなさそうというのもちょっと気になるところです。
Flutterの開発元はGoogleですが、GoogleのプロダクトでFlutterを利用しているものがあるかというと、パッとは思いつきません。
https://flutter.dev/showcase
ここにあるように、すでに様々なもので利用されてはいますが、GoogleがFlutterで儲けてはいないと思います。今後も儲ける気があるのかよくわかりません。
現状のツールやドキュメントやビデオなど、お金とってもいいレベルで揃えているのに無料です。ある日突然、Googleがもう辞める。って言い出さないか不安になります。
Open sourceだからみんなでメンテすれば大丈夫。というのは少々ナイーブで、現実には、お金を儲けているプロダクトの基盤になっている技術だから、企業がお金を出して雇っているエンジニアを使ってメンテするわけなので、Googleにはなにか儲ける方法も考えていただきたいところです。もしくは、儲かっているプロダクトで使ってほしいです。
Open sourceも当たり前ですが、万能じゃないです。様々なゲームアプリ(たぶんモンストも)で利用されているCocos2dxもhttps://takachan.hatenablog.com/entry/2019/11/30/234321
こんな状況のようです。
数年前に僕もCocos2dxを使ってゲームアプリをリリースしましたが、当時はCocos2dxも最新のLibraryを導入したり活発に開発されていたので、隔世の感があります。
Flutterはこうならないことを祈ります。


とはいえ、実際に触ってみた感触としては、Productionで利用できると思いましたし、実際に海外(主に中国?)では、使われているようです。
今、新しいNativeアプリ作るなら選択するかなと思います。
まあ、完璧なframeworkやlibraryなど無いので、何を使うかは、その時々のチームの人員などによって妥協しつつ決めていくと思うので、現実には常にFlutterがオススメというわけではなく、すべてのモバイル環境でカメラや指紋認証も使えるようになりつつあるWebでのアプリ作成も選択肢に入れていくとは思いますが。

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のネストが深くてもツールで追いかけられるのはすごいです。

2019年12月18日水曜日

1人 Flutter Advent Calendar 2019:#18 fps check


Flutterでは60fpsを目指しているとありますが、実際に60fpsがkeepされているかどうやったら分かるでしょうか?

Android StudioのでFlutter Performanceのwindowを開くのが最も良さそうです。
https://flutter.dev/docs/development/tools/android-studio#show-performance-data
ただ、release buildの時には当然使えません。

そんな時に大体のfpsを取ろうと思うとendOfFrameというのが使えると思います。
runAppの内部で行っていることをmainの中に書いて、WidgetsBindingにあるendOfFrameがfutureになっているので、thenで呼び出される関数を設定します。
これは、現在のframeの最後に呼び出されるだけで、毎frameごとに何回も呼び出されるものではないです。そのため、常に監視をしたい場合は、callbackの最後にもう一度設定をする必要があります。

https://github.com/matsuhiro/fps_check_test/blob/master/lib/main.dart#L3-L21

こんな感じ。

ただ、Flutterは、描画の必要がなければIdle状態になります。
ゲームのように常にEvent Loopが60pfsで動き続けているわけではないです。

実際に、Android studioで、Flutter Performanceのwindowを開くと描画がされていないときには、処理を行われていないことがはっきり分かると思います。

ここで示した例では、endOfFrameにcallbackを設定しています。そうするとidleになっていたものが起動されます。
そのため、Idleにならずに処理が続くことになります。
当然ですが、普通の状況では行うべきではないです。
release buildでfpsが知りたいときには使えるかもしれない程度で、実際にはこんな事しないでdebug buildでしっかりとperformanceのチューニングを行うのが良いでしょう。

サンプルです。

https://github.com/matsuhiro/fps_check_test

Flutter PerformanceはAndroid Studioの右下にあるのでclickすると見れます。
もしくは、⇧⌘A(Shift + command + a)で、「Flutter Performance」で検索してください。

2019年12月17日火曜日

1人 Flutter Advent Calendar 2019:#17 log


Flutterで適当にlogを出そうと思ったらとても楽で、printを使えばよいだけです。
それだけで、AndroidでもiOSでもログを見ることが出来ます。

ただ、これ、製品を作るときには困ります。
製品版では出したくないですよね。

Androidではproguradの設定で消したりtimber使ったりすると思います。
同じようにFlutterでも最初からLogについてはきちんと考えておいたほうが良いです。

print自体の制御をしたいと思っても簡単にprintの中身についてはわかりませんでした。
https://github.com/flutter/engine/blob/master/lib/ui/dart_runtime_hooks.cc#L200
このあたりで出力しているようなのですが、外部からの入力値によって出す出さないを切り替えるみたいな仕組みが無いような。。。うーん。

Flutterで利用するlogのツールとしては
https://pub.dev/packages/logger
が良さそうです。

特別なことはしておらずv,d,i,w,eと別れていて、使いやすいです。完全にログを出したくないと思えばLogFilterなどを作成して設定すれば良いです。
https://github.com/matsuhiro/font_log_test/blob/master/lib/main.dart#L11-L15
こんな感じで使えます。

といいえ、適当にprintだけ書いてそのままcommitしてしまうような事故もあるとは思うので、print自体をいじりたいもんです。


2019年12月16日月曜日

1人 Flutter Advent Calendar 2019:#16 cookbook


Flutterの記事はたくさんあるし、動画もあるしで、Flutterはドキュメントには事かきません。
日本語のドキュメントもあるし、日本語の本も何冊かあります。

なので、別に英語のドキュメントを頑張って読まなくても良いのかなとか思ったりもします。
ただでさえFlutterって公式のドキュメントが充実しているので、一つ一つ読んでいるとキリがないというのもありますが。

でも、Cookbookは一通り読んでおいたほうが良いのかなーとか思ったりもします。
こういう事言うと、「いや、これも。いや、あれも。」みたいな意見も出るでしょうけど、CookBookがいいなと思ったのは、テーマが具体的でこういうのってプロダクトで必要だよね。といったものがまとめられているという印象だからです。だからCookbookなのでしょうけど。

例えば
https://flutter.dev/docs/cookbook/images/cached-images
これとか、プロダクトで画像データのcacheとか絶対やると思いますし。

データの永続化だったり動画の再生だったりとかとか。いくつかの必要そうなテーマで短めに書かれているので読みやすいです。

FlutterのWidgetを一つ一つ見ていくのも面白いですが、実践的なものがよくまとまっているのはこれかなと。

githubにcodeもあればいいんですがね。自分で作れという話ではありますが、現在はないみたいです。

2019年12月15日日曜日

1人 Flutter Advent Calendar 2019:#15 click event


特定のWidgetをClickしたら処理をするのは当たり前ですが、その周りの余白だったりをclickした時になにか処理をしたい時にはどうすればよいか?

大抵は、Cancelのような挙動を設定したくなると思います。
Containerなどにもtapのイベントを設定することは可能です。GestureDetectorを利用して実装してみます。


https://github.com/matsuhiro/click_event_test/commit/39081b5a452cf23dd76f277c5b6919c1c4f8ca87#diff-fe53fad46868a294b309fc85ed138997R94-R132

これを適当なところにいれて表示させます。

Enable Debug paintをすると上のようになります。

Button以外の部分をtapすると「container tapped」と表示されるのが分かると思います。
Paddingの上をTapしても「container tapped」が表示されるので、だいたい想像通りに動作するようです。

ただ、Containerが何も描画していないとonTapが反応しません。
Event handingをしているのが、描画のObjectであるのが原因ぽかったです。
そのため、以下のようなコードだと
https://github.com/matsuhiro/click_event_test/blob/53596dfd7a6fd8e625ed8c27616be672f0a0b052/lib/main.dart#L94-L141
「inner container tapped」は出力されません。

「inner container tapped」のコードが記述されているのは、GestureDetectorとContainerを2重に入れ子にしたものの内側のGestureDetectorのonTapで表示させようとしています。
内側のContainerは何も描画していないので、onTapが反応しません。

そこで
https://github.com/matsuhiro/click_event_test/commit/8d44ed0b81a21374e5718249a3502ff4ccc0a3ac
このように透明な色を描画すれば「inner container tapped」が出力されて、ただの入れ物のContainerでもonTapを処理できるようになります。

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

2019年12月14日土曜日

1人 Flutter Advent Calendar 2019:#14 qrcode


QR codeを読んだり表示したりってことがあると思います。

QRだけで検索するとreaderとgeneratorが両方引っかかります。
QR code readerもViewで大きさを変えられるものから、全画面のカメラになるものまでありました。

ただ、全体的に言えることは、ただただ簡単。ということです。
QR code readerって作るのこんなに簡単だったのかよ。。。
って思うくらい簡単です。

QR code 生成:https://pub.dev/packages/qr_flutter
QR code 読取:https://pub.dev/packages/flutter_qr_reader

が良いかなとか思いました。flutter_qr_readerの方は、サンプルアプリとか中国語でちょっと構えてしまいますが、別にコード読めばすぐ分かる感じでした。

当たり前ですが、Transformとかもきちんと動きますし、ほんとに簡単でした。


https://github.com/matsuhiro/qr_code_test
サンプルです。
カメラにアクセスするためのpermissionを取得する部分はQRコードとは関係ないけど必要なので入れました。

2019年12月13日金曜日

1人 Flutter Advent Calendar 2019:#13 storyboard

UI作っているとUIの部品を実機で見せたくもなります。
デザイナーなどに共有するために、いちいち専用アプリを作るのもだるいですし、実機で見せてコミュニケーション取りたいケースはたくさんあると思います。

そんな時にStoryboadというのが便利そうです。
https://proandroiddev.com/storyboarding-widgets-in-flutter-96d79d9a72f0

ただ部品を並べるための枠が用意されるだけなのですが便利です。

デザイナーなどとコミュニケーションするにも良いですし、エンジニアにとっても良いshowcaseになるります。


class TextStory extends Story {
  @override
  List get storyContent {
    return [
      Text('this is story sample'),
    ];
  }
}

のようにStoryを拡張したclassを作成して


class StoryBoardApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Storyboard([
      TextStory(),
    ]);
  }
}

といあった感じでWidget作れば良いので、組み込みも楽ちんです。

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

2019年12月12日木曜日

1人 Flutter Advent Calendar 2019:#12 font

Flutterでfontをカスタマイズするのはほんとに簡単です。
ちょっとpubspec.yamlに


fonts:
        - family: NotoSerifJP-Regular
          fonts:
            - asset: assets/fonts/NotoSerifJP-Regular.otf
        - family: KouzanSoushoOTF
          fonts:
            - asset: assets/fonts/KouzanSoushoOTF.otf


とかいて

           Text(
              msg,
              style: TextStyle(
                fontFamily: 'NotoSerifJP-Regular',
              ),
            ),
            Text(
              msg,
              style: TextStyle(
                fontFamily: 'KouzanSoushoOTF',
              ),
            ),


こんな感じで指定するだけです。

すごい。
文字列の描画自体はどうしているのかと気になりましたが、WebのコンテンツなどのようにPlatformViewで表示するのも良いのでは?とか思ったりもしますが、独自に
https://github.com/flutter/engine/tree/master/third_party/txt
このあたりのライブラリを利用して独自に描画しているようです。

調べといてなんですが、本やニュースなど文字そのものが商品と直結しているようなものでない限りは、フォントに関してカスタマイズするということはそんなにないかなーとは思います。

サンプルは以下においてます
https://github.com/matsuhiro/font_log_test

2019年12月11日水曜日

1人 Flutter Advent Calendar 2019:#11 webview


WebViewにもいくつかpackageがあります。
Flutterを利用しているとWebViewを利用したくなるケースというのはお知らせなどでしょうか。

アプリ開発の経験があれば、だれしもWebViewにいろいろ表示したいという要望が上がって、WebViewとChromeやSafariとの挙動の違いに苦しんだことがあると思います。

WebView自体はChromeとは違うアプリであるというのが理由ですが、WebのFrontendのエンジニアや非エンジニアからは、理解されずにバグ扱いされることもあったりすると思います。
https://flutter.ioをユーザーに提示するにあたって3パターンあると思いますが、
  1. Chromeを開く
  2. Chrome custom tabで開く
  3. WebViewで開く
ユーザーの体験で問題ないならば1がおすすめです。
とはいえ、アプリ遷移させたたくないという要望もあると思うのですが、そのときには2かなと言う感じで、どうしてもヘッダーなどをカスタマイズしたいとかがあれば3を選択するのが良いと思います。

ただ、これはエンジニア的な視点で、大体の場合は3を作ることになるのかなーとかいうのも思う所ではあります。(ログを取りたいとかJSのI/Fがほしいとか。。)
それぞれ、ライブラリがあって、
  1. https://pub.dev/packages/url_launcher
  2. https://pub.dev/packages/flutter_custom_tabs or https://pub.dev/packages/flutter_web_browser
  3. https://pub.dev/packages/webview_flutter
かなと思います。
2は、flutter_custom_tabsの方がoption設定が柔軟ですが、そんなにいじらないと思うので、flutter_web_browserで良いんじゃないかと思ったりします。


とりあえず全部使ったサンプルです

https://github.com/matsuhiro/flutter_webview_test


2019年12月10日火曜日

1人 Flutter Advent Calendar 2019:#10 flexのbaseline


FlexBoxっぽいFlutterですが、Flexのレイアウトなどの実装は何が使われているのでしょうか?
Chromeはblinkで、React nativeはYogaです。

https://flutter.dev/docs/resources/rendering
ここにあるようにFlutterでは、RenderObjectがレイアウトと描画の基本クラスのようです。
FlutterのFlexの内部をみるとcreateRenderObjectでreturnしているのはRenderFlexです。
RenderFlexにperformLayoutがあって、ここでレイアウトを決めています。
他の種類のRenderXXXでも、performLayout内部でレイアウトが決められています。
ここからもわかるようにFlutterではレイアウトの仕組みも1からDartで実装しているようです。
そうしないとWebで実行とか出来ないわけですから、当たり前といえば当たり前かも知れません。

正直、これを読んだところで何が分かるというわけでも無いかなと思います。
全体的に眺めていて、目に止まったのはbaselineの計算部分でした。

例えば、RenderFlexの中にgetDistanceToBaselineという関数呼び出しがあります。alignmentをbaselineに設定した時に位置の計算に利用されます。
Reactだと以下のように出来ます。
https://snack.expo.io/BJfcbjDsH
gの下の部分が画像やViewよりも下に来ているのが分かると思います。

このように文字と他の要素をbaselineで揃えたいという要望は普通にあると思います。

これ、Flutterでどうやるのか?

rowに素直に並べてalignにbaselineを設定すると以下のようになります。
https://github.com/matsuhiro/flex_baseline_test/commit/043dfd7f9cd73cd0e8068fddf45923b3cd94629a

全然違いますね。

Flutter自体にはBaselineクラスというのがあるので、すべての要素を囲ってbaselineを0にすればそれっぽくはなります。
https://github.com/matsuhiro/flex_baseline_test/commit/804261a3453c7a8c2c2153c0535f214c2caee9ab

baselineの値を0ってなんでしょうね。baseline関連はdocumentもすごい充実しているわけではなかったです。

baseline関連はまだ発展途上なのかもななどとも感じます。

2019年12月9日月曜日

1人 Flutter Advent Calendar 2019:#9 flexで画像をcenterに


アプリを作成しているときには、大抵の場合は、画像自体も表示してほしいアスペクト比になっているたりすると思います。
とはいえ、画像を加工したりは普通にしたくなるとは思います。
中心に合わせて拡大したり、画像全体が表示されるようにしたりなど。

その場合にFlutterでどうするのかというとImage.assetのfitにcoverを指定したりすれば簡単に出来ます。
画像の表示をするだけならこれだけで十分ですし、SVGを表示したいと思った場合も、flutter_svgを利用すれば、変数にfitがあるので、それを指定すれば十分です。

その他にやりたくなりそうなのは、Containerの背景に画像を指定しようとした場合とかでしょうか。
その場合は、BoxDecorationとDecorationImageを利用すれば可能です。

この例ではjpegを背景としてchildにsvgを表示させています。
https://github.com/matsuhiro/flutter_image_test/blob/master/lib/main.dart#L121-L136

BoxDecorationはshapeで円に切り抜きなども出来るので、やってみます。
そうするとsvgの画像がはみ出しています。

こういった時にはClipOvalなどのClipXXXを使うのが良いかと思います。
https://github.com/matsuhiro/flutter_image_test/blob/master/lib/main.dart#L154-L170
SVGも含めて全体をclipする感じです。

コードの全体はここにあります。

https://github.com/matsuhiro/flutter_image_test

2019年12月8日日曜日

1人 Flutter Advent Calendar 2019:#8 flexboxで縦横くみあわせたり改行とか

FlexBoxを利用した場合の良さを実感できるのは、値としてflexやwrapを指定した場合かなと個人的には思っていたりします。


FlutterにもFlexがあるので、利用してみたいと思います。Flexの配下にFlexibleを指定しているのはパラメーターとしてflexを指定することで、要素の比率を決めるためです。
比率だけが固定されて幅が変われば全体的に変化します。

縦と横の指定を組み合わせれば、このようなことも可能です。

このレイアウト自体は、FlexとFlexibleとAspectで表現しています。

更にwrapについても見てみます。
FlexBoxだとwrapを指定することで、配下に存在する要素が折り返したりするのですが、Flex自体にはwrapに関連するパラメーターがありません。
かわりにWrapというWidgetが存在します。

Flexの配下に要素を並べたようにWrapの配下に要素を並べれば折返しが実現できます。
今回は、heightを固定して、widthをバラバラにした要素を横に並べてみました。




各要素のwidhtは同じなのですが、それが入る全体の幅が変わることで、UIの表示が変わっています。

これだけ見てもメリットが特に無いに感じるとは思うのですが、縦横比がバラバラの画像の高さだけを合わせて、全て並べるような場合がよく例に出されます。

上記2つを一つにまとめてみると以下のようになります。



幅の違うものを縦に並べていますが、レイアウトが崩れずに表示されているのがわかると思います。

Zeplinで表現されたものをFlutter上で表現することができることは以前に示しましたし、同じようなレイアウトをFlexでも実現可能なことは以前示しました。

どっちも良し悪しあるとは思いますが、個人的にはFlexibleやWrapなどを利用して表現するほうが、UI的な完成度は高くなるのではないかと感じます。
横幅に制限されないかつ、ただの縮小でなくレイアウトされるのは視認性の面でもメリットが高いと感じます。

ただ、FlexBoxなどに多少なりとも触れたことがあれば、FlexBoxのあれはどこにあるのだろうか?という発想でいろいろ探せますし、探してみるとFlutterにすでに欲しい機能の実装が存在することが多いと思います。
しかし、FlexBoxのことを何も知らないで、いきなりFlutterのflexなどを利用するのは少々厳しいのではないかとも感じます。

サンプルは
https://github.com/matsuhiro/flex_box_wrap
においてあります。

2019年12月7日土曜日

1人 Flutter Advent Calendar 2019:#7 layout by flex


ZeplinからUIを取り込むというのをやりましたが、Zeplinなどから取り込むのとは違う方法でやってみたいと思います。

基本的にはwidthなどに直値を指定せずに横幅が変わったらいい感じにレイアウトも変化するようにしたいです。

これを実現するにはaspect比を指定したり、親の幅のpercent指定でwidthを決めたりします。画像の重ね合わせはStackを利用して配置します。
https://github.com/matsuhiro/flex_box_percent

コードを見てもらえばわかるところではありますが、親の幅に合わせた変化のところで、FractionalSizedBoxを利用します。
それだけだと画像が縦長になってしまうので、AspectRatioで縦横比が常に一定になるようにしています。

このようなレイアウトを工夫した場合の一つのメリットはテキストサイズかなと思います。
基本的には横幅に合わせてレイアウトが変化するようにしているので、画面幅が小さくなったら、アスペクトを固定している画像などは小さくなります。
ただ、それと同じようにフォントまで小さくなってしまうと文字が読めないくらい小さくなることが懸念されます。
FittedBoxを利用して、Zeplinなどで指定されたデザインが歪まないように画面幅に合わせて大きさを変えた場合にこのようになります。フォントサイズは小さくなるので、視認性が悪くなる可能性があります。

しかし、フォントサイズを変えないようにすれば、以下のように文字の視認性が損なわれることはないです。(この例のデザインが良いかは別にして。。。)

サンプルのアプリだとGroupFlexというWidgetを作って、引数でwidhtを変えられるようにして、わかりやすくなるようにしています。

2019年12月6日金曜日

1人 Flutter Advent Calendar 2019:#6 list

Dartのtourのlistのページをみると
https://dart.dev/guides/language/language-tour#lists
listの中にifやforをまぜたり、...でlistを連結させられたりとか書いてあります。

面白そうなので、試してみるました。
      Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (_counter % 2 == 0)
              Text(
                'Count is even!',
              ),
            if (_counter % 2 != 0)
              for (var i = 0; i < _counter; i++) Text('This is $i'),
            ..._widgets(),
          ],
        ),
      ),

適当にこんな感じで入れるとエラーが出ます。2.2.2までサポートしていないよ。と。

Flutterの開発環境にはstable,master,dev.betaとあって、簡単に切り替えられるわけですが、stableで

$ flutter create [application name]

を実行するとpubspec.yamlの中のsdkversionが

sdk: ">=2.1.0 <3.0.0"

といった感じになります。
なので、2.2.2にすれば上記のコードも動作します。

サンプルはこんな感じです。
https://github.com/matsuhiro/flutter_dart_list_test
いつものfloating buttonをクリックするとcount upするやつです。

counterが偶数のときだけ「'Count is even!'」が表示されます。
奇数のときにはforで繰り返された文だけTextが表示されます。
最後に、適当にTextを関数からの戻り値で追加しています。

軽く使うなら便利だなと思いますが、たくさんWidgetを配置するならStatelessWidgetを作ったほうが良いのだろうとは思います。

2019年12月5日木曜日

1人 Flutter Advent Calendar 2019:#5 Flexbox


CSSにはFlexible boxというのがありますが、FlutterColumnとかもこれがベースになっています。
ColumnのParentを見るとFlexだったりすのでわかりやすいです。
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/basic.dart#L4293

なので、FlexBoxの仕様に従ってデザインを実現して行けば画面サイズがかなり変わったとしてもうまいこと表示できたりもします。

たとえば http://tinyurl.com/yy55rm3m こんなレイアウトを作成しようとしたらどうなるでしょうか? 
アイテムごとにalignを変えています。ここで使っているのは、YogaというライブラリのPlaygroundです。YogaはReactNativeとかでも使われているライブラリです。
ここで、FlexBoxのレイアウトを試したりもできます。

Flutterには、FlexというWidgetがあるので、これを利用してみます。
directionを設定して、内部にFlexを入れ子にしてAlignmentを指定することで、上に上げたようなレイアウトが実現できます。
https://github.com/matsuhiro/flex_box_test/blob/master/lib/main.dart#L93-L146

YogaのPlaygroundで表現するとしたらhttp://tinyurl.com/y46o8ksr こんな感じの組み合わせで表現しています。

いまはFlexで実現してみましたが、ColumnはFlexでdirectionを指定した実装になっているので、ColumnとRowの組み合わせでも実現できます。
https://github.com/matsuhiro/flex_box_test/blob/master/lib/main.dart#L148-L198

flexを設定すると以下のように1:2:3のような感じにも出来ます。
https://github.com/matsuhiro/flex_box_test/blob/master/lib/main.dart#L200-L247

flexはFlexクラスには存在しないですがExpandedクラスなどには存在しています。個人的にはFlexクラスの中ですべてを実現できているとわかりやすいのですが、そうするとFlexクラスが巨大になるでしょうし、しかたが無いのかも知れません。

この辺のFlexBoxのレイアウトは、AndroidやiOSのエンジニアよりもWebのfrontendのエンジニアやReactNativeなどを利用していたエンジニアにとっての方が馴染みやすいかもなと感じます。

2019年12月4日水曜日

DartでThrowできるもの

DartのAdvent Calendarがあまりにも過疎っていたので、何でもいいから書こうかという気持ちになりました。
そこでLanguage tourを見直しました。

Exceptionのところをみると
NullでないObjectは何でも投げられる!
ということで、いろいろThrowしてみました。

まずはint
  try {
    throw 1;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
What is thrown: 1
投げられました!

いくつか続けて試します。boolとsymbolsとclassです。
  printDivider();
  try {
    throw false;
  } catch (e) {
    print('What is thrown: $e');
  }

  printDivider();
  try {
    int i = 0;
    throw #i;
  } catch (e) {
    print('What is thrown: $e');
  }

  printDivider();
  try {
    var h = Hoge(0);
    throw h;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
-----------------
What is thrown: false
-----------------
What is thrown: Symbol("i")
-----------------
What is thrown: Instance of 'Hoge'
投げてもいいんですね。 

ふとclassにinstanceを渡さなかったらどうなるのかなと思い試してみました。
 try {
    Hoge h;
    throw h;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
What is thrown: Throw of null.
行けましたね。コンパイルエラーとかにならないですね。
non-nullオブジェクトって言ってた気がしますがいけました。

じゃあ、null自体は?
  try {
    throw null;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
What is thrown: Throw of null.
これも、いけます。

ほんとになんでも投げられますね。
関数は。。。
試してみました。
 try {
    var f = (int i) {
      return i + 1;
    };
    print(f(1));
    throw f;
  } catch (e) {
    print('What is thrown: $e');
    print(e(2));
  }

結果は
2
What is thrown: Closure 'main_closure'
3

catchした後にも関数として利用できるのは面白いです。何に使うんだって話ですが。

ちなみに、
 try {
    var f = (int i) {
      return i + 1;
    };
    throw f;
  } catch (e) {
    print('What is thrown: $e');
    print(e(2));
  }

このように、tryのなかのprintを消してみたら、DartPadでは、結果は
What is thrown: Closure 'main_closure0'
Uncaught TypeError: i.$add is not a functionError: TypeError: i.$add is not a function
のようになってしまいました。

Flutter上で実行したら

I/flutter ( 7570): What is thrown: Closure: (int) => int
I/flutter ( 7570): 3
といった感じで成功したので、Platformによって挙動がちょっと違うみたいです。
詳しい人いたら教えて下さい。

サンプルは下にあります。
https://dartpad.dartlang.org/29bb6a884331c48abb9091a7f3c9941b

Flutter(Android)で実行した場合のログは以下のような感じです。
https://gist.github.com/matsuhiro/8700addb5939dc33be3433d055a96bc3

でも、ここでの内容は、使うことは無いと思います。
ドキュメントにも、こう書かれていますし。

1人 Flutter Advent Calendar 2019:#4 Flutter for Web


現状はまだ正式サポートじゃないです。
ただ、動かすことは可能なので、試してみます。

dev channelに変更してWebアプリを作成します。
作成の仕方はhttps://flutter.dev/docs/get-started/web とか見るのが早いのでやってみてください。コマンド何個か打つだけですし。
簡単に作成出来ます。

https://github.com/matsuhiro/flutter_for_web_test
サンプルおいておきます。

これを実行すると画面幅が十分ならば以下のようになります。

ただ、ブラウザってすごく小さくすることもできます。そうすると

こんなかんじになって、上の段は横幅が小さくなると右端の四角が隠れていると思います。

別にWebに限ったことではないですが、こういうことが起こるのでPositionedDirectionalとかは基本的に使わないほうが良いかなと思います。
Webの場合はユーザーが画面幅を自由に変えられるので、こういった問題が起こりやすいかなと思います。
Flutterのコードで横幅がXXXpx以下だったらこのレイアウト。とか、画面幅ごとにレイアウトの場所の定義テーブルみたいなのを持つのはあまりやりたくは無いですからね。。。

SnackBarもちゃんとブラウザで表示されて、UIに関して言えば、それなりに動くなーという印象です。

しかし、公式にもありますが、
https://flutter.dev/docs/development/platform-integration/web
dart:ioがつかえなかったりとまだまだな感じです。

なんとなくですけど、1年位で商用で利用できるようになることはないかなーという印象です。


Webとは直接関係ないですが、dev channelでは、以下のことがあったのでついでに記載しておきます。

stable channelはAndroid support libraryを利用していますが、dev channelはAndroidXです。
そのため、既存のProjectでflutter create .とか実行するとAndroidXに対応していないとかでメッセージが出ると思います。

他にはPluginの呼び出しをrunAppの前に行うと

https://gist.github.com/matsuhiro/1ccbc9a6a5527b2c490ed31b79ffd1eb

このようなエラーが表示されます。
dev channelだけです。

Pluginの機能を呼び出す前にWidgetsFlutterBinding.ensureInitialized()を実行せよと言っているわけです。
呼び出せばいいだけといえばいいだけですが、onRunApp的なinterfaceがFlutter側にあっても良いのかなとかは思いました。
アプリの起動シーケンスの中で、Native側の初期化をしたいとはあると思うので。

サンプルを試すときは
https://github.com/matsuhiro/flutter_for_web_test/blob/master/lib/main.dart#L6
コメントアウト消してください。

2019年12月3日火曜日

1人 Flutter Advent Calendar 2019:#3 display resolution


前回のソースをQVGAのデバイス実行してみると以下のようになります。

色々とはみ出していてデザインが破綻しています。
ボタンが要素の右の端にあったらもう押すことも出来ないです。

Androidだけではなく、iOSでも画面の解像度にに種類があります。
FlutterではMediaQueryから取得できるsizeはLogical Pixelとされており、Deviceに関係なくだいたい同になるようにいい感じにやってくれます。Androidのdpみたいなもんですね。

しかし、画面自体が小さすぎれば当然、上の画像のように切れてしまいます。

Androidは画像やsizeの定義ファイルを解像度ごとにdirectoryで分けることで、こういった状況に対処します。
https://developer.android.com/training/multiscreen/screendensities?hl=ja

Flutterでも画像に関しては似たような回避策がありそうです。https://flutter.dev/docs/get-started/flutter-for/android-devs#where-do-i-store-my-resolution-dependent-image-files

しかし、widthやheightなどの値については回避策が無さそうでした。
また、デザインツールで出力されるものはwidhtなどが直値で指定されるので、これですべての解像度に対応するのは難しいです。

とはいえ、デザインツールで作成したものからは直値しかわかりません。
アプローチの方法は、Percentで指定する、全体にScaleをする、FlexBoxを利用してサイズに対して柔軟に対応するかだと思います。

Percent指定ですとflutter_screenutil というのがあります。
最初に画面のサイズを決めて、以降はutilityで提供されている関数でwidth/heightを指定します。
基本的には最初に設定したサイズでscaleしているだけになります。
ただ、これだと全体でScreenUtil.getInstance()を利用してサイズを指定しないとダメなので、ちょっと手間ですし、画面の縦横比に従って設定値を調整しないといけません。
https://github.com/matsuhiro/display_resolution_tests/blob/master/lib/main.dart#L189-L275

全体にScaleする方法につい
https://flutter.dev/docs/development/ui/widgets/layout
の中から利用できるものを選んで解決しました。

私の見た中ではFittedBoxが解決策に最適でした。
使った場所はこんなかんじです。
https://github.com/matsuhiro/display_resolution_tests/blob/master/lib/main.dart#L85-L87

全体のレイアウトは崩さずに全体的にScaleされます。
現実には、見栄えのために画面幅に応じたPaddingを入れるのが良いと思います。

これは、最大でも画面幅の90%になるようにPaddingを入れてあります。

これを解像度が大きい端末で実行すると
この様になって、全体にきれいに表示できると思いますし、解決策としても簡単に出来るのではないかと思います。

ここで扱ったサンプルは
https://github.com/matsuhiro/display_resolution_tests
にあります。


FlexBoxに関しては、また違う機会に扱おうと思います。

2019年12月2日月曜日

1人 Flutter Advent Calendar 2019:#2 FigmaとZeplinでUI構築


Flutterでアプリを作成するにあたって、デザインツールでデザインしたものをきれいに取り込みたいという要望とか同期はアプリを作っていれば当然あると思います。
それを実現する方法としてFigma→Zeplin→Flutterの方法を一つの案として照会しようと思います。

連携のためにFigmaとZeplinはそれぞれDesktop版を入れてくれさい。
また、FigmaとZeplinでそれぞれProjectを一つ作っておいてください。

Figmaの細かい説明はしませんがFrameを作成してGoogle Pixel 2 XLを選択してみてください。これでだいたいいい感じに表示されると思います


Group化して、Component化などします。

(画像は適当に拾ったものです。。。)

Component化したGroupを選択した状態で、メニューからExportを選択すると

このようなメニューが出てくるので、Zeplinを選びます。
すると、1回、Browserを経由したあとで、以下のような画面になります。


Figma側では、以下を選んでおきます。


そうすると、Componentに以下のように取り込めます。


ココまでで、Designを取り込む準備が出来ました。

次にZeplinにFlutterのExtensionがあるので入れてみてください。
https://extensions.zeplin.io/johnatagoranet/flutter_extension
これです。

Zeplinを再起動すると以下のようにWidgetのコードが見られます。

これをコピペーすると


画像をassetsディレクトリーに入れて実行すると

となります。うまくいかないときは、Figmaの部品の並び順を見てみてください。z-indexで考えた時に下にあるものほど、下の順番にしないときちんと描画されないです。
Figmaの並び順でFlutterのコードも生成されるので、その順番で重なるためです。

なかなかの再現度ですが、微妙にずれています。
ずれている原因はborderのwidthの計算がextensionとFlutterの実装が合っていないようです。
borderのwidthを0にするとぴったり一致します。Blurなどのeffectは再現できていないようです。

同じようなことは、Sketch → Zeplinでも可能です。

このようにデザイナーが作成したものをきれいに取り込むことが出来るわけですが、小さい解像度のデバイスなどでは以下のようになってしまいます。

これについては、別途記述したいと思います。

ここで用いたcodeは
https://github.com/matsuhiro/figma_zeplin_test
こちらにあります。
Figmaはfigファイルというのを作成できだので、後でgitにでもpushしておきます。
Zeplinのプロジェクトは僕が無料版使っているから共有できないっぽいですね。


2019年12月1日日曜日

1人 Flutter Advent Calendar 2019:#1 snackbar


Advent Calendarに参加してみようかと思いましたが、なんかもう席が埋まっていたので1人でやることにしました。
25日間続けばいいけど。。。

FlutterのSnackbarはAndroidのSnackbarみたいなもんです。
両方ともMaterial Designといっているわけだし
https://material.io/components/snackbars/
の実装って感じです。

Android的なSnackBarだと表示できるものはCharSequenceだけなので、文字しか表示できません。
Spannableとかを利用すればiconを表示したりは出来ると思いますが、そこまでする必要ないだろうなとは思います。
Flutterの場合は、任意のWidgetがcontentとして指定することが出来ます。そのため、大きい画像を入れたり、好きなことが出来ます。

          final snackBar = SnackBar(
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.only(
                topLeft: Radius.circular(20),
                topRight: Radius.circular(20),
              ),
            ),
            behavior: SnackBarBehavior.fixed,
            content: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                Image.asset('assets/image_1.jpeg'),
                Text('Yay! A SnackBar!'),
              ],
            ),
            action: SnackBarAction(
              label: 'Undo',
              onPressed: () {
                // Some code to undo the change.
              },
            ),
          );


https://github.com/matsuhiro/snack_bar_test/blob/master/lib/main.dart#L60-L67


しかしながら、あまり自由なことをしないほうが無難でしょう。SnackBarのソースコードのコメントにも書かれていますし。
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/snack_bar.dart#L187-L188

注意することがあるとするとScaffoldのtreeの下にしか存在できないということでしょうか。
Scaffold.of(context).showSnackBar(snackBar)を利用するので、Scaffoldの配下のWidgetから呼び出すことが前提です。

ですが、floatingActionButtonのonPressed内部でshowSnackBarを呼び出そうするのは、誰しもが踏むみたいですね。
解説の記事が至るところにあります。
そういった解説記事を読むのも良いですが、たぶん一番良いのはFlutterが出しているErrorの内容をちゃんとよく読むのが良いです。
エラーの内容に回避策が書かれていますので。

回避策の一例を、いちおう上げておきます。
https://github.com/matsuhiro/snack_bar_test/blob/master/lib/main.dart#L131-L138