Gradle Version Catalog への書き換えツールを作る【python】

素晴らしいツールを公開されています。

👉 takahirom/gradle-version-catalog-converter: Convert `implementation 'androidx.core:core-ktx:1.7.0'` into `androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }` hatena-bookmark

結果は以下。

gradle-version-catalog-converter


implementation "com.google.accompanist:accompanist-systemuicontroller:0.17.0"
implementation 'com.google.accompanist:accompanist-swiperefresh:0.26.5-rc'


[versions]
comGoogleAccompanist = "0.17.0"

[libraries]
com-google-accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "comGoogleAccompanist" }
com-google-accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version.ref = "comGoogleAccompanist" }

build.gradle
implementation libs.com.google.accompanist.systemuicontroller
implementation libs.com.google.accompanist.swiperefresh

build.gradle.kts
implementation(libs.com.google.accompanist.systemuicontroller)
implementation(libs.com.google.accompanist.swiperefresh)

高機能で便利です。

作った動機はだれもが同感できるでしょう。

ということで、

私も python の勉強がてら雑魚ツールを作ります。

クリップボードにコピーした


implementation "com.google.accompanist:accompanist-systemuicontroller:0.17.0"
implementation 'com.google.accompanist:accompanist-swiperefresh:0.26.5-rc'

を python スクリプト実行後すると


* source
  implementation "com.google.accompanist:accompanist-systemuicontroller:0.17.0"
  implementation 'com.google.accompanist:accompanist-swiperefresh:0.26.5-rc'

* gradle/libs.versions.toml
[libraries]
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version = "0.17.0"" }
accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version = "0.26.5-rc" }

* build.gradle
implementation libs.accompanist.systemuicontroller
implementation libs.accompanist.swiperefresh

と表示します。

まずは、ただそれだけです。

[versions] を設定するのもだるい implementation 単独バージョンのライブラリ用。

幼稚なスクリプトなので拡張や変更、削除しやすいです。

重複に注意です。

このへんの変換処理は、

きっと、Android Studio プラグインが登場して、

そのあと、Android Studio に取り込まれいく

のだろうと妄想しています。

それまでのつなぎで。

※ このページは gist を更新しながら更新していきます。

※ 追記: こんなのあったんですね!


👉 「⚠ This project uses Gradle Version Catalogs: this tool may not behave as expected.」→ 今現在、Gradle Version Catalog には gradle-versions-plugin が必須では? hatena-bookmark

👉 【Gradle Version Catalog】libs.versions.toml キー名の形式 camelCase vs kebab-case hatena-bookmark


Caused by: org.gradle.internal.resolve.ModuleVersionNotFoundException

build 失敗して出ますよね。

意味は、

設定しているリポジトリにそのモジュールのそのバージョンがないよ。

ということです。

今、こういうの出ました。


Caused by: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find app.cash.sqldelight:android-paging3-extensions:2.0.0-alpha03.
Searched in the following locations:
  - https://dl.google.com/dl/android/maven2/app/cash/sqldelight/android-paging3-extensions/2.0.0-alpha03/android-paging3-extensions-2.0.0-alpha03.pom
  - https://repo.maven.apache.org/maven2/app/cash/sqldelight/android-paging3-extensions/2.0.0-alpha03/android-paging3-extensions-2.0.0-alpha03.pom

エラーの https から始まるリンクから「バージョン」を含む文字列までを省いてブラウザでアクセスします。

👉 https://dl.google.com/dl/android/maven2/app/cash/sqldelight/android-paging3-extensions/hatena-bookmark

👉 https://repo.maven.apache.org/maven2/app/cash/sqldelight/android-paging3-extensions/hatena-bookmark

maven central にある、android-paging3-extensions の最新バージョンは、「2.0.0-alpha01」ですね!

build.glade のバージョンを変更すれば通りますね!

 

まとめ


Caused by: org.gradle.internal.resolve.ModuleVersionNotFoundException

が出たときは、build.gradle に設定したリポジトリに、そのバージョンが存在しません。エラーメッセージそのままですね!

ほぼ、利用してるリポジトリは以下2つのリポジトリであることが多いでしょう。


repositories {
  google()
  mavenCentral()
}

以下の2つをブラウザにブックマークしておくといいですね。

👉 Google's Maven Repository hatena-bookmark
👉 Maven Central Repository Search 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