20190226のKotlinに関する記事は6件です。

Kotlin用ドキュメント生成ツール「dokka」のセットアップ方法

「dokka」とは?

Kotlin用のドキュメント自動生成ツールです。
Kotlin公式が提供しています。

環境

  • OS:
  • Kotlin:1.3.21
  • Gradle:
  • dokka:0.9.17

セットアップ

appフォルダ配下の「bundle.gradle」にdokkaのプラグインと設定を追加します。

/app/build.gradle
apply plugin: 'kotlin-android'
+ apply plugin: 'org.jetbrains.dokka-android'
+ dokka {
+     outputFormat = 'html' 
+     outputDirectory = "$buildDir/javadoc"
+ }

ルート直下の「bundle.gradle」にdokkaのバージョンとクラスパスを追加します。

/build.gradle
buildscript {
    ext.kotlin_version = '1.3.21'
+     ext.dokka_version = '0.9.17'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+         classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:${dokka_version}"
    }
}

実装

ドキュメント化したいメソッドやプロパティにドキュメンテーションコメント(KDoc)を付けます。

MainActivity.kt
package com.example

import android.os.Bundle
import android.support.v7.app.AppCompatActivity

/**
 *  メインアクティビティ
 */
class MainActivity : AppCompatActivity() {

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

    /**
     *  2つの数字を足す
     *
     *  @param x 数字1
     *  @param y 数字2
     *  @return 数字1 + 数字2
     */
    fun add(x: Int, y: Int): Int {
        return x + y
    }

}

ドキュメントの生成・閲覧

Android Studioのターミナルで ./gradlew dokka を実行すると、 outputDirectory で指定したフォルダ( /app/build/javadoc/)内にドキュメントが生成されます。

「…javadoc/app/index.html」をWebブラウザで開くとドキュメントを閲覧できます。
{画像}

非常にシンプルなデザインです。
個人的にはもう少し凝ったデザインにしてもいいと思いました。。

おわりに

ここでは簡単にしか紹介していません。
dokkaの使い方についてもっと詳細に知りたい場合は参考リンクなどをご参照ください。

KDocについては以下のページが参考になります。
https://kotlinlang.org/docs/reference/kotlin-doc.html
https://qiita.com/opengl-8080/items/fe43adef48e6162e6166

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kotlin用ドキュメント生成ツール「dokka」のセットアップ&操作方法

「dokka」とは?

Kotlin用のドキュメント自動生成ツールです。
Kotlin公式が提供しています。

環境

  • OS:macOS Mojave 10.14
  • Kotlin:1.3.21
  • Gradle:5.2.1
  • dokka:0.9.17

セットアップ

appフォルダ配下の「bundle.gradle」にdokkaのプラグインと設定を追加します。

/app/build.gradle
apply plugin: 'kotlin-android'
+ apply plugin: 'org.jetbrains.dokka-android'
+ dokka {
+     outputFormat = 'html' 
+     outputDirectory = "$buildDir/javadoc"
+ }

ルート直下の「bundle.gradle」にdokkaのバージョンとクラスパスを追加します。

/build.gradle
buildscript {
    ext.kotlin_version = '1.3.21'
+     ext.dokka_version = '0.9.17'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+         classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:${dokka_version}"
    }
}

実装

ドキュメント化したいメソッドやプロパティにドキュメンテーションコメント(KDoc)を付けます。

MainActivity.kt
package com.example

import android.os.Bundle
import android.support.v7.app.AppCompatActivity

/**
 *  メインアクティビティ
 */
class MainActivity : AppCompatActivity() {

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

    /**
     *  2つの数字を足す
     *
     *  @param x 数字1
     *  @param y 数字2
     *  @return 数字1 + 数字2
     */
    fun add(x: Int, y: Int): Int {
        return x + y
    }

}

ドキュメントの生成・閲覧

Android Studioのターミナルで ./gradlew dokka を実行すると、 outputDirectory で指定したフォルダ(/app/build/javadoc/)内にドキュメントが生成されます。

「…javadoc/app/index.html」をWebブラウザで開くとドキュメントを閲覧できます。

スクリーンショット_2019-02-26_23_59_18.jpg
スクリーンショット_2019-02-27_0_01_45.jpg
スクリーンショット_2019-02-26_23_46_37.jpg

非常にシンプルなデザインです。

おわりに

ここでは簡単にしか紹介していません。
dokkaの使い方についてもっと詳細に知りたい場合は参考リンクなどをご参照ください。

KDocについては以下のページが参考になります。
https://kotlinlang.org/docs/reference/kotlin-doc.html
https://qiita.com/opengl-8080/items/fe43adef48e6162e6166

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kotlin 1.3のCoroutineのキャンセル②(協力的なキャンセル)

検証環境

この記事の内容は、以下の環境で検証しました。

  • Intellij IDEA ULTIMATE 2018.2
  • Kotlin 1.3.0
  • Gradle Projectで作成
  • GradleファイルはKotlinで記述(KotlinでDSL)

準備

詳細は下記の準備を参照してください。
https://qiita.com/naoi/items/8abf2cddfc2cb3802daa

Cancellation is cooperative

前回に引き続き、公式サイトを読み解いていきます。

今回のタイトルは日本語にすると意味がわかりません。
内容を理解すれば、理解できるかもしれません。
早速読み進めます。

Coroutine cancellation is cooperative. A coroutine code has to cooperate to be cancellable.
All the suspending functions in kotlinx.coroutines are cancellable.
They check for cancellation of coroutine and throw CancellationException when cancelled. However, if a coroutine is working in a computation and does not check for cancellation, then it cannot be cancelled, like the following example shows:

訳してみると

コルーチンはキャンセルに処理に対して協力的です。また、コルーチンのコードはキャンセルに対して、協力的でなければなりません。
kotlinx.coroutinesパッケージに存在するすべてのsuspend関数はキャンセルできます。
それらの関数はキャンセル状態かどうかを常にチェックしています。キャンセルが発生するとCancellationException例外が発生します。
しかし、コルーチンで、計算処理の最中などの場合は、キャンセルができません。

なるほど、タイトルの本質は、コルーチンに関する関数は常にキャンセルされているかチェックしているから、キャンセルに対応できている(協力できている)という意味だったんですね。

でも、キャンセル出来ない場合もあるそうで、そのサンプルコードも掲載されているので確認してみます。

fun main() = runBlocking {
    //sampleStart
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) { // computation loop, just wastes CPU
            // print a message twice a second
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
//sampleEnd
}

実行結果

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm sleeping 3 ...
I'm sleeping 4 ...
main: Now I can quit.

前回との大きな違いはrepeat関数を使わずにwhile文を使用しているため、キャンセルのチェックが行われていません。while文は確かに通常の計算処理と同じため、キャンセル出来ていない事が確認できます。

公式サイトには、最後にこんな一文が載っています。

Run it to see that it continues to print "I'm sleeping" even after cancellation until the job completes by itself after five iterations.

訳してみると

実行し見ると、コルーチンはキャンセルされているにもかかわらず、ジョブが完了するまで、表示をしているね。

確かにそのとおりです。

まとめ

このブロックで理解できたことは以下のことだと思います。

  • kotlinx.coroutinesパッケージ内の関数はキャンセルをチェックしていること
  • 上記以外はチェックしていないこと
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kotlin標準関数の習得 run, with, let, also and applyの日本語訳

自分の学習のため日本語訳しながら理解していく。

元記事はこちら

Mastering Kotlin standard functions: run, with, let, also and apply
https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84

Kotlinの標準的な機能の習得

Kotlinの標準関数のいくつかはとても似ているので、どちらを使うべきかわからない。それらの違いを明確に区別するための簡単な方法と、どちらを使用するかを選ぶ方法を紹介します。

スコープ関数

私が注目する機能は、T.runT.letT.alsoT.apply
それらをスコープ関数と呼ぶ、

スコープを説明する最も簡単な方法はrun関数です。

fun test() {
    var mood = "I am sad"

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}}

test関数の中でprintln前にmoodが再定義され別のスコープを持つことになる。

run {
    if (firstTimeView) introview else normalView
}.show()

スコープ関数の3つの属性

1.通常関数と拡張関数

withT.runを見てみると、両者の関数はかなり似ている。

with(webview.settings) {
    javaScriptEnabled = true
    databaseEnabled = true
}

webview.settings.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

しかしそれらの違いは、withが通常の関数、T.runが拡張関数である。
それぞれの問題/利点は何だろう?

webview.settingsがnullになる可能性があると想像してみよう。

// イケてない
with(webview.settings) {
    this?.javaScriptEnabled = true
    this?.databaseEnabled = true
}

// ナイス
webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

この場合は、runを使用する前にnull許容のチェックができるため、明らかにrunの拡張関数が優れている。

This 対 it 引数

runletを見ると、2つの関数は1つの点を除いて似ているが、引数を受け入れる方法が異なる。
以下は両方の機能に対する同じロジックを示している。

// runの場合
stringVariable?.run {
    println("この文字列の長さは$length")
}

// letの場合
stringVariable?.let {
    println("この文字列の長さは${it.length}")
}

T.run関数のシグネチャを確認すると、それがT.run拡張関数呼び出しとして作成されたものであることがわかりblock: T.()ます。したがって、範囲内であればすべてT、this.Inプログラミングと呼ぶthisことができ、ほとんどの場合省略することができます。したがって、上記の例$lengthでは、printlnステートメントの代わりにを使用できます${this.length}。これを引数として送ることとしてこれを呼びます。

しかしT.let関数シグネチャの場合はT.let、関数に自分自身を送信していることに気付くでしょうblock: (T)。それゆえ、これはラムダ引数がそれを送ったようなものです。それはスコープ関数の中では参照できますit。それでこれを引数として送ることと呼びます。

上記から、暗黙的であることT.runよりも優れているように思われますが、以下のようT.letにT.let機能のいくつかの微妙な利点があります。

  • T.let明確に区別使用して、外部のクラス関数/メンバ対与えられた変数機能/部材を提供し
  • this省略することができない場合、例えばitが関数のパラメータとして送られるとき、書くよりも短く、そしてthisより明確になります。
  • T.letは変換後の使用済み変数の命名が容易になります。つまり、itを他の名前に変換することができます。
stringVariable?.let {
    nonNullString ->
    println("The non null string is $nonNullString$")
}

3. 自分を返す vs 他のタイプを返す

それでは、T.letとT.also見てみましょう。内部の機能範囲を調べれば、どちらも同じです。

stringVariable?.let {
    println("The length of this String is ${it.length}")
}

// 全く同じ
stringVariable?.also {
    println("The length of this String is ${it.length}")
}

しかし彼らの微妙な違いは彼らが返すものです。 T.letは異なるタイプの値を返し、T.alsoはT自体、つまりこれを返します。

どちらも機能を連鎖させるのに役立ちます。
T.letはあなたが操作を進化させることを可能にし、T.alsoもあなたが同じ変数、T.alsoも同じ変数、つまりthisで実行させます。

val original = "abc"
// 値を進化させて次のチェーンに送る
original.let {
    println("元の文字列は ${it}") // "abc"
    it.reversed() // itを次のletに送るためのパラメータとして発展させる
}.let {
    println("逆の文字列は ${it}") // "cba"
    it.length // 他のタイプに発展させることができる
}.let {
    println("文字列の長さは ${it}")
}

// 間違い
// 同じ値がチェーンで送信されます(printlnされた答えが間違っています)
original.also {
    println("元の文字列は ${it}") // "abc"
    it.reversed() // itを進化しても使われない
}.also {
    println("逆の文字列は ${it}") // "abc"
    it.length  // itを進化しても使われない
}.also {
    println("文字列の長さは ${it}") // "abc"
}


// 元の文字列として操作する
// 同じ値がチェーンで送信されます
original.also {
    println("元の文字列は ${it}") // "abc"
}.also {
    println("逆の文字列は ${it.reversed()}") // "cba"
}.also {
    println("文字列の長さは ${it.length}") // 3
}

T.alsoは、これらを単一の機能ブロックに簡単に組み合わせることができるので、上記では意味がないように思われるかもしれません。慎重に考えて、それはいくつかの良い利点があります

  1. 同じ対象物に対して非常に明確な分離プロセスを提供することができ、すなわちより小さな機能部分を作ることができる。
  2. 使われる前に自己操作のために非常に強力になることができて、連鎖ビルダー操作を作ります。

両方が鎖を結合するとき、すなわち、一方がそれ自身を進化させ、一方がそれ自身を保持するとき、それは強力な何かになる。

// 通常アプローチ
fun makeDir(path: String): File {
    val result = File(path)
    result.mkdirs()
    return result
}

// 改善後アプローチ
fun makeDir(path: String) = path.let { File(it) }.also {it.mkdires() }

すべての属性を見る

3つの属性を見れば、関数の動作をほとんど知ることができました。 T.apply関数については、上記では説明していないので、ここで説明します。

T.apply

T.applyの3つの属性は以下の通りです

  1. 拡張関数である
  2. thisを引数とする
  3. thisを返す

それゆえ、それを使うと、想像することができ、それは、

// ノーマルアプローチ
fun createInstance(args: Bundle) :MyFragment {
    val fragment = MyFragment()
    fragment.arguments = args
    return fragment
}

// 改善アプローチ
fun createInstance(args: Bundle) = MyFragment().apply { arguments = args }

あるいは、連鎖のないオブジェクト作成を連鎖可能にすることもできます。

// ノーマルアプローチ
fun createIntent(intentData: String, intentAction:String): Intent {
    val intent = Intent()
    intent.action = intentAction
    intent.data = Uri.parse(intentData)
    return intent
}

// 改善アプローチ
fun createIntent(intentData: String, intentAction: String) {
    Intent.apply { action = intentAction }
          .apply { data = Uri.parse(intentData) }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ImageView】ScaleTypeを変更したときの内部処理

ScaleTypeと表示画像の対応を整理するために作成したサンプルアプリで、ScaleTypeFIT_STARTFIT_XYMATRIXと切り替えたときにFIT_STARTMATRIXのときの画像表示が同じになる現象が発生したので、備忘録を兼ねてその際に調査した内容をまとめました。

ScaleTypeを変更したときの内部処理

ImageView.javaのソースはこちらにあります。

まず、ScaleTypeを切り替えるためにsetScaleType()を呼び出します。
引数に指定されたScaleTypemScaleTypeに代入されます。

ImageView.java#LL.827-838
public void setScaleType(ScaleType scaleType) {
  if (scaleType == null) {
    throw new NullPointerException();
  }

  if (mScaleType != scaleType) {
    mScaleType = scaleType;

    requestLayout();
    invalidate();
  }
}

  
このmScaleTypeconfigureBounds()内で分岐条件として使われます。
以下、今回の調査に必要な条件のみを抜粋します。

mScaleType == MATRIXのとき、以下のような処理が行われます。

ImageView#LL.1253-1257
if (mMatrix.isIdentity()) {
  mDrawMatrix = null;
} else {
  mDrawMatrix = mMatrix;
}

mMatrixMatrixの参照を持つ変数です。mDrawMatrixmMatrixの参照を代入するための変数です。この2つの変数によってMatrixによる画像変換を行うか否かを制御しています。
mMatrixが単位行列(変換前後で画像の状態を変化させないMatrix)の場合、mDrawMatrixにnullを代入しています。

mScaleType == FIT_CENTER、FIT_END、FIT_STARTのとき、以下のような処理が行われます。

ImageView#LL.1302-1306
mTempSrc.set(0, 0, dwidth, dheight);
mTempDst.set(0, 0, vwidth, vheight);

mDrawMatrix = mMatrix;
mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));

ネイティブコードを呼び出していたので詳細は確認できなかったのですが、最後のsetRectToRect()によってmScaleTypeに対応したMatrixを計算しているようです。
この処理によってmDrawMatrixの参照先の内容が更新されることがEvaluate Expressionでトレースすることによって分かりました。

mScaleType == FIT_XYのとき、以下のような処理が行われます。
vwidthvheightはそれぞれImageViewの幅と高さです。

ImageView.java#LL.1244-1245
mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;

  
そして、mDrawMatrixonDraw()内で分岐条件として使われています。
mDrawMatrixがnullでないときのみMatrixによる画像変換が行われます。

ImageView.java#LL.1350-1383
@Override
protected void onDraw(Canvas canvas) {
  ...
  if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
    mDrawable.draw(canvas);
  } else {
    ...
    if (mDrawMatrix != null) {
      canvas.concat(mDrawMatrix);
    }
    mDrawable.draw(canvas);
    canvas.restoreToCount(saveCount);
  }
}

  
以上を踏まえ、改めて状況を整理します。

ScaleTypeFIT_STARTFIT_XYMATRIXと切り替えたとき、

  • FIT_START
    • ②の処理が行われるので、mMatrixの参照先のMatrixの内容はFIT_STARTに対応した値になる
    • mDrawMatrixはnullでないので、Matrixによる画像変換が行われる
  • FIT_XY
    • ③の処理が行われるので、mMatrixの参照先のMatrixの内容はFIT_STARTに対応した値のままになる
    • mDrawMatrixはnullなので、Matrixによる画像変換は行われない
  • MATRIX
    • ①の処理が行われるので、mMatrixの参照先のMatrixの内容はFIT_STARTに対応した値のままになる
    • mDrawMatrixはnullでないので、Matrixによる画像変換が行われる

となるため、FIT_STARTMATRIXのときの画像表示が同じになっていました。

最後に、この現象を回避するためにScaleTypeを変更する前にsetImageMatrix()を呼び出します。

ImageView.java#LL.871-884
public void setImageMatrix(Matrix matrix) {
  // collapse null and identity to just null
  if (matrix != null && matrix.isIdentity()) {
    matrix = null;
  }

  // don't invalidate unless we're actually changing our matrix
  if (matrix == null && !mMatrix.isIdentity() || matrix != null && !mMatrix.equals(matrix)) {
    mMatrix.set(matrix);
    configureBounds();
    invalidate();
  }
}

setImageMatrix()の引数に新しい単位行列を代入することでmMatrixが初期化されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Notificationのタイトルに色を付ける方法

こったプッシュ通知(notification)だと色付きのものをよく見るがそれをする方法
意外と調べても出てこなかったので掲載

addColorMethod.kt
private fun addColor(title: String): SpannableString {
    return SpannableString(title).apply {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            return@apply
        }
        setSpan(ForegroundColorSpan(ContextCompat.getColor(context,
                R.color.notification_job_title_text)), 0, title.length, 0)
    }
}
NotificationCompatBuilderでの利用法.kt
NotificationCompat.Builder(yourContext, "yourId")
    .setContentTitle(addColor("your notification title"))
    .set .. // 中略
    .build()

現場からは以上です

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む