エラー対処メモ

Logcatで確認すべきポイント

Android Studio の Logcat フィルタを

package:com.名前.アプリ名

または

show only selected application

にすると、自分のアプリに関係するログだけ見られます。

実際の開発では、

Finsky
GooglePlayServices
GmsCore
SystemUI
SurfaceFlinger

あたりのエラーは大量に出るので、自分のアプリが正常動作しているなら気にしないことが多いです。

Platform declaration clash: The following declarations have the same JVM signature (setPasswordToggle(Z)V): fun <set-passwordToggle>(<set-?>: Boolean): Unit defined in com.〜 fun setPasswordToggle(newValue: Boolean): Unit defined in com.〜

このエラーは Kotlin のプロパティ setter と、自分で定義した関数の名前が JVM レベルで衝突している ことを意味します。

あなたのコードでは、

var passwordToggle: Boolean by mutableStateOf(false)
    private set

このプロパティに対して Kotlin は内部的に

getPasswordToggle()
setPasswordToggle(Boolean)

という getter/setter メソッドを生成します。

private set を付けていても JVM 上には setter が生成されます。

一方で、あなた自身が

fun setPasswordToggle(newValue: Boolean) {
    ...
}

を定義しています。

すると JVM から見ると両方とも

setPasswordToggle(boolean)

になってしまい、どちらを呼べばよいか分からなくなるため、

Platform declaration clash

が発生します。


解決方法

関数名を変更するのが一般的です。

例えば

fun onPasswordToggleChanged(newValue: Boolean) {
    ...
}

あるいは

fun updatePasswordToggle(newValue: Boolean) {
    ...
}

など。

Compose の ViewModel では

fun onPasswordToggleChanged(...)
fun onBioAuthenticationToggleChanged(...)
fun onPasswordChanged(...)

のような命名がよく使われます。


例えば

fun onPasswordToggleChanged(newValue: Boolean) {
    ...
}

fun onBioAuthenticationToggleChanged(newValue: Boolean) {
    ...
}

に変更するとエラーは解消されます。


ちなみに同じ問題は今後も発生し得ます。

例えば次もアウトです。

var password: String = ""

fun setPassword(value: String) { ... }

なぜなら password プロパティの setter が内部的に setPassword(String) になるからです。

Kotlin では

var xxx
fun setXxx(...)

の組み合わせは基本的に避ける、と覚えておくとよいです。

Lint警告の@Composable関数にmodifierの引数がないと出る【This @Composable function emits content but doesn’t have a modifier parameter.See https://slackhq.github.io/compose-lints/rules/#when-should-i-expose-modifier-parameters for more information.】

はい、その警告は 「とりあえずアノテーションで消す」のはあまり推奨されません。

Compose界隈では、

@Composable
fun MyWidget(
    modifier: Modifier = Modifier
) {
    ...
}

を付けるのが基本スタイルになっています。


例えば

@Composable
fun LoadingOverlay() {
    Box(
        modifier = Modifier.fillMaxSize()
    ) {
        ...
    }
}

より

@Composable
fun LoadingOverlay(
    modifier: Modifier = Modifier
) {
    Box(
        modifier = modifier.fillMaxSize()
    ) {
        ...
    }
}

の方が好まれます。

呼び出し側で

LoadingOverlay(
    modifier = Modifier.zIndex(100f)
)

LoadingOverlay(
    modifier = Modifier.padding(16.dp)
)

ができるためです。


例外もある

すべての Composable に Modifier が必要というわけではありません。

例えば

@Composable
fun AppRoot()

はアプリのルートなので Modifier を付けても意味がありません。

また

@Composable
fun TableMemoApp()

みたいなトップレベルの画面構築関数も不要です。

その場合は

@Suppress("ModifierMissing")

やプロジェクトで使っているLintルールの suppress を付けるのは普通です。


判断基準

私は次の基準で考えています。

Modifierを付けるべき

  • 再利用するUI部品
  • Button風コンポーネント
  • Card風コンポーネント
  • Dialogの中身
  • Overlay
  • Row/Column/Boxを返すUI部品

例:

UserCard()
SearchBar()
LoadingOverlay()
SettingsSection()

Modifier不要

  • AppRoot
  • NavigationHost
  • MainScreen
  • LoginFlow
  • App全体のCoordinator

例:

AppRoot()
MainNavigation()
AuthenticationGate()

SwiftUIで考えると、

struct UserCard: View

には外から .padding() や .frame() を付けたいので Modifier 相当が欲しい。

一方で

@main
struct MyApp: App

に .padding() は付けない。

Composeでもほぼ同じ感覚です。

なので、今回の LoadingOverlay() のような再利用UIなら modifier: Modifier = Modifier を追加し、AppRoot() のようなアプリ全体のエントリーポイントなら suppress で問題ありません。

タイトルとURLをコピーしました