Kotlin スコープ関数 の上手な使い分け その4 - let

kotlin scope function

 

■ let の便利な使い方 (公式)


public inline fun <T, R> T.let(block: (T) -> R): R {
  contract {...}
  return block(this)
}

The Kotlin standard library function let can be used for scoping and null-checks. When called on an object, let executes the given block of code and returns the result of its last expression. The object is accessible inside the block by the reference it (by default) or a custom name.

Kotlinの標準ライブラリ関数 let は、スコープや NULL チェックに使用することができます。オブジェクト上で呼び出されると、let は与えられたコードブロックを実行
し、その最後の式の結果を返します。オブジェクトはブロック内で参照 it (デフォルト) またはカスタム名でアクセスできます。

Executing a lambda on non-null objects

NULLでないオブジェクトに対してラムダを実行する

Introducing an expression as a variable in local scope

ローカルスコープでの変数としての式の導入

Execute if not null

nullでない場合は実行


val value = ...

value?.let {
    ... // execute this block if not null
}

Map nullable value if not null

nullでない場合、nullableな値をマップする


val value = ...

val mapped = value?.let { transformValue(it) } ?: defaultValue
// defaultValue is returned if the value or the transform result is null.

👉 Kotlin Examples: Learn Kotlin Programming By Example hatena-bookmark
👉 Scope functions | Kotlin hatena-bookmark
👉 Idioms | Kotlin hatena-bookmark

 

■ まとめ

公式では、「let」は「null を避けてのブロック内処理」に使うことを推しています。

let 自体が不要な場合は、AndroidStudio や IDE が知らせてくれます。

👉 Kotlin スコープ関数 の上手な使い分け その1 - apply hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その2 - also hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その3 - with hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その4 - let hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その5 - run hatena-bookmark


Kotlin スコープ関数 の上手な使い分け その2 - also

kotlin scope function

apply と also は似ています。

👉 Kotlin スコープ関数 の上手な使い分け その1 - apply hatena-bookmark

 

■ also の便利な使い方 (公式)


public inline fun <T> T.also(block: (T) -> Unit): T {
  contract {...}
  block(this)
  return this
}

also works like apply: it executes a given block and returns the object called. Inside the block, the object is referenced by it, so it's easier to pass it as an argument. This function is handy for embedding additional actions, such as logging in call chains.

also も apply と同じように動作します。与えられたブロックを実行し、呼び出されたオブジェクトを返します。ブロックの内部では、オブジェクトは it によって参照されるので、引数として渡すのは簡単です。この関数は、コールチェーンにロギングなどの追加アクションを埋め込むのに便利です。


val jake = Person("Jake", 30, "Android developer") 
  .also {                                          
    writeCreationLog(it)                         
  }

Additional effects

追加効果

👉 Kotlin Examples: Learn Kotlin Programming By Example hatena-bookmark
👉 Scope functions | Kotlin hatena-bookmark
👉 Idioms | Kotlin hatena-bookmark

 

■ まとめ

「also」は「オブジェクトに対しての追加処理」に使う と良さそうです。

あと、Singleton インスタンスの生成時のコードで見かけることが印象に強いです。


@JvmStatic
fun getInstance(context: Context): PowerSpinnerPersistence =
  instance ?: synchronized(this) {
    instance ?: PowerSpinnerPersistence().also {
      instance = it
      sharedPreferenceManager =
        context.getSharedPreferences("com.skydoves.powerspinenr", Context.MODE_PRIVATE)
    }
  }


companion object {
  @Volatile
  private var INSTANCE: MySingleton? = null
  fun getInstance(context: Context) =
    INSTANCE ?: synchronized(this) {
      INSTANCE ?: MySingleton(context).also {
        INSTANCE = it
      }
    }
}

👉 Kotlin で書きたい「正しいシングルトン(Singleton)」 hatena-bookmark

こんな使い方も公式に書いてました。

Swap two variables

2つの変数を入れ替える


var a = 1
var b = 2
a = b.also { b = a }

👉 Kotlin スコープ関数 の上手な使い分け その1 - apply hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その2 - also hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その3 - with hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その4 - let hatena-bookmark
👉 Kotlin スコープ関数 の上手な使い分け その5 - run hatena-bookmark


【JetpackCompose】BottomNavigation の使い方

アプリの基本的な画面です。

【JetpackCompose】BottomNavigation の使い方

シンプルなもので眺めてみます。

 

■ 構成

「Column」を使って2つのパーツを縦に並べます。

【JetpackCompose】BottomNavigation の使い方

メイン画面を表示する部分は

「NavHost」

それを表示切り替えする下側に並んだ操作部分は

「BottomNavigation」

です。

コードは以下のように構成できます。


@Composable
fun MainScreen() {

  val navController = rememberNavController()

  Column {

    NavHost(
      navController = navController,
    ) {
      composable(NavigationItem.HOME.route) {
        HomeScreen()
      }
      composable(NavigationItem.CRYPT.route) {
        CryptScreen()
      }
      // ...
    }

    BottomNavigation {
      val backStack by navController.currentBackStackEntryAsState()
      val current = backStack?.destination?.route
      NavigationItem.values().forEach { item ->
        BottomNavigationItem(
          // ...
        )
      }
    }

  }

}


enum class NavigationItem(
  // ...
)

 

■ NavController

NavHost を BottomNavigation を紐付けるコントローラーです。


val navController = rememberNavController()

両方からアクセスしやすい場所に置くことになります。

 

■ NavigationItem

BottomNavigation に並べるアイテムの情報をまとめておきます。

以下、3つの組み合わせになります。


- 画面を表す内部文字列(遷移先)

- アイコン画像

- 表示する画面名称文字列

すべて定数なので enum クラスで作成します。


enum class NavigationItem(
  val route: String,
  val icon: ImageVector,
  @StringRes val label: Int,
) {

  HOME("home", Icons.Outlined.Home, R.string.nav_label_home),
  CRYPT("crypt", Icons.Outlined.MonetizationOn, R.string.nav_label_crypt),
  // ...

}

ここでは多言語化を考慮して、

遷移を表す内部で使用する文字列と、画面名称を表示する文字列を区別しています。

必要なければまとめてもいいと思います。

 

■ BottomNavigation

NavigationItem を BottomNavigation に配置します。


BottomNavigation {

  val backStack by navController.currentBackStackEntryAsState()
  val current = backStack?.destination?.route

  NavigationItem.values().forEach { item ->
    BottomNavigationItem(
      selected = current == item.route,
      onClick = {
        navController.navigate(item.route) {

          // no back stacks. 0 is the root navigation
          popUpTo(id = 0) {  
            saveState = true
          }
          launchSingleTop = true
          restoreState = true
        }
      },
      icon = { Icon(item.icon, null) },
      label = {
        Text(
          text = stringResource(item.label),
          maxLines = 1
        )
      },
      alwaysShowLabel = false
    )
  }
}

onClick のラムダブロックでは、


- バックスタックなし。

- 画面の state は保存/リストアする。

としています。

それぞれの画面内での遷移がない場合は、これがシンプルで自然だと思います。

画面のスクロールポジションもこれで保持できます。

 

■ NavHost

画面のメイン表示部分です。

NavigationItem に紐づけて各画面の Screen-level Composable を記述しておきます。


NavHost( 
) {
  composable(NavigationItem.HOME.route) {
    HomeScreen()
  }
  composable(NavigationItem.CRYPT.route) {
    CryptScreen()
  }
  // ...
}

 

■ まとめ


@Composable
fun MainScreen() {

  val navController = rememberNavController()

  Column(
    modifier = Modifier.fillMaxSize(),
    verticalArrangement = Arrangement.Bottom
  ) {

    NavHost(
      navController = navController,
      startDestination = NavigationItem.HOME.route,
      modifier = Modifier
        .weight(1f)
    ) {
      composable(NavigationItem.HOME.route) {
        HomeScreen()
      }
      composable(NavigationItem.CRYPT.route) {
        CryptScreen()
      }
      // ...
    }

    BottomNavigation {

      val backStack by navController.currentBackStackEntryAsState()
      val current = backStack?.destination?.route

      NavigationItem.values().forEach { item ->
        BottomNavigationItem(
          selected = current == item.route,
          onClick = {
            navController.navigate(item.route) {
              // no back stacks. 0 is the root navigation
              popUpTo(id = 0) { 
                saveState = true
              }
              launchSingleTop = true
              restoreState = true
            }
          },
          icon = { Icon(item.icon, null) },
          label = {
            Text(
              text = stringResource(item.label),
              maxLines = 1
            )
          },
          alwaysShowLabel = false
        )
      }

    }

  }

}


enum class NavigationItem(
  val route: String,
  val icon: ImageVector,
  @StringRes val label: Int,
) {

  HOME("home", Icons.Outlined.Home, R.string.nav_label_home),
  CRYPT("crypt", Icons.Outlined.MonetizationOn, R.string.nav_label_crypt),
  // ...

}

Scaffold って使い勝手悪くね?

👉 JetpackCompose Coil で GIF - Qiita hatena-bookmark