⚙️

【Flutter】380+ LIKES のパッケージを開発するために実践したこと

2021/08/16に公開

Flutter の画像切り抜きパッケージ crop_your_image を開発・公開しています。

https://pub.dev/packages/crop_your_image

2021年3月に最初のバージョンを公開してからかれこれ約3年、気がついたら LIKES の数も 380 を達成し(2024.02.09現在)、画像切り抜きパッケージとしてもかなり上の方まで上がってこれたようです。使ってくださっている方、ありがとうございます!

crop imageで検索すると一番上に!

この記事では、そんな crop_your_image パッケージを開発する上で気をつけたこと、気をつけていることをまとめてみようと思います。

Flutter の Widget らしい、使いやすい設計にする

さっそく1つ目ですが、これが開発をスタートする上で一番気をつけたことです。

Flutter の特徴のひとつに Everything is a Widget というものがありますが、これは単に全ての要素を Widget として扱うとうことだけでなく、それによって 好きな Widget をどこにでも配置できる という特徴も実現していると私は考えています。

例えば ScaffoldfloatingActionButton プロパティは Widget 型なので必ずしも FloatingActionButton を渡す必要はありませんし、 showDialogbuilder が返却する Widget も必ずしも AlertDialog のようなダイアログ用の Widget である必要はありません。

プロパティや引数、戻り値の型が Widget で定義されている以上、そこには任意の Widget を返却してもエラーにならずに動作する、というのが Flutter の便利なところであり、直感的に開発できる要因になっていると理解しています。[1]

crop_your_imageCrop という Widget も、同様になるべくどこに置いても動作するよう工夫しました。

例えば、 Cropbuild() では、まず真っ先に LayoutBuilder を使ってレイアウトの状況に応じて確定した幅と高さを測定し、それを MediaQuerysize に上書きすることで、そのサイズを Crop の世界における最大の幅と高さ として各種計算に利用しています。


  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (c, constraints) {
        final newData = MediaQuery.of(c).copyWith(
          size: constraints.biggest,
        );
        return MediaQuery(
          data: newData,
          child: _CropEditor(
            image: image,
            onCropped: onCropped,
            aspectRatio: aspectRatio,

...以下略

これにより、 Crop を配置するためのサイズや比率を使う側に強制・意識させることなく、任意の画面の好きな場所に配置すれば、それが画面全体であろうがダイアログの中であろうが、ボトムシートの中であろうが どこに置いても画像の切り抜きができる ことを目指しています。

画像切り抜きパッケージの中には、そもそも Widget ではなく特定の static なメソッドを呼び出すと画面ごとネイティブで作ったものに遷移するような作りのものもありますが、やはり Flutter らしく好きな場所に好きな Widget を配置する感覚で切り抜き UI を埋め込めることでデザインの自由度も上がり採用しやすくなるだろう、という考えでこのような設計にしています。

可能な限りデザインをカスタマイズできるようにする

「デザインのカスタマイズ性」も crop_your_image では再優先事項の1つです。

Flutter の公式ドキュメントの FAQ には "Why would I want to share layout code across iOS and Android?" という項目で、 iOS と Android で共通のデザインでアプリを提供することに対する Flutter チームの考え方が記載されています。

https://flutter.dev/docs/resources/faq#why-would-i-want-to-share-layout-code-across-ios-and-android

ここでは、

More and more, we see mobile app layouts and designs evolving to be more brand-driven and unified across platforms.

と説明されている通り、近年のモバイルアプリでは 「プラットフォームらしさ」よりも「ブランドらしさ」を優先して UI をデザインすることが大事 であり、それを OS のデフォルト UI に縛られることなく達成できるのが Flutter の Widget の思想であることが読み取れます。

画像切り抜きパッケージの中には、決まった UI で文言や色だけ変更可能なものも数多く公開されていますが、それでは Flutter の目指す「ブランドらしさ」を実現できないだろう、ということで、「可能な限りのカスタマイズ性」をとても意識した API デザインになっています。

これは、例えば Crop() のプロパティを見てもわかるかと思います。

  const Crop({
    // ... 関係のないプロパティは除外
    this.aspectRatio, // 切り抜き枠の縦横非(指定しない場合は任意の比率)
    this.initialSize, // 初期表示時の切り抜き枠のサイズ
    this.initialArea, // 初期表示時の切り抜き枠(座標指定)
    this.withCircleUi = false, // 切り抜き枠を丸くするかどうか(切り抜き結果とは別)
    this.onMoved, // 切り抜き枠が移動するごとに呼び出されるコールバック
    this.onStatusChanged, // ステータスが変わるごとに呼び出されるコールバック
    this.maskColor, // 切り抜き枠外の色
    this.baseColor = Colors.white, // 切り抜き UI の余白の色 
    this.cornerDotBuilder, // 切り抜き枠を操作するための四隅のドットを表す Widget
  })  : assert((initialSize ?? 1.0) <= 1.0,
            'initialSize must be less than 1.0, or null meaning not specified.'),

...以下略

という具合に、切り抜き枠のサイズや形、またそれらを構築する UI の色だけでなく、切り抜き枠を操作する四隅のドットに至っては任意の Widget を配置してカスタマイズできるようにしています。

また、maskColor 等の色関係は setState() などのリビルドで、切り抜き枠に対するサイズ指定等は CropController を通して任意のタイミングで変えられるようになっているため、「このボタンを押したら枠を丸くする」「一定時間経ったら色を変える」等の細かな制御もかなりアプリ開発者の自由にできるようになっています。

とはいえこのあたりは言葉で説明するよりサンプルを見せた方が早いだろう、ということで、サンプル画面を集めたギャラリー的なアプリのリポジトリも用意しています。

https://github.com/chooyan-eng/crop_your_image_gallery

このリポジトリは crop_your_image の利用者が自由にページを追加して画像切り抜きの利用した画面を作成してプルリクしてくれれば、私の方でその画面のスクショと作成者を README.md に追加するようにしていますので、興味のある方はぜひ自由なデザインでひと画面作ってみてください。

宣伝する

作っても知ってもらえなければ使ってもらうこともできないだろう、ということで宣伝もいろいろ試しました。

このあたりについては以前書いた以下の記事に詳しく書いていますので、「コミュニティで宣伝する」の項目を読んでみてください。

https://zenn.dev/chooyan/articles/f358c9a80f471e#コミュニティで宣伝する

結論だけ言うと、Discord や Slack, Reddit などに投稿した結果、 Reddit が一番反応が良かったため、その後も何か宣伝したいものがある場合は Reddit に投稿するようにしてみています。

他にも、Flutter Osaka や Flutter Tokyo などの勉強会で何度か LT したり宣伝したりさせていただきました。

Flutter Meetup Osaka #7

動画

https://www.youtube.com/watch?v=Ycpo2Dq3qKs

資料

https://docs.google.com/presentation/d/1YssRkNYTwwkUCX826B6l6xI2B-lUWN94-Zzg0NxdBMc/edit?usp=sharing

Flutter Meetup Tokyo #16

動画

https://www.youtube.com/watch?v=_jMXRMbC6l0&t=2245s

資料

https://docs.google.com/presentation/d/1pMZ8XkdTrNZrYiPmIKwG6V03JyfR_lEqstbflrlk9ik/edit?usp=sharing

作るパッケージのジャンルによるかもしれませんが、ある程度 LIKE がつけば pub.dev でキーワード検索した際に上の方に上がってくるため、世の開発者の「ちょっと試してみるか」候補に上がりやすくなるため、初速は大事だと考えています。

その他

その他、ちょっとした工夫ポイント一覧です。

「差別化ポイント」を Readme でアピールする

競合となる類似のパッケージとの差別化ポイントを明確に決め、それを基に API デザインをするのはもちろんのこと、そのことを Readme にしつこいくらいに書くようにしています。

開発者にとっては「まず Readme を読んで触るかどうか考える」人も多いと思います。実際にインポートしてコードを書いて動作をするより、ざっと Readme を斜め読みした方が早いのは確かにその通りだと思うので(英語を日常的に使っている国の方々にとっては特に)、 斜め読みでもちゃんと目に入るよう大項目を作って説明する のが大事になると思います。

例えば crop_your_image の場合は Readme の最初の項目で以下のように説明しています。

crop_your_image provides only minimum UI for deciding cropping area inside images. Other UI parts, such as "Crop" button or "Change Aspect Ratio" button, need to be prepared by each app developers.

This policy helps app developers to build "Cropping page" with the design of their own brand.In order to control the actions for cropping images, you can use CropController from whatever your Widgets.

crop_your_image は最小限の UI パーツしか提供しないから、切り抜き操作に必要な UI パーツはアプリ開発者が用意してね、でもそのおかげで各アプリのブランドに応じた画面デザインが可能だよ、ということを頑張って伝えているつもりです。

ポイントは、「まだそれが実現できていない」という自己評価の場合でも 「まだ実現できてはいないけどそれを目指している」旨を記載する ことかと思います。

自信がないから書かないのではなく、ゴールがどこにあるかを書いておくことで、「完成したら使いたい」と利用者に覚えておいてもらえるかもしれませんし、もしかしたらゴールに近づくためのプルリクがある日飛んでくるかもしれません。

依存パッケージは極力少なくする

crop_your_image が依存しているパッケージは、画像処理をする image という Dart パッケージのみです。

依存するパッケージが多くなると確かに開発は楽になるかもしれませんが、一方で依存先の開発状況にこちらの開発状況がひっぱられるリスクがある(Null Safety対応やマルチプラットフォーム対応などはわかりやすい例です)、アプリ側の依存先と競合する、依存先のバグがそのまま自分のパッケージのバグにつながる、など、 自分だけではコントロールできない範囲が広がる のも事実です。

他のパッケージを頼るのではなく、Flutter フレームワークや Dart が標準で用意してくれているものをうまく使ってパッケージを作り上げることで、かなり小回りのきく、開発や保守のしやすいパッケージになると考えています。

ドキュメントコメントをしっかり書く

Flutter フレームワーク自体のソースコードや provider パッケージなどを見ていると特に実感するのですが、クラスやフィールド、メソッドなどに対するドキュメントコメントがしっかりかかれていると、使う側はいちいち使い方を調べるためにブラウザを立ち上げる必要がなく、開発体験を大きく向上できます。

変数名やメソッド名を見ればわかるようなことだけではなく、なぜそのような設計になっているのか、どのような状況で使われることを想定しているのか、どのように使うのか(サンプルコード)、その他注意点などを、 Readme を書くように丁寧に書くと良いと思います。

ちなみにパブリックな要素(クラス名やメソッド、フィールドなど)に対するコメントの有無はパッケージの PUB POINTS に影響しますので、その意味でもしっかり書いておくと良いでしょう。

PUB POINTS は満点をキープする

Flutter パッケージの "Scores" の項目には PUB POINTS という項目があり、先述のコメントやコードフォーマット、依存先が最新に保たれているか、対応プラットフォームはどれか、などに応じてポイントが機械的につくようになっています。

この項目は基本的に「やれば対応できる」ものが多く、満点をキープすることも無理ではない[2]ため、可能な限り高得点を保つようにすると良いでしょう。ここがしっかりしているだけで、「ちゃんとメンテされているパッケージ」感が出ると思います。

まとめ

以上です。他にも何かあれば追記します。また、ここはどうしているの?というような疑問があればコメントいただければと思います。

パッケージを開発して使ってもらうということは、アプリを作ってユーザーに使ってもらうことと同じだと考えています。そこには利用者が一般ユーザーか開発者か、という違いがあるだけです。

APIデザインやコードの品質だけではなく、差別化ポイントのアピール、広報、問い合わせに対するサポートなどいろいろな頭を使うことになりますが、その分自分のパッケージをインポートしたアプリが世に出た時の嬉しさは自分でアプリを開発して公開したときとはまた別のものがあります。(と思います)

作って終わりではなく、どうせなら使ってもらえるパッケージを目指して試行錯誤していきましょう。私も引き続き頑張ります。

脚注
  1. もちろん使い方によってはレイアウトが崩れたりレイアウト関連のエラーが発生したりする場合もありますが。 ↩︎

  2. Platform Channel を使っている場合は "Support multiple platforms" の項目が難しくなりますが。 ↩︎

Discussion