okuzawatsの日記

Android / Kotlin / GitHub Actions Enthusiast 🤖

[Android] SnackbarをBuilderパターンっぽく表示したい

目次

2022年の今、遅きに失したというか今更感があるんですが…。AndroidのSnackbarについて、テキストの色、背景色、アクションなどよく変更する設定をBuilderパターンっぽく書きたいなと思いまして、ちょっと試してみました、というのがこの記事です。

Builderクラスを作るパターン

最初に試したのが、SnackbarBuilderというクラスを作るパターン(ソースコードは後述します)。本当は Snackbar.Builder() というようにBuilderのコンストラクタを呼び出したかったのですが、Kotlinの文法ではいい感じにできなさそうです。そのため、SnackbarBuilderというクラスを作った、というのがこのパターンです。

SnackbarBuilder()
  .view(view)
  .text("Awesome Text")
  .textColor(R.color.text_color)
  .backgroundColor(R.color.background_color)
  .length(Snackbar.LENGTH_LONG)
  .action("Action") { /* TODO */ }
  .actionTextColor(R.color.text_color)
  .build()
  .show()

本来やりたかったことがだいたいできているんですが、viewとtextは必須にしたいです(lengthはデフォルト値を決めておけば必須でなくても良い)。SnackbarBuilderのコンストラクタでviewとtextを受け取るようにもできますが、それならば標準で用意されている Snackbar#make を使った方が筋が良さそうです。

ということで、没案となったSnackbarBuilderの(実験的な)ソースコードはこちらです。このコードブロックのあとに改善版を示しますので、改善版をすぐに見たい方は急いでスクロールしてください。

class SnackBarBuilder {
  private var view: View? = null
  private var string: String? = null
  @ColorRes private var textColor: Int? = null
  @ColorRes private var backgroundColor: Int? = null
  private var length: Int = Snackbar.LENGTH_SHORT
  private var actionString: String? = null
  private var action: ((View) -> Unit)? = null
  @ColorRes private var actionTextColor: Int? = null

  fun view(view: View): SnackBarBuilder =
    this.also {
      it.view = view
    }

  fun text(string: String): SnackBarBuilder =
    this.also {
      it.string = string
    }

  fun textColor(@ColorRes textColor: Int): SnackBarBuilder =
    this.also {
      it.textColor = textColor
    }

  fun backgroundColor(@ColorRes backgroundColor: Int): SnackBarBuilder =
    this.also {
      it.backgroundColor = backgroundColor
    }

  fun length(length: Int): SnackBarBuilder =
    this.also {
      it.length = length
    }

  fun action(actionText: String, action: (View) -> Unit): SnackBarBuilder =
    this.also {
      it.actionString = actionText
      it.action = action
    }

  fun actionTextColor(@ColorRes actionTextColor: Int): SnackBarBuilder =
    this.also {
      it.actionTextColor = actionTextColor
    }

  fun build(): Snackbar {
    val view = requireNotNull(view)
    val string = requireNotNull(string)

    val snackBar = Snackbar.make(view, string, length)

    if (textColor != null) {
      snackBar.view
          .findViewById<TextView>(R.id.snackbar_text)
          .setTextColor(
            ContextCompat.getColor(view.context, requireNotNull(textColor))
          )
    }

    if (backgroundColor != null) {
      snackBar.view
          .setBackgroundColor(
            ContextCompat.getColor(view.context, requireNotNull(backgroundColor))
          )
    }

    if (actionString != null && action != null) {
      snackBar.setAction(actionString, action)
    }

    if (actionTextColor != null) {
      snackBar.setActionTextColor(
        ContextCompat.getColor(view.context, requireNotNull(actionTextColor))
      )
    }

    return snackBar
  }
}

長い!

拡張関数で頑張るパターン

次に試したのが、Kotlinの拡張関数で頑張るパターンです。こちらのパターンは、標準で用意されている Snackbar#make を使ってSnackbarのインスタンスを生成した後、その他の属性値をBuilderパターンっぽく渡せるようにしています。とりあえずは、この実装で満足しました。

Snackbar.make(view, "Awesome Text", Snackbar.LENGTH_LONG)
  .textColor(R.color.text_color)
  .backgroundColor(R.color.background_color)
  .action("Action") { /* TODO */ }
  .actionTextColor(R.color.text_color)
  .show()

Kotlinの拡張関数で頑張るパターンのソースコードを以下に示します。Snackbarに対して自身を返す拡張関数を定義して、 also 内で追加の属性値を設定しています。先程の実装よりもこちらの実装の方が短く書けますね。

fun Snackbar.textColor(@ColorRes color: Int): Snackbar =
  this.also {
    it.view
      .findViewById<TextView>(R.id.snackbar_text)
      .setTextColor(ContextCompat.getColor(context, color))
  }

fun Snackbar.backgroundColor(@ColorRes color: Int): Snackbar =
  this.also {
    it.view
      .setBackgroundColor(ContextCompat.getColor(context, color))
  }

fun Snackbar.action(text: String, action: (View) -> Unit): Snackbar =
  this.also {
    it.setAction(text, action)
  }

fun Snackbar.actionTextColor(@ColorRes color: Int): Snackbar =
  this.also {
    it.setActionTextColor(ContextCompat.getColor(context, color))
  }

今更感がすごいですが、きれいに書けるようになって自分的には満足です。

#Android

書いている人 😎

profile

茨城県つくば市在住のモバイルアプリケーションアーキテクト(Androidが得意です)。モバイルアプリのアーキテクチャ、自動テスト、CI/CDに興味があります。いわゆる「レガシーコード」のリファクタリング・リアーキテクチャが好きです。

👉 もっと詳しく

著書 ✍

Android 依存性注入 ヒッチハイク・ガイド🧳

Androidアプリでの依存性注入(Dependency Injection)に入門するためのガイダンスです。依存性注入の概念やメリットを理解し、Dagger Hiltを用いてAndroidアプリに適用する方法を解説しています。

販売サイトへ🏃

ソフトウェアデザイン 2023年6月号📚

特集「クリーンアーキテクチャとは何か?」の第5章「モバイルアプリ開発における実践」を執筆しました。

販売サイトへ🏃

Android クリーンアーキテクチャ ヒッチハイク・ガイド🧳

Androidアプリでのクリーンアーキテクチャに入門するためのガイダンスです。クリーンアーキテクチャの概念を理解し、Androidアプリに適用する方法を解説しています。

販売サイトへ🏃

Android ユニットテスト ヒッチハイク・ガイド🧳

Androidアプリのユニットテストに入門するためのガイダンスです。初学者が混乱せずにAndroidアプリのユニットテストを書き始めることができる、ということを目的としています。

販売サイトへ🏃

Android MVVMアーキテクチャ入門🛠

Androidアプリ開発の初学者に向けた、MVVM(Model-View-ViewModel)アーキテクチャの入門書を書きました。初学者の方を確実にネクストレベルに引き上げる技術書です。NextPublishingより出版されています。

販売サイトへ🏃

関連記事 👀

お問い合わせ✉️

Androidアプリ開発、特にレガシープロジェクトに関するご相談はこちらまで📨

お名前

メールアドレス

お問い合わせ内容