カスタムコルーチンスコープの作成

kotlin.github.io

CoroutineScopeは実装することが出来る。

interface CoroutineScope

CoroutineScope概要

launchasyncといった全てのコルーチンビルダーはCoroutineScopeを拡張し、全ての要素とキャンセルを自動的に伝播するために、そのcoroutineContextを継承する。

単独のスコープインスタンスを取得する方法は、CoroutineScope()もしくはMainScope()ファクトリ関数である。 これらが必要なくなった際は、スコープのキャンセルに対して注意を取り計らう。

追加的なcontext要素は、[plus](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/plus.html)演算子を使うことで追加することが出来る。

カスタムコルーチンの作成

import kotlinx.coroutines.*
import kotlin.coroutines.*

class CustomScope : CoroutineScope {
    // ルートjob
    val job = Job() 
    override val coroutineContext: CoroutineContext get() = job // + Dispatchers.IO Dispacherを指定しなかった場合は、Dispachers.Defaultが使用される

    fun <T> delayRun(f: ()->T): Job {
        // launchメソッドが自動導出される
        return launch {
            delay(1000)
            f()
        }
    }
}
  • launchメソッドなどが自動導出される

Android KTXを

developer.android.com

Android KTXとは

Android JetpackのKotlin拡張をまとめたライブラリ。

以下のKotlinの機能を用いた拡張が用意されている

  • Extension functions
  • Extension properties
  • Lambdas
  • Named parameters
  • Parameter default values
  • Coroutines

サンプル SharedPreference

KTXを用いなかった場合

sharedPreferences
    .edit()
    .putBoolean("key", value)
    .apply()

KTXを用いた場合

sharedPreferences.edit { putBoolean("key", value) }

編集と同時にコミットも出来る

// Commit a new value sychronously
sharedpreferences.edit(commit = true) { putBoolean("key", value) }

ViewModelの生成と初期化方法

ViewModel

import androidx.lifecycle.ViewModel

class UserViewModel(name: String, age: Int, gender: Int) : ViewModel() {
    val name = name
    val age = age
    val gender = gender
}

ViewModelProviderを使う

val viewModel = ViewModelProvider(this, object : ViewModelProvider.NewInstanceFactory() {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return UserViewModel("Tanaka", 27, 1) as T
    }
}).get(UserViewModel::class.java)

Log.d("MainActivity", "${viewModel.name}, ${viewModel.age}, ${viewModel.gender}")

Android KTXを使う

Android KTXでは、Delegated propertiesを使って、ViewModelの生成と初期化、またActivityのViewModelの共有を行うことが出来る。

dependencyの追加

Core KTX

dependencies {
    implementation "androidx.core:core-ktx:1.6.0"
}

Fragment KTX

dependencies {
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha02"
}

viewModels

viewModels

val viewModel: UserViewModel by viewModels<UserViewModel>() {
    object : ViewModelProvider.NewInstanceFactory() {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return UserViewModel("Tanaka", 27, 1) as T
        }
    }
}

参照

ViewModelProvider

https://developer.android.com/reference/android/arch/lifecycle/ViewModelProvider#viewmodelprovider_2

Android KTX

https://developer.android.com/kotlin/ktx

How to instantiate ViewModel in AndroidX

https://stackoverflow.com/questions/54313453/how-to-instantiate-viewmodel-in-androidx

コルーチン Coroutin

コルーチン

使うにあたって

dependencyの追加

https://github.com/Kotlin/kotlinx.coroutines#using-in-your-projects

Gradleの場合

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0'
}

import

App.kt

import kotlinx.coroutines.*

About コルーチン in Kotlin

非同期関数

asyncに相当する関数を生成する。

//  launch内部は非同期で実行される
launch { 
    println("Hello World")
}

生成と実行

コルーチンスコープ内部で生成することが出来る

// コルーチンスコープの生成
runBlocking { // this: CoroutineScope
    // コルーチンの生成 (launchは、CoroutineScopeのメソッド)
    launch { 
    }
}

Job

CoroutineScope.launch()によって生成されるコルーチンはJobとしてその参照を返す。

fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job

カレントスレッドをブロックしないで、新しいコルーチンを起動し、Jobとしてコルーチンの参照を渡す。 このjobがキャンセルされたとき、コルーチンがキャンセルされる。

サスペンド関数

コルーチンの実行を中断する関数await に相当する関数。

suspend修飾子を関数につける。

App.kt

import kotlinx.coroutines.*

// サスペンド関数 (コルーチンを中断する)
suspend fun doWorld() {
    delay(1000L)
    println("World")
}

fun main() {
    runBlocking {
        doWorld() // 完了まで中断する
        println("Hello")
    }
}

/* 出力
World
Hello
*/

コルーチンコンテキスト

インデックスされたElementの集合インスタンスを表す。 コンテキスト同士を結合して新たなコルーチンコンテキストを作ることも出来る

open operator fun plus(
    context: CoroutineContext
): CoroutineContext
(source)
class MyActivity : Activity(), CoroutineScope {

    private val job = Job()

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

Job

Elementの一種。 コルーチンへの参照を表すコルーチンコンテキスト。

f:id:yossan2:20210627162655p:plain
Job

CoroutineDispatcher

Elementの一種。 コルーチンを実行するスレッドを表すコルーチンコンテキスト。

f:id:yossan2:20210627162722p:plain
CoroutineDispatcher

CoroutineDispatcherとして用意されている。

  • Dispatchers.Default - タスクは、適切なバックグラウンドスレッドに読み込まれるように設計されたCorountineDispatcher。
  • Dispatchers.IO - IOタスクをブロックするスレッドの共有プールに、予め読み込んでおくために設計されたCorountineDispatcher。
  • Dispatchers.Main - メインスレッド上で動作することが保証されたCorountineDispatcher。
  • Dispatchers.Unconfined

コルーチンスコープ

  • コルーチンコンテキストを持つ
  • コルーチンを起動するメソッドを持つ
    • launc
    • async
    • promise
    • ...

生成方法

両者の違いは、CorountineDispatcherが異なる。

  • CoroutineScope() - Dispatchers.Default
  • MainScope() - Dispatcher.Main

まとめ

  • コルーチンコンテキストはElementの集合を表す
  • Jobは、コルーチンへの参照を表すElementであり、コルーチンコンテキスト
  • CorutinDispatherは、タスクを実行するスレッドを表すElementであり、コルーチンコンテキスト
  • コルーチンスコープはコルーチンコンテキストを持つ

classにつけられるキーワード

classにつけられるキーワード

  • data - 値型もどき
  • sealed - 代数的データ型もどき
  • inner - Outerクラスの呼び出しが可能となる
  • enum - Javaenum
  • open - Javafinalの反対。Kotlinの全てのclassはデフォルトでfinalであるため
  • value - ?

data class

自動導出されるメソッド

プライマリコンストラクタを用いて、以下のメソッドが自動導出される。

  • equals() / hashCode() ペア - 同値比較
  • toString() - 値の文字列化
  • copy() - 値のコピー
  • componentN() - 値の分割代入

equals() / hashCode()

data classは同値比較。値が同じかどうか。

data class UserData(var name: String)

val tanaka = UserData("tanaka")
val tanaka2 = UserData("tanaka")
println(tanaka == tanaka2) // true

classは、デフォルトでは、同一比較。インスタンスが同じかどうか。

class User(var name: String)

val tanaka = User("tanaka")
val tanaka2 = User("tanaka")
println(tanaka == tanaka2) // false

toString()

data classの場合

data class UserData(var name: String)

println(UserData("tanaka")) // UserData(name=tanaka)

classの場合

class User(var name: String)

println(User("tanaka")) // Classes.User@4ca8195f

copy()

data classの代入はそのまま参照コピーされる

data class UserData(var name: String)

val tanaka = UserData("tanaka)
val tanaka2 = tanaka

// true
println(System.identityHashCode(tanaka) == System.identityHashCode(tanaka2)) 

tanaka2.name = "yoshida"
println(tanaka.name) // yoshida

copy()インスタンスを複製する。

data class UserData(var name: String)

val tanaka = UserData("tanaka)
val tanaka2 = tanaka.copy()

tanaka2.name = "yoshida"
println(tanaka.name) // tanaka

componentN()

分割代入

プライマリーコンストラクタのプロパティ数に応じて、compoennt1(), ...を自動導出する。

data class UserData(val name: String, val age: Int)
val tanaka = UserData("tanaka", 27)

// 分割代入
val (name, age) = tanaka
println("name: ${name}, age: $age")

通常classとの違い

プライマリコンストラクタは、最低限一つのプロパティを持つ必要がある。

// コンパイルエラー
// Data class must have at least one primary constructor parameter
data class User() 

プライマリコンストラクタのプロパティは、var もしくは varである必要がある。

// コンパイルエラー
// Data class primary constructor must have only property (val / var) parameters
data class User(name: String) { 
    val last_name = name
}

abstract, oepn, sealed, innerをつけることができな

// コンパイルエラー
// Modifier 'data' is incompatible with 'open
open data class User(val name: String) {}

sealed

代数的データ型の作成。

sealed class Barcode {
    // dataキーワードを付けることが出来る
    data class UPC(val p1: Int, val p2: Int, val p3: Int, val p4: Int): Barcode()
    class QRCode(a: String): Barcode()
}

var productBarcode = Barcode.UPC(8, 85909, 51226, 3)
println(productBarcode)

内部に記述しなくても良い

sealed class Barcode()
data class UPC(val p1: Int, val p2: Int, val p3: Int, val p4: Int): Barcode()
class QRCode(a: String): Barcode()

// 名前空間が取れる
var productBarcode = UPC(8, 85909, 51226, 3)
println(productBarcode)

whenによるdestructingが対応されていないのでパターンマッチングが微妙ではある。

// 型アノテーションを付ける必要がある
//val barcode: Barcode = Barcode.UPC(8, 85909, 51226, 3)
val barcode: Barcode = Barcode.QRCode("http://yossan.github.io")
// 型におけるパターンマッチングが可能
when(barcode) {
    is Barcode.UPC -> {
        if (barcode is Barcode.UPC) { // smart case 
            val (p1, p2, p3, p4) = barcode
            println("UPC($p1, $p2, $p3, $p4)")
        }
    }
    is Barcode.QRCode -> {
        if (barcode is Barcode.QRCode) { // smart case 
            val (p) = barcode
            println("QRCode($p)")
        }
    }
}

sealedは、interfaceにもつけることも可能

sealed interface Expr

inner

https://kotlinlang.org/docs/nested-classes.html

nested classは、外側のclassに基本アクセスできない

class Outer {
    private val bar: Int = 1
    class Inner {
        // Unresolved reference: bar
        fun foo() = bar
    }
}

innerキーワードを付けることでアクセスが可能となる。

class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo()

Annonymouse inner class

object式を使ったインスタンス生成は、Annonyous inner class instanceとなる。

→ 内部でOuterクラスの変数が使える。

window.addMouseListener(object : MouseAdapter() {

    override fun mouseClicked(e: MouseEvent) { ... }

    override fun mouseEntered(e: MouseEvent) { ... }
})

// This type is final, so it cannot be inherited from // class MyApp: App()

enum

https://kotlinlang.org/docs/enum-classes.html

それぞれのenum定数はobjectになる。

value

https://kotlinlang.org/docs/inline-classes.html