fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

effective dartを一言ぐらいでまとめていく(Style編)

JavaではEffective Javaを、KotlinではKotlin in actionを読んだので、同じようなことをDartでもやっておこうと思って探したら公式のページに書いてあった。

dart.dev

書き始めて3か月ぐらい経つのでちょっと遅いかもしれないが読んでいくことにする。全訳するのはだるいので、項目ごとに短めにまとめていく。タイトルをほぼ直訳するだけになるかもしれない。また、自分の解釈を多分に含むので、Dartを勉強しようとして間違えてこのページに来てしまった人は公式のページも合わせて読んでほしい。

本題

一貫性があり、ロバストで速いDartのコードを書くための2つの包括的なテーマを紹介する:

  • 一貫性があること:二つのコードを見比べたとき、両者の異なる部分にこそ有用なことが存在する。そうであるべき。
  • 簡潔であること:Dartは他のいろんな言語を改善するような形で簡潔に記述できるように作られた。ただし、コードゴルフみたいに詰め込んだコードを書くよりも、経済的なコードを書くべき。

DartアナライザーにはLinterの機能があって、一貫性のあるいいコードを書く助けになる。

ガイドラインは4つの部分に分けられる。 - スタイル - ドキュメンテーション - 使い方 - デザイン

スタイル

識別子

  • class, enum, typedef, 型パラメータはパスカルケースにする。引数なしのアノテーションクラスは小文字で始めてもよい。
  • extensionsの名前はパスカルケースにする。
  • ライブラリ、ファイル名、ディレクトリ名、パッケージ名はスネークケースにする。
  • importプレフィクスはスネークケースにする。
  • メンバ、トップレベル定義、変数、引数、名前付き引数はキャメルケースにする。
  • これから書くコードでは、定数やEnumのメンバはキャメルケースにする。
    • 昔はSCREAMING_CAPSを使ってたけどやめた。その時のものが多少残っている。
  • 2文字より多い頭字語や略語は大文字にする。ただし、IO(Input/Output)、Id(Identification)は例外とする。
  • 使わないコールバック変数は_, __にするのが好ましい。ただし、匿名とかローカルのものだけで、トップレベル関数は逆に省略してはならない。
  • privateでない識別子にリーディングアンダースコアを使わない。
  • プレフィックス文字を使わない。

順序

  • インポートの順序
    • dart:
    • package:
    • 相対インポート
    • export
  • それぞれのセクションごとに空白で区切る
  • セクションごとにアルファベットでソートする

フォーマット

  • dart formatを使え
  • フォーマットしても読みにくいコード(識別子が長すぎたり、ネストがめっちゃ深かったり、いろんなオペレータを複雑に組み合わせてたり)になったら意味がないので、変数名を短くしたり、別の変数に移し替えたりできないか考えてみてほしい。
  • 1行が80文字より長くならないようにする。ただし、URLやファイルパス、コメント、長い文字列(Stringの変数に格納するものなど)はこの限りではない。
  • すべての制御文で波カッコ(中カッコ)を使う。ただし、一行で書けるif文についてはこの限りではない。

BackdropFilterを使わずに画像にblurをかける

"flutter Image blur"で検索してもBackdropFilterの記事しか出ないのでカッとなって書いた。画像にボケと透明度を同時に適用したいとか、BackdropFilterだとボケの感じが思ってたのとなんか違うなという時にどうぞ。

ImageFiltered(
  imageFilter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
  child: Image.network('...',  // dst
    color: Colors.white.withOpacity(0.5),  // src
    colorBlendMode: BlendMode.lighten, // 重ねる色に合わせていい感じになるやつを選んでください
  ),
)

私の場合は、

  • 画像にボケをかけたい -> BackdropFilterを使いたまえ
  • Widgetのグループを半透明にしたい -> Opacityで包みたまえ

の合わせ技で、リストに入っているというのも効いたのか、ボケと半透明が適用された画像が表示されないことがあった。

ここでの問題点は、BackdropFilter自体が結構重い処理*1なのに加えて、さらにOpacityもまたそれなりに重い*2ということで、これを解決したければ、Opacityのページにも書いてあるようにImageのプロパティを使って透過度を適用するのが軽くてよいらしい。私はPorter-DuffのBlendModeをつかって適用+ImageFilteredblurで解決したが、もっと簡単な方法もあるかもしれない。

*1: https://api.flutter.dev/flutter/widgets/BackdropFilter-class.html のサンプルコードの下あたりを参照。

*2: https://api.flutter.dev/flutter/widgets/Opacity-class.html のサンプルコードの下あたりを参照。offscreen bufferを使うことになって重いし、画面の再描画処理も走るので古いGPUの端末では遅くなりそうみたいなことが書いてある。

NavController.currentBackstackEntryをみながら戻りたい画面までpopBackStack()する

前提とやりたいこと

関係しそうな環境:

"androidx.navigation:navigation-fragment-ktx:2.3.0"
"androidx.navigation:navigation-ui-ktx:2.3.0"

ツイッタークライアントを作っている。起動したらホームタイムラインを表示して、そこからツイートの詳細やユーザーのプロフィールのFragmentに遷移したり、会話を読んだりできる。また、自分で管理しているユーザーリストを表示して、そのユーザーリストのタイムラインを見ることもできる。他にもいくつか機能はあるけど、今回は割愛。

基本的な画面遷移の構成はいわゆるmaster/detail flowとかいうやつで、このアプリでは次の3つの遷移ができるようになっている。

  • リスト(master)→詳細(detail)
  • 詳細(detail)→リスト(master) 例:会話とかタグで検索(未実装だけどこれから作る)とか
  • リスト(master)→リスト(master) 例:会話とかユーザーリスト(ナビゲーションドロワーから飛べる)とか

リストのFragmentにはどのエンドポイントを叩くかを決めるパラメータをArgumentとして渡している。論理的には無限に画面遷移できるので、たくさん画面遷移した後でも簡単にホームタイムラインに戻ってこられるように、ナビゲーションドロワーでホームタイムラインへ遷移する機能を作りたい。

困ったことと解決策

普通にandroidx.navigationのNavController.popBackstack(Int, Boolean)を呼ぶと最初に見つかったFragmentまでしか戻れない。

setGraph()をやる時にバックスタックの状態をクリアするかな?と考えて内部の処理を読んでみたら、今まで持ってたグラフのIDとinclusive: trueを渡していたので、inclusive: falseに変えてやってみた。すると画面自体は巻き戻るものの、バックスタックの状態が変わった時に呼ばれるコールバックが呼ばれず、画面の状態に追従できなくなっていた。

実際のところ、これはナビゲーショングラフのルートまで戻る処理であって、ホームタイムラインのFragmentNavControllerのバックスタックからポップされている。ここでようやく、NavControllerのバックスタックというのがFragmentのバックスタックとは違うのだという事に気づいた。

NavControllercurrentBackstackEntryというプロパティを持っていて、これがNavControllerが管理しているバックスタックである。私はこれをFragmentが管理しているバックスタックエンティティ(か、あるいはそれに追従するもの)だと思っていたのだが実際のところはそこまで関連がない。

currentBackstackEntryFragmentのIDや渡したArgumentを持っている。また、popBackStack()を呼んだらすぐに変わるので、ホームタイムラインを表示するためのクエリパラメータを持ったBackstackEntryが出てくるまでpopBackStack()を呼び続ければよい。

github.com