📱

[Android]Kotlin2.0

2024/11/27に公開

droidkaigiでとてもタメになるKotlin2.0の解説があったので、すぐ見返せるようにメモ
https://www.youtube.com/watch?v=aZds8ewbRkk

K2 Compilerの影響

  • Stablized Features
  • Text Experience
  • Jetpack Compose
  • Compose Multiplatform
  • kotlin Native

kotlin2.0にマイグレーション

kotlin, kspのバージョンを上げる(2.0.0以上)

依存先の追加compose compilerがkotlinのリポジトリに統合された

[versions]
koltin = "2.0.0"
[plugins]
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin"}

プロジェクトルートのbuild.gradle.ktsに依存を追加

plugins {
	alias(libs.plugins.compose.compiler) apply false
}

Composeを使用する各モジュールでプラグインを適用

plugins {
	alias(libs.plugins.compose.compiler)
}

kotlinCompilerExtensionVersionを削除

gitignoreに.kotlinを追加(コンパイル時に.kotlinが作られるため)

kotlinOptionsをkotlinのcompileroptionsに書き換える

kotlin {
	compileroptions {
		jvmTarget.set(JvmTarget.JVM_17)
	}
}

Android Studioで有効化する(プロジェクトがkotlin2.0である場合、現状無理にする必要はない)

setting → Language & Frameworks → Kotlin → Enable K2 Kotlin Mode

K2 Compilerによる影響

Smart castの改善

  • ローカル変数とさらなるスコープ

    • 条件の前に宣言した変数はSmart castされるようになった

      val isCat = animal is Cat
      if (isCat) {
      	animal.purr()
      }
      
  • 論理和演算子を使用した型チェック

    • or演算子で型チェックを組み合わせると共通のsuper typeに smart cast
    interface Status {
    	fun signal() {}
    }
    
    interface Ok : Status
    interface Postponed : Status
    
    fun signalCheck(signalStatus: Any) {
    	if (signalStatus is Postponed || signalStatus is Ok) {
    		signalStatus.signal()
    	}
    }
    
  • インライン関数

// プロセスを表すインターフェース
interface Processor {
    fun process()
}

class SampleProcessor : Processor {
    override fun process() {
        println("処理実行")
    }
}

// Kotlin 1.9.24 での動作
class OldExample {
    inline fun runProcess(processor: Processor?) {
        if (processor != null) {
            // ここでコンパイラが警告を出す可能性があった
            processor.process()  // 不安定だと判断される
        }
    }
}

// Kotlin 2.0 での動作
class NewExample {
    inline fun runProcess(processor: Processor?) {
        if (processor != null) {
            // スマートキャストが確実に機能する
            processor.process()  // 安全だと判断される
        }
    }
}

fun main() {
    val processor: Processor? = SampleProcessor()
    
    // 両方とも同じように動作するが、
    // Kotlin 2.0ではコンパイラがより賢く判断する
    NewExample().runProcess(processor)
}
  • 関数型のプロパティ
// Kotlin 1.9.24 での問題
class Holder(val provider: (() -> Unit)?) {
    fun process() {
        if (provider != null) {
            provider() // 1.9.24ではここでスマートキャストが効かないことがあった
        }
    }
}

// Kotlin 2.0 での修正
class Holder(val provider: (() -> Unit)?) {
    fun process() {
        if (provider != null) {
            provider() // 2.0ではスマートキャストが正しく機能する
        }
    }
}

// 使用例
fun main() {
    val holder = Holder { println("実行") }
    holder.process() // 正しく動作する
}
  • インクリメント演算子とデクリメント演算子
// Kotlin 1.9.24
private fun foo(longs: MutableList<Long>) {
    longs[0] += 1  // このとき内部で暗黙の型変換が必要だった(intからlongへ)
}

// Kotlin 2.0
private fun foo(longs: MutableList<Long>) {
    longs[0] += 1  // 型変換が自動的に処理される
}
class Box(val longs: MutableList<Long>)

// Kotlin 1.9.24
fun foo(box: Box?) {
    box?.longs[0] += 1    // 警告が出る可能性があった
    box?.longs[0] += 1L   // 明示的にLong型を指定する必要があった
}

// Kotlin 2.0
fun foo(box: Box?) {
    box?.longs[0] += 1    // 警告なく動作
    box?.longs[0] += 1L   // どちらの書き方も問題なく動作
}

BackingFieldsの進化

private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean>
	get() = _isLoading

val isLoading: StateFlow<Boolean>
	field = MutableStateFlow(false)

使用する際には現状では実験中の機能なので以下が必要

kotlin {
	sourceSets.all {
		languageSettings.enableLaunguageFeature("ExplicitBackingFields")
	}
}

ExperimentalのものからStableになったもの

enum classのvalues()は毎回ArrayListを生成、entriesでは同じListを毎回返すようになったので実行パフォーマンスが向上した

AutoClosable

  • リソース(ファイル、ネットワーク接続など)を使い終わったら自動的に閉じる(片付ける)ための仕組み
// 従来の方法
class NetworkConnection {
   fun connect() {
       println("接続開始")
   }
   
   fun readContent(): String {
       return "データ"
   }
   
   fun close() {
       println("接続を閉じます")
   }
}

val connection = NetworkConnection()
try {
   connection.connect()
   connection.readContent()
} finally {
   connection.close()
}

// AutoClosableを使った方法
class NetworkConnection : AutoCloseable {
   fun connect() {
       println("接続開始")
   }
   
   fun readContent(): String {
       return "データ"
   }
   
   override fun close() {
       println("接続を閉じます")
   }
}

// useを使って自動的にclose()が呼ばれる
NetworkConnection().use { connection ->
   connection.connect()
   connection.readContent()
} // ここで自動的にclose()が呼ばれる

Test Experimence

Power Assertが公式サポート

  • 失敗した時に過程を詳しく出力してくれる

  • 導入方法

    • 使用するmoduleに
    plugins {
    	koltin("plugin.power-assert") version "2.0.0"
    }
    
    • dependenciesに追加
    dependencies {
    	testImplementation(kotlin("test"))
    }
    
    • 使用するmoduleに
    import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
    
    @OptIn(ExperimentalKotlinGradlePluginApi::class)
    powerAssert {
       functions = listOf(
           "kotlin.assert",
           "kotlin.require", 
           "kotlin.test.assertTrue",
           "kotlin.test.assertEquals",
           "kotlin.test.assertNull"
       )
    
       includedSourceSets = listOf(
           "releaseUnitTest",
           "debugUnitTest"
       )
    }
    

Jetpack Compose Straong Skip Mode

前提

  • Stable
    • プリミティブ値、文字列、Stableな値で構成されたImmutableなclass
  • UnStable
    • List、Set、MutableなClass(MutableList, var, ArrayList)、ThrowableやDate
    • Composeを使用していないmodule、外部Libraryのclass

変更点

  • Composable関数のRecomposeの条件
    • Unstableな引数を使用していてもinstanceが同じならRecomposeされない
    • @Imutable @Stable
      • Unstableなものを、Stableとcompilerに認識させる
      • @StableはObject equals(中身が同じならRecomposeされない)
      • @ImutableはInstance equals(インスタンスが違えばRecomposeされる)
      • config fileでstableを一括指定できる(java class, パッケージ一括)
      • Object equalsはO(n)
      • instace equalsはO(1) 件数の多い複雑なlistの場合こっちの方が速い
  • lamda式の再生成の条件
    • UnStableなclassを参照していてもRecompose時に再生成されなくなった
      • lambda = @DontMemoize { viewModel.onEvent() }で再生成できる
  • Composable関数に@NonSkippableComposableをつけれる
    • 頻繁にRecomposeさせたい場合
    • 関数内で定義されているlambdaを再生成してくれない場合は@DontMemoizeで解決
  • そもそもStrongSkipModeをオフにしたい場合
composeCompiler {
   featureFlags = setOf(ComposeFeatureFlag.StrongSkipping.disabled())
}

スライド

https://speakerdeck.com/masayukisuda/kotlin-2-dot-0gayu-eruandroidkai-fa-nojin-hua?slide=2
動画
https://www.youtube.com/watch?v=aZds8ewbRkk

Discussion