📱
[Android]Kotlin2.0
droidkaigiでとてもタメになるKotlin2.0の解説があったので、すぐ見返せるようにメモ
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() }で再生成できる
- UnStableなclassを参照していてもRecompose時に再生成されなくなった
- Composable関数に@NonSkippableComposableをつけれる
- 頻繁にRecomposeさせたい場合
- 関数内で定義されているlambdaを再生成してくれない場合は@DontMemoizeで解決
- そもそもStrongSkipModeをオフにしたい場合
composeCompiler {
featureFlags = setOf(ComposeFeatureFlag.StrongSkipping.disabled())
}
スライド
動画
Discussion