Kotlin1.3 まとめ

10/30日にKotlin1.3がりりーすされました。 1.2や1.3RCからの変更点等を主にJVMで動かすときに関わる範囲で適当にまとめようと思います。

変更・追加点

  • kotlinx.coroutines がexperimentalからstableに

  • capturing when subject

  • 引数なしエントリポイント main

  • contracts

  • Result<T> の実装

  • inline class の実験的なサポート

  • unsigned integer の実験的なサポート

  • Collection まわりでなんかいろいろ拡張関数追加された。

  • annotation calss をネストして宣言できるようになった。

  • @JvmStatic@JvmFieldcompanion object内で使えるようになった

  • Bool型が companion object を持つようになった。

  • 基本型のcompanion objectになんかいろいろ追加された。

適当にピックアップしてそれぞれ例を挙げて解説してみます。

引数なしエントリポイント

引数なしのmain関数がエントリポイントになるようになりました。
従来の main(args: Array<String>) は当然なくなってないです。
初心者フレンドリらしい。

fun main() {
    println("はろわ!")
}
//はろわ!

capturing when subject

when式に使う変数をwhen式の中で宣言できるようになったやつ。

when (val hoge = createRandomString()) {
    "hoge" -> println(hoge)
}

こんなかんじ
変数のスコープはwhen式の中に制限される。

Collection まわりの拡張関数

なんかいろいろ追加されてる。
SequenceorEmpty() とか Collection,Map,ListisNullOrEmpty()とかはAPIの一貫性を向上させるために用意されたらしい。

val strArray = arrayOf("aa", "bbb", "ccccccc")
strArray.isNullOrEmpty() //false
strArray.ifEmpty { arrayOf("hoge", "hoge") }

val strSequence: Sequence<String>? = strArray.asSequence()
strSequence.orEmpty()
requireNotNull(strSequence).ifEmpty { emptySequence() }

val strList: List<String>? = strArray.toList()
strList.isNullOrEmpty() // false
if (!strList.isNullOrEmpty()) {
    //contractsのおかげでスマートキャストされるようになった。
    strList.ifEmpty { listOf("hoge") }
}

Result

成功した処理の結果 T か 失敗した結果 Throwable を格納する共用体。
早速inline class で実装されてる。
1.3 m1 時点では SuccessOrFailureという名前で実装され rc-190 にて Result に名前が変わった。
自分は偶然 Resultってtypealiasを作って使っていたので I get kotonaki でした。
基本的にはrunCatching 関数を使って生成するものだと思う。

val result: Result<String>= runCatching {
    randomeFailuire() //一定確率で例外投げる関数だと思って
    "hoge"
}
result.onSuccess(::println)
    .onFailure(Throwable::printStackTrace)
fun Throwable.zero() = 0
val fold: Int = result.fold(String::length, Throwable::zero)

onSuccessT->Unitを受け取りResult<T>を返します。
成功した場合は当然それを実行。

onFailureThrowable->Unitを受け取りResult<T>を返します。

foldT->RThrowable->R を受け取り成功した場合は前者、失敗した場合は後者を実行しRを返す関数です。

多分よく使うのはこの3つだと思う。
他にもmap() とかgetOrDefault() とか便利なのいろいろ用意されてます。
ちなみにResult を返す関数を作るには今の所コンパイラ-Xallow-result-return-typeを渡す必要があります。

inline class

primary constructor で必ず一つの引数を受け取ってそれがコンパイル時にインライン展開されるかもしれない。
当然普通のクラスとは違っていろいろ制限がある。

  • primary constructorval でアノテートされたプロパティを一つだけ持つ必要がある。
  • backing field を持つ property(よく見る普通のプロパティ)は定義できない。
  • initializer block を持てない。
  • === (参照等価性)での比較は不可

他にも制限あったかもしれない。
正確には必ずコンパイル時にプロパティがインライン展開されるわけではなくて、可能な限りインスタンスの生成等を行わずに済むように最適化しようとする感じです。
その最適化を行うために上記の制限があるみたい。

fun main() {
    val age = Age(14)
    val ageValue = age.value
    println(age)
    println(ageValue)
    println(age.birthYear())
}

inline class Age(val value: Int) {
    fun birthYear(): LocalDateTime {
        //todo 雑
        val now = LocalDateTime.now()
        return LocalDateTime.of(
            now.year - value, now.month,
            now.dayOfMonth, now.hour, now.minute
        )
    }
}

こんな感じのコードはコンパイルされると

fun main() {
    val age: Int = Age.constructor_impl(14)
    val var2: Age = Age.box_impl(age)
    println(var2)
    println(age)
    val var3:LocalDateTime = Age.birthYear_impl(age)
    println(var3)
}

こんな感じになります。
クラスの実装は割愛です。

inline class を使用するにはコンパイラに引数として-XXLanguage:+InlineClasses を渡す必要があります。

kotlinx.coroutines

これは例とか出してるとやたら長くなりそうなので 1.0.0 での変更点を自分が実際に使った際に気づいたところを軽くまとめようと思います。

  • グローバルでsuspend propertycoroutineContext が消えてCoroutineScopeのプロパティになった。

  • launch() 等の関数が GlobalScopeオブジェクトの拡張関数になった。

  • launch()async から parent: Job? の引数が消えた。

  • async(coroutineContext + CommonPool){}とかしてたと思うけどCommonPool消えので ForkJoinPool.commonPool().asCoroutineDispatcher()とかする。

  • launch(UI){ } とかしてたと思うけどUI 消えた。Dispatchers.Mainに置き換える

launchasync くらいしか使わないから気づいたこと少なすぎた…
んぐぉぉぉぉぉ……

contracts

コンパイラになんかいろいろ教えてあげるやつ。
これのおかげで特定のメソッド呼び出しの結果が true ならレシーバがnon-null型にスマートキャストされたりとか、ラムダ式が一度だけ同期的に呼び出されることを保証してその中で外のスコープの変数を初期化できるようになったりとかする。
早速Array<T>?.isNullOrEmpty()とか requireNotNull() とか T.run() とかに使われている。

使い方の例として一度だけ呼ばれるラムダ式contracts を挙げ…ようとおもったけどなんかビルド通らないな…んんん~~また今度。
1.3-m2 が出て即試したときは普通にできたけどBackend Internal error吐きまくってなんかビルドできないので寝ます。

標準ライブラリのrequire関数とか使う分には問題なく動きます。
以上

なんか変なこと書いてたりしてたら指摘していただければ嬉しいです。

参考

Kotlin 1.3リリース – コルーチン、Kotlin/Nativeベータ | JetBrains ブログ

What's New in Kotlin 1.3 - Kotlin Programming Language

非同期処理とFragment

なんか非同期処理してそれが終わったらFragmentを生成してFragmentManagerに追加するみたいな処理するときってあれですよね。
onSaveInstanceStateの後にやってしまうと怒られますよね。
それ回避用のユーティリティークラス作ってみたのでメモ書きです。
Android Architecture Componentslifecycleを使用します。

import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.OnLifecycleEvent

interface IOnActiveRunner {
    fun setOwner(owner: LifecycleOwner)
    fun runOnActive(handle: () -> Unit)
}

class OnActiveRunner : IOnActiveRunner {
    private val mObserver = OnActiveLifeCycleObserver()
    private var mIsOwnerInitialized = false

    override fun setOwner(owner: LifecycleOwner) {
        if (!mIsOwnerInitialized)
            owner.lifecycle.addObserver(mObserver)
        else throw IllegalStateException()
        mIsOwnerInitialized = true
    }

    override fun runOnActive(handle: () -> Unit) {
        if (!mIsOwnerInitialized) throw IllegalStateException()
        mObserver.run(handle)
    }

    class OnActiveLifeCycleObserver : LifecycleObserver {

        private var isSafe = true
        private val tasks = ArrayList<() -> Unit>()

        fun run(task: () -> Unit) {
            if (isSafe) {
                task()
            } else {
                tasks.add(task)
            }
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        fun onPause() {
            isSafe = false
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        fun onResume() {
            isSafe = true
            tasks.forEach { it() }
            tasks.clear()
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        fun onDestroy() {
            isSafe = false
        }
    }
}

単純にonPause()が呼ばれたら与えられた関数オブジェクトをリザーブしてonResume()で実行するってだけです。

インターフェースを用意したのは移譲を使ってActivity等で楽に使えるようにするためです。

使うときは…

class HogeActivity : AppCompatActivity(),
        IOnActiveRunner by OnActiveRunner() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //runOnActiveを呼ぶ前にsetOwnerしておく
        setOwner(this)

        //例としてkotlinのcroutine使います
        launch(UI) {
            //async/awaitつかったりとか
            runOnActive { 
                //何かしらの処理
            }
        }
    }
}

みたいな感じで使います。 非同期処理のコールバックでonPause後に呼ばれたくないような処理を行なう時に使う感じです。
最たるものはFragmentTransaction周りの処理だと思います。
他にもonPause後に呼ばれたくないような処理っていくらでもあると思うので使い所は結構多いのでは。

いや、こんなの用意しなくてもそれできるぜって情報があれば教えていただければ幸いです。
commitAllowingStateLossは無しの方向で

Androidでカメラアプリとかから画像取得

なんかやたらと intent.extras.get("data") で画像が取れるってブログが引っかかるので
ちゃんと撮影した画像のサイズのままのBitmapとFileが取れる方法をメモっとこうかと思います。

 

まずこんな関数を用意します

/**
 * @param ctx 適当なコンテキスト
 * @param directory ディレクトリの名前 例 "hoge/fuga"
 * @param fileName 拡張子抜きのファイルの名前 例 "piyo"
 * @param mimeType mimeType 例 "image/png"
 * @return 確保したUriとそのパスのPair
 */
fun reserveContentUri(ctx: Context,
                      directory: String,
                      fileName: String,
                      mimeType: String): Pair<Uri, String> {

    val extension = MimeTypeMap.getSingleton()
            .getExtensionFromMimeType(mimeType)//mimeTypeから拡張子を得る
    val name = fileName + ".$extension"
    val directoryNames = directory.split("/")
    var directoryPath = Environment.getExternalStorageDirectory().absolutePath
    //ディレクトリを作るのと存在確認
    directoryNames.forEach { directoryName ->
        directoryPath += "/$directoryName"
        val file = File(directoryPath)
        if (!file.exists() && !file.mkdir()) throw IOException("failed directory creation")
    }
    
    //contentResolverにinsert
    val path = "${File(directoryPath).absolutePath}/$name"
    val values = ContentValues().apply {
        put(MediaStore.Images.Media.TITLE, name)
        put(MediaStore.Images.Media.DISPLAY_NAME, name)
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
        put(MediaStore.Images.Media.DATA, path)
    }
    return ctx.contentResolver
            .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) to path
}


上記のコードだと問題がありました。
これ、カメラアプリの起動キャンセルしたり失敗したるすると余計なものがcontentResolverにinsertされちゃいます。

下記に修正版を

fun createSaveUri(directory: String, fileName: String, mimeType: String): Pair<Uri, String> {
    val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
    val name = fileName + ".$extension"
    val directoryNames = directory.split("/")
    var directoryPath = Environment.getExternalStorageDirectory().absolutePath
    directoryNames.forEach { directoryName ->
        directoryPath += "/$directoryName"
        val file = File(directoryPath)
        if (!file.exists() && !file.mkdir()) throw IOException("directory creation failure")
    }
    val path = "${File(directoryPath).absolutePath}/$name"
    return Uri.fromFile(File(path)) to path
}

この関数で保存先を確保しておきます。

そしたらAndroidManifest.xmlには

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

のpermissionを追加します。

そしたらこんな感じで使う

class HogeActivity : AppCompatActivity() {

    private var mPath: String = ""
    private var mUri: Uri = Uri.EMPTY

    companion object {
        private val REQUEST_CODE_CAPTCHA = 100
        private val SAVED_LATEST_CAPTCHA_IMAGE_PATH = "SAVED_LATEST_CAPTCHA_IMAGE_PATH"
        private val SAVED_LATEST_CAPTCHA_IMAGE_URI = "SAVED_LATEST_CAPTCHA_IMAGE_URI"
    }

    override fun onSaveInstanceState(outState: Bundle) {
        outState.putString(SAVED_LATEST_CAPTCHA_IMAGE_PATH, mPath)
        outState.putParcelable(SAVED_LATEST_CAPTCHA_IMAGE_URI, mUri)
        super.onSaveInstanceState(outState)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        mPath = savedInstanceState.getString(SAVED_LATEST_CAPTCHA_IMAGE_PATH)
        mUri = savedInstanceState.getParcelable(SAVED_LATEST_CAPTCHA_IMAGE_URI)
    }

    //このメソッドを適当に呼ぶ
    private fun executeCaptchaApp() {
        //val (uri, path) = reserveContentUri(ctx, "numetter/captcha", "${System.currentTimeMillis()}", "image/jpeg")
        val (uri, path) = createSaveUri("numetter/captcha", "${System.currentTimeMillis()}", "image/jpeg")
        mPath = path
        mUri = uri
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
            putExtra(MediaStore.EXTRA_OUTPUT, uri)
        }
        startActivityForResult(intent, REQUEST_CODE_CAPTCHA)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode != Activity.RESULT_OK) return
        when (requestCode) {
            REQUEST_CODE_CAPTCHA -> {
                val bitmap = BitmapFactory.decodeFile(mPath)//撮影した画像のBitmap
                val imageFile = File(mPath)//撮影した画像のFile
                val imageView = findViewById<ImageView>(R.id.image)
                imageView.setImageURI(mUri)//Uriを元に画像をセット
            }
        }
    }
}

取得された画像のサイズとか考慮してなかったり、パーミションのチェックと要求をしていなかったり雑ではありますが 大方こんな感じのコードでカメラアプリ等で撮影された画像を取得できます。

AndroidN以降はFileProviderを使用してFileからUriを取得する必要があります。

 

Android Architecture Components + Kotlin Croutine

Androidでasync awaitする時のライフサイクルとのバインド 初ブログで慣れませんが頑張ります。

ごめんなさいですが、ここではAndroid Architecture Componentsについての詳しい解説はしないです…
Android Arch(以下略)については公式のドキュメントを見ると良いと思います。
Android Architecture Components

例としてここでは画像のダウンロードと表示を行なうようなプログラムを書いていきます。
(画像のダウンロード処理の実装は省きます)
前提として画像はActivity起動時の一度しかダウンロードせず、さらにそれはキャッシュされるという条件で作っていきます。


Android Arch(以下略)のViewModelとLiveDataを使用しているならば、以下のようにすることでそれは実装できます。

//とりあえずこんな感じの拡張関数を用意
//簡単にLiveDataをmapできるようにして(Listのmapとかと大体同じ感じ)
fun <T, R> LiveData<T>.map(func: (T) -> R): LiveData<R> = Transformations.map(this, func)
fun <T> MutableLiveData<T>.mediate() = map { it }

画像を表示したいActivityで使うViewModelを定義

//ImageRepositoryという画像のDLとキャッシュを行なうためのクラスが外からDIされるということにします
class HogeViewModel @Inject constructor(private val mImageRepository: ImageRepository)
    : ViewModel() {    
    //これの値をViewModel内で弄る
    private val mImageLiveData = MutableLiveData<Bitmap>()

    //mImageLiveDataの変更を仲介して伝播する為のLiveDataを外に公開
    val imageLiveData = mImageLiveData.mediate()
    
    //画像のダウンロードを開始して結果をimageLiveDataまで伝えるメソッド
    fun loadImage(url: String) {
         AsyncTask.execute {
            val bitmap = mImageRepository.downloadOrGet(url)//画像をDLしてキャッシュする処理
            mImageLiveData.postValue(bitmap)//非同期スレッドから値を変更するときはpostValue
        }
    }
}

画像を表示したいActivity

class HogeActivity : AppCompatActivity() {

    //ViewModelに何かしらをDIしたい場合このコードでは無理ですが、例なのであしからず。
    //生成されたActivityのライフサイクルとバインドされたViewModelが取ってこれます。
    //つまり画面回転等でActivityが再生成されたとしても同一のViewModelを持ってきます。
    val model by lazy { ViewModelProviders.of(this).get(HogeViewModel::class.java) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hoge)

        val imageView = findViewById<ImageView>(R.id.image)
        model.imageLiveData.observe(this, Observer {
            imageView.setImageBitmap(it)
        })

        //最後の変更がobserveした時に渡ってくるのでloadImageするのは一回でおk
        if (savedInstanceState == null)
            model.loadImage("hogehogeImageUrl")
    }
}

雑な実装でエラー処理だとか色々と省いてますが、ざっとこんな感じにすれば非同期的に画像をダウンロードしてそれを表示することができるとおもいます。
(適当に書いたのでできなかったらごめんなさい。)

しかし、ModelViewには「外に公開する用のLiveData」と「公開せずに値の変更を行なうためのLiveData」、 Activityでは「observe」と「画像のダウンロードを開始するためのメソッド呼び出し」と画像一回ダウンロードして表示するためだけに少々大仰になってしまいました。
更に画像をダウンロードするために使用している「ImageRepositoryのdownloadOrGet」メソッドで画像のキャッシュも行っているとなればLiveDataを使用する必要性は薄いです。
(LiveDataは最後の変更をobserve時に渡してきます。これにより結果的に画面回転時の復帰が可能です。)

そこでKotlinのCoroutineを用いてできるだけ簡潔になるようにしてみます。


class HogeViewModel @Inject constructor(private val mImageRepository: IImageRepository)
    : ViewModel() {
    //Deferred<Bitmap>が返ります。
    //これをActivity側でawait()します。
    fun loadImage(url: String) = async { mImageRepository.downloadOrGet(url) }
}
class HogeActivity : AppCompatActivity() {

    val model by lazy { ViewModelProviders.of(this).get(HogeViewModel::class.java) }

    private var mLoadImageJob: Job? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hoge)
        val imageView = findViewById<ImageView>(R.id.image)
        
        //Jobを保存
        mLoadImageJob = launch(UI) {
            val image = model.loadImage("hogehogeImageUrl").await()
            imageView.setImageBitmap(image)
        }
    }

    override fun onDestroy() {
        mLoadImageJob?.cancel()//Activityが死んだら処理をキャンセル
        super.onDestroy()
    }
}

簡潔になりましたが、LiveDataを用いていたときと違いって、onDestroy()でJobの中断処理をしないといけなくなったので何やら辛い感じになってしまいました。
この例では画像一枚を落とすだけなのでこれで済んでいますが、複数のJobを扱うようになると、Jobを生成する度にListに加え、Jobが終わればListから外す、等の処理を書かなければならなくなりますし大変面倒ですし、書き忘れ等があればとても悲しいです。

ですが、幸いAndroid Arch(以下略)には、ライフサイクルを扱うためのモジュールがあるので、これを使えばなんとかなりそうです。 試してみましょう。


Jobとライフサイクルをバインドする関数

//与えられたLifecycleOwnerのライフサイクルのonDestroyで自動的にキャンセルされるJobを作る
fun bindLaunch(owner: LifecycleOwner, start: CoroutineStart = CoroutineStart.DEFAULT,
               block: suspend CoroutineScope.() -> Unit) = launch(UI, start, block).apply {
    val observer = createLifecycleObserver(this)
    val lifecycle = owner.lifecycle
    lifecycle.addObserver(observer)
    //ジョブが終わればLifecycleOwnerからObserverをremove
    invokeOnCompletion { lifecycle.removeObserver(observer) }
}

//与えられたjobを元にLifecycleObserverを生成
private fun createLifecycleObserver(job: Job) = object : LifecycleObserver {
    val mRef = WeakReference<Job>(job)
    
    //onDestroyにバインドされるメソッド
    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {
        val j = mRef.get() ?: return
        if (!j.isCompleted)  job.cancel()//Jobをキャンセル
    }
}

LifecycleOwnerからニョキッと生やす

//上で定義したbindLaunchのLifecycleOwnerの拡張関数版を定義
//AppCompatActivityはLifecycleOwnerを継承しているのでいい感じに呼び出せます。
fun LifecycleOwner.bindLaunch(start: CoroutineStart = CoroutineStart.DEFAULT,
                              block: suspend CoroutineScope.() -> Unit)
        = bindLaunch(this, start, block)

こんな感じで呼び出すだけでLifecycleOwnerとJobをバインドしてくれる関数を定義してやれば……

class HogeActivity : AppCompatActivity() {
    val model by lazy { ViewModelProviders.of(this).get(HogeViewModel::class.java) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hoge)
        val imageView = findViewById<ImageView>(R.id.image)
         bindLaunch {
            val image = model.loadImage("hogehogeImageUrl").await()
            imageView.setImageBitmap(image)
        }
    }
}

ライフサイクルについてあまり考えずにasync/awaitをActivity上で使えるようになりました。

ただこれだけだとエラー処理がしづらいところがあるので、「非同期処理の結果と例外が発生すればその例外情報を保持するようなクラスを作ってそのクラスにラッピングして返す」というような工夫が必要かもです。
(なにかこれ以外でいい方法があれば教えてほしいです。)

と、こんなかんじでAndroid Arch(以下略)とkotlin Coroutineを組み合わせてAndroidのライフサイクルと仲良くする方法を書きましたが。 この中で「ここをこうすればもっと良い」や「ここがおかしい」、「別にこんなことしなくても、もっといい方法がほかにあるよ」っていうのがあれば教えていただければ幸いです。 ここの日本語がおかしいとかの添削も大歓迎です。(驚くべきことに僕は日本人です)

以下、Jobとライフサイクルのバインドを行なう関数のあれのGistです。
”_BindLaunch.kt”

これがブログですか

お題「コンビニ」

 

なんとなくブログ始めました。

レイアウトとかよくわからんです。頑張ります。

手始めにお題スロットがあったので回してみました。お題は「コンビニ」です。

コンビニ、すごいですよね。

通販の決済できたり光熱費払ったりできるし、なんならパンツとシャツも買える。

近所のコンビニの店員さんもたまにチキンくれるのでコンビニがあれば生きていける気がします。

 

しかし、僕はコンビニに入ると高確率でなんとかPlayカードとかいうのと引き換えに諭吉が消えてそれをスマホで無に還してしまうのでコンビニが近くにあると生きていけなくる気がします。最近は崩壊3rdというゲームで諭吉を無に還したので…しばらくもやしで生きていきます。

 

コンビニについて深い造詣があるわけじゃないので全く話が広がらない…

 

ブログが続くとすれば主にAndroidアプリ開発とかのブログになりそうです。

よろしくおねがいします。