20190228のKotlinに関する記事は9件です。

[Flutter/Android] Kotlin や C/C++ をデバッグする方法

Flutter でも Kotlin や C/C++ 書くことはあると思いますが、そのデバッグ方法が分かりにくかったのでメモ。

結論を言うと、Using OEM debuggers に書いてある。
なんかうまく検索でこの記事にたどり着けなくて無駄に時間を消費してしまった。

手順

iOS/Swift/C は簡単にできますが、Android/Kotlin/C はこんな感じ。

1. アプリを起動する

VSCode や Android Studio から起動する

2. android ディレクトリのみを Android Studio で開く

image.png

3. デバッガをアタッチする

image.png
Auto のまま、対象のデバイス & アプリ を指定する
image.png

4. いえーい!

C/C++ のコードにブレークポイントを打つと、ちゃんと止まってくれる。
image.png

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

[Flutter/Android] Kotlin と C/C++ をデバッグする方法

Flutter でも Kotlin や C/C++ 書くことはもちろんありますが、そのデバッグ方法が分かりにくかったのでメモ。

結論を言うと、Using OEM debuggers に書いてある。
なんかうまく検索でこの記事にたどり着けなくて時間を浪費してしまった。

手順

iOS/Swift/C は簡単にできますが、Android/Kotlin/C はこんな感じ。

1. アプリを起動する

VSCode や Android Studio から起動する

2. android ディレクトリのみを Android Studio で開く

image.png

3. デバッガをアタッチする

アプリが起動して画面が表示されたのを確認したら、アタッチする
image.png
Auto のまま、対象のデバイス & アプリ を指定する
image.png

4. いえーい!

C/C++ のコードにブレークポイントを打つと、ちゃんと止まってくれる。
image.png

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

Ktlintを使って特定のディレクトリにだけフォーマッターをかける

./gradlew ktlintFormat

の後に

git diff --name-only  | grep -v 特定ディレクトリまでのパス | xargs git checkout
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Kotlin】Listの中身をコピーする

Kotlinのdata classにはcopy()というメソッドがあります。
copy()はオブジェクトの中身をコピーして新たなオブジェクトを生成します。

data class User(val name: String, val age: Int)

val user1 = User("test", 20)
val user2 = user1.copy()

println(user1 == user2)  // => true(中身は同じ)
println(user1 === user2) // => false(参照は異なる)



同じようなことをListで実現したい場面があったのですが、Listにはcopy()のような処理を行えるメソッドは存在しませんでした。
※2019/3/1追記
@sdkeiさんより、toList()でも同様のことが実現できることを教えていただきました。

ただ、代わりにmap()を使うことによって実現できました。

val list1 = listOf(1, 2, 3, 4, 5)
val list2 = list1.map { it }

println(user1 == user2)  // => true
println(user1 === user2) // => false



拡張関数として宣言しておけば、data classと同じような感覚でListの中身をコピーして新たなオブジェクトを生成できるようになります。

fun <T> List<T>.copy() = this.map { it }
val list1 = listOf(1, 2, 3, 4, 5)
val list2 = list1.copy()

println(user1 == user2)  // => true
println(user1 === user2) // => false
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

coroutineとViewModelを使ってDialogFragmentを実装する

https://qiita.com/FKbelm/items/f85949dff7a3c0e7781d で試した内容の改善版です。

上の記事では、メモリリークを引き起こしていたので、ViewModelの力を使って再実装を目指しました。

ViewModelの作成

MainActivityViewModel.kt
class MainActivityViewModel() : ViewModel() {

    val hogeDialogEvent = MutableLiveData<Unit>()

    private val _hogeDialogChannel = Channel<Boolean>()
    val hogeDialogChannel = _hogeDialogChannel as SendChannel<Boolean>

    private suspend fun showHogeDialog(): Boolean {
        // ダイアログを表示
        hogeDialogEvent.postValue(Unit)

        // suspendで結果を取得
        // Channelに値が入るまで待つ
        val result = _hogeDialogChannel.receive()

        // 結果からなんかやって返す
        return TODO(result)
    }
}

はActivityにダイアログを開くよう通知するhogeDialogEventに通知を出して、_hogeDialogChannel.receive()で結果が入ってくるのを待ちます。

Activityの作成

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val viewModel by lazy {
        ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        viewModel.hogeDialogChannel.observe(this, Observer {
            // ダイアログが存在しないときに開く処理
            if (supportFragmentManager.findFragmentByTag(TAG_HOGE) == null) {
                supportFragmentManager
                    .beginTransaction()
                    .add(AlertDialogFragment(), TAG_HOGE)
                    .commitAllowingStateLoss()
            }
        })
    }
}

ActivityはDialogFragmentを開く処理のみを担っています。

DialogFragmentの作成

ChannelDialogFragment.kt
// 大元になるDialogFragment
abstract class ChannelDialogFragment<T> : DialogFragment() {
    protected abstract val channel: SendChannel<T>?
}

SendChannel<T>はcoroutinesのChannel<T>でのうち、送信機能のみのインターフェイスです。
ここから結果を送信してあげます。

AlertDialogFragment.kt
// 実際に使うDialogFragment
class AlertDialogFragment : ChannelDialogFragment<AlertDialogFragmentResult>() {

    // ActivityのViewModelを取得し、そこからchannelを取得する
    override val channel by lazy {
        activity?.let {
            ViewModelProviders.of(it).get(MainActivityViewModel::class.java).hogeDialogChannel
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val context = this.context ?: return super.onCreateDialog(savedInstanceState)

        return AlertDialog.Builder(context).apply {
            setTitle(R.string.hoge_title)
            setMessage(R.string.hoge_message)

            setPositiveButton(R.string.hoge_positive) { _, _ ->
                channel?.offer(true) // ViewModelのreceiveが受け取る
            }
            setNegativeButton(R.string.hoge_negative) { _, _ ->
                channel?.offer(false) // ViewModelのreceiveが受け取る
            }
        }.create()
    }
}

ChannelをViewModelに持たせることでActivityの破棄,再生成後にもChannelを容易に再取得できるようになりました。

Channelを直接公開せずにViewModelにインターフェイスを実装させることでも実現できそうですが、複数のDialogFragmentを持ちたいといったことがあった場合はChannelを直接扱う方が楽かなと思いました。

ViewModelの力は大きいですね。

参考

Coroutine時代のDialogFragment
https://qiita.com/idaisuke/items/b4f3c2e0a872544b97d0

Prism 5とReactiveProperty
https://blog.okazuki.jp/entry/2014/05/09/083315

Android Architecture ComponentsのViewModelとDialogFragment
http://sys1yagi.hatenablog.com/entry/2017/08/24/125416

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

coroutineとViewModelを使ってDialogFragmentを実装する

https://qiita.com/KyoPeeee/items/f85949dff7a3c0e7781d で試した内容の改善版です。

上の記事では、メモリリークを引き起こしていたので、ViewModelの力を使って再実装を目指しました。

ViewModelの作成

MainActivityViewModel.kt
class MainActivityViewModel() : ViewModel() {

    val hogeDialogEvent = MutableLiveData<Unit>()

    private val _hogeDialogChannel = Channel<Boolean>()
    val hogeDialogChannel = _hogeDialogChannel as SendChannel<Boolean>

    private suspend fun showHogeDialog(): Boolean {
        // ダイアログを表示
        hogeDialogEvent.postValue(Unit)

        // suspendで結果を取得
        // Channelに値が入るまで待つ
        val result = _hogeDialogChannel.receive()

        // 結果からなんかやって返す
        return TODO(result)
    }
}

はActivityにダイアログを開くよう通知するhogeDialogEventに通知を出して、_hogeDialogChannel.receive()で結果が入ってくるのを待ちます。

Activityの作成

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val viewModel by lazy {
        ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        viewModel.hogeDialogChannel.observe(this, Observer {
            // ダイアログが存在しないときに開く処理
            if (supportFragmentManager.findFragmentByTag(TAG_HOGE) == null) {
                supportFragmentManager
                    .beginTransaction()
                    .add(AlertDialogFragment(), TAG_HOGE)
                    .commitAllowingStateLoss()
            }
        })
    }
}

ActivityはDialogFragmentを開く処理のみを担っています。

DialogFragmentの作成

ChannelDialogFragment.kt
// 大元になるDialogFragment
abstract class ChannelDialogFragment<T> : DialogFragment() {
    protected abstract val channel: SendChannel<T>?
}

SendChannel<T>はcoroutinesのChannel<T>でのうち、送信機能のみのインターフェイスです。
ここから結果を送信してあげます。

AlertDialogFragment.kt
// 実際に使うDialogFragment
class AlertDialogFragment : ChannelDialogFragment<AlertDialogFragmentResult>() {

    // ActivityのViewModelを取得し、そこからchannelを取得する
    override val channel by lazy {
        activity?.let {
            ViewModelProviders.of(it).get(MainActivityViewModel::class.java).hogeDialogChannel
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val context = this.context ?: return super.onCreateDialog(savedInstanceState)

        return AlertDialog.Builder(context).apply {
            setTitle(R.string.hoge_title)
            setMessage(R.string.hoge_message)

            setPositiveButton(R.string.hoge_positive) { _, _ ->
                channel?.offer(true) // ViewModelのreceiveが受け取る
            }
            setNegativeButton(R.string.hoge_negative) { _, _ ->
                channel?.offer(false) // ViewModelのreceiveが受け取る
            }
        }.create()
    }
}

ChannelをViewModelに持たせることでActivityの破棄,再生成後にもChannelを容易に再取得できるようになりました。

Channelを直接公開せずにViewModelにインターフェイスを実装させることでも実現できそうですが、複数のDialogFragmentを持ちたいといったことがあった場合はChannelを直接扱う方が楽かなと思いました。

ViewModelの力は大きいですね。

参考

Coroutine時代のDialogFragment
https://qiita.com/idaisuke/items/b4f3c2e0a872544b97d0

Prism 5とReactiveProperty
https://blog.okazuki.jp/entry/2014/05/09/083315

Android Architecture ComponentsのViewModelとDialogFragment
http://sys1yagi.hatenablog.com/entry/2017/08/24/125416

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

Kotlinでアルゴリズムの問題を解く際のTips集

大学でアルゴリズムとデータ構造という科目があり、課題がAizu Online Judgeで出されていたのでKotlinで解きました。その際に得た知見をまとめてみようと思います。
当方は競プロ勢ではなくKotlinでコンテストに出たこともないので、記事の内容も非効率なものかもしれません。コメントなどで遠慮なくご指摘ください。

はじめに基本的な入出力を紹介し、高速化編でより早い入出力の例を紹介しています。後日他の項目も追加するかもしれません。

入力編

基本

Kotlinでは、JavaのScannerを使うことができます。

import java.util.*

fun main() {
    val scanner = Scanner(System.`in`)
    val n = scanner.nextInt()
}

しかし、Kotlinで標準で用意されているreadLine()を使ったほうがいいでしょう。

val n = readLine()!!.toInt()

readLine()は標準入力から一行読み込み、String?(StringのNull許容型)を返します。
StringにはtoInt()toDouble()などのメソッドが用意されているので、それを使い目的の型に変換します。
readLine()の戻り値がString?ですが、今回はAOJやAtCoderなどでアルゴリズムの問題を解く状況を想定しているのでreadLine()!!.toInt()でreadLine()のString?Stringに強制アンラップしています。個人的には以下の書き方が好みですが、今回の記事では上の強制アンラップを使うやり方で解説していこうと思います。

val n = readLine()?.toInt() ?: 0

KotlinのNull許容型まわりの詳しい解説は【Null安全】Kotlin Java比較メモ 正しいNull安全の使い方などを参考にしてください。

リスト

値のまとまりを入力して何かをするというアルゴリズムの問題も多いと思います。いくつかのパターンを挙げて解説していきます。

1. 一行に要素が空白区切りで渡される場合

例えば以下の様な入力があった場合です。

5 // 要素数
2 6 1 4 3 // 要素のまとまりが空白区切りで入力される

C言語などを使う場合、一旦要素数を読み込みその分for文を回して配列に値を入れていくというパターンが多いと思いますが、KotlinにはStringクラスにsplitという区切り文字を指定して文字列を分割する便利なメソッドが用意されているため、2行目を読み込むだけでリストを作成することができます。

readLine() // 要素数を取得する必要はないため変数に代入しない
val list = readLine()!!.split(" ").map(String::toInt)

splitで半角スペースを区切り文字に指定しています。splitList<String>を返すため、map(String::toInt)List<Int>に変換しています。
また、入力が1行で先頭に要素数を入力しその後空白区切りで要素を入力するというパターンの場合、以下のようにして書くことができます。

5 2 6 1 4 3
// val list = readLine()!!.split(" ").let { it.subList(1, it.size) }.map(String::toInt)
val list = readLine()!!.split(" ").drop(1).map(String::toInt)

letというスコープ関数内で元のリストの先頭を取り除いたサブリストを生成しています。
let { it.subList(1, it.size) }run { subList(1, size) }にしても同じことができます。また、map以降の操作もlet(またはrun)のブロック内に記述しても同じように動作します。このあたりはお好みでどうぞ。

コメントにてsikaniさんにご指摘を頂きました。dropを使うほうが簡潔でわかりやすくいいと思います。

2. 要素数nを入力し、続くn行に要素を入力していく場合

例えば以下の様な入力があった場合です。

5 // 要素数n 以下にn行入力が続く
2
6
1
4
3

C言語などの場合1と同じく要素数を読み込みその分for文を回して配列に値を入れていくというパターンになるかと思いますが、KotlinのListやArrayは以下のようにして生成と値の代入を同時に行うことができます。

val n = readLine()!!.toInt()
val list = List(n) {
    readLine()!!.toInt()
} // 2 6 1 4 3

上の例ではListが生成されます。Listのクロージャの最後の行の値がリストに入るので、クロージャ内で一旦入力を変数に格納し、それをもとに別のクラスのインスタンスを生成しそれのリストにすることもできます。僕が実際にある問題を解いたときのコードを載せます。

val nodeList = List(readLine()?.toInt() ?: 0) {
    val input = readLine()?.split(" ".toRegex())?.map(String::toInt) ?: listOf()
    Node(input[0], input.subList(2, 2 + input[1]))
}

この例では僕が独自に定義したNodeクラスのインスタンスを生成しそれをリストの要素に入れています。

出力編

基本

print("Foo")
println("Bar")
println("FooBar")
// 出力:
// FooBar
// FooBar

Kotlinの出力には改行なしのprintと改行ありのprintlnがあります。
使い方はJavaのSystem.out.print()System.out.println()と同じです。(内部でJavaのそれを呼び出している)
String以外のオブジェクトを入れた場合自動的にtoString()されて出力されます。つまり、独自に定義したクラスでもtoString()をオーバーライドして望む形で返すようにすればそのように出力されます。

fun main() {
    val hoge = Hoge("hoge", 100)
    println(hoge) // hoge, 100
}

class Hoge(val str: String, val num: Int) {
    override fun toString() = "$str, $num"
}

上の例でも使いましたが、Kotlinでの文字列結合はJavaのような+で結合する方法に加え、String Templateという方法で書くこともできます。

val a = 2
val b = 3
println("$a + $b = ${a + b}") // 2 + 3 = 5

IntDoubleなどで桁数指定をしたいときは、Stringに用意されているメソッドformatを使います。

val pi = 3.14159265
println("%.2f".format(pi)) // 3.14

リスト

Kotlinに限らず、JVM言語でアルゴリズムの問題を解く上での一つの壁として、出力が遅いということがあります。
リストを出力する際には、ループで回して1要素ずつ出力するのではなく、事前にリストを結合して1回の出力で終わるようにしましょう。
KotlinのListなどが継承しているIterableインターフェースや、各種Arrayには、joinToStringというメソッドが用意されています。

fun <T> Iterable<T>.joinToString(
    separator: CharSequence = ", ", 
    prefix: CharSequence = "", 
    postfix: CharSequence = "", 
    limit: Int = -1, 
    truncated: CharSequence = "...", 
    transform: (T) -> CharSequence = null
): String
引数 説明 デフォルト値
separator 区切り文字 ,
prefix 先頭に設定する文字列 ""
postfix 末尾に設定する文字列 ""
limit 連結する要素の最大値 -1
truncated 省略された要素の表記 ...
transform 各要素を任意の値に変換する処理 null

よく使うのがseparatorです。これに"\n"を指定すれば1行ごとに改行されます。出力するときに値をいじる必要があるときはtransformで操作します。transformは殆どの場合クロージャで書くと思います。
以下にFizzBuzzを出力する例を載せます。

println(
    (1..100).joinToString(separator = "\n") {
        when {
            it % 15 == 0 -> "FizzBuzz"
            it % 3 == 0 -> "Fizz"
            it % 5 == 0 -> "Buzz"
            else -> it.toString()
        }
    }
)

たったこれだけで100行のFizzBuzzの出力ができます。ループを回して出力するより遥かに手軽で高速です。

高速化編

競技プログラミングでJVM言語を使う欠点として、入出力でのTLEやMLEが起こりやすいことが挙げられます。
以下に自分が実際にAOJの問題を解く上で起きた問題とその解決策を載せていきます。

1. 入力が多い

入力をリストで渡すような問題で、入力数が膨大なケースがあると思います。こういった問題では特にTLEが起きやすいです。
この場合に試せる解決策をいくつか紹介します。

Stringを数値に直すときにInteger.parseIntを使う

JavaのIntegerparseIntというstaticメソッドがあり、KotlinのStringtoIntより若干早いです。

fun main() {
    val inputs = (1..100000).map(Int::toString)
    val time1 = measureTimeMillis {
        inputs.map(String::toInt)
    }
    val time2 = measureTimeMillis {
        inputs.map(Integer::parseInt)
    }

    println("toInt: ${time1}msec") // toInt: 12msec
    println("parseInt: ${time2}msec") // parseInt: 4msec
}

System.in.bufferedReader()を使う

KotlinのreadLine()より、JavaのSystem.inBufferedReaderを生成してそこからreadLine()するほうが若干早いです。

fun main() {
    val reader = System.`in`.bufferedReader() // inは予約語のためバッククォートでエスケープする
    val time1 = measureTimeMillis {
        repeat(100000) {
            readLine()
        }
    }
    val time2 = measureTimeMillis {
        repeat(100000) {
            reader.readLine()
        }
    }
    println("readLine: ${time1}msec") // readLine: 118msec
    println("reader.readLine: ${time2}msec") // reader.readLine: 12msec
}

あえてScannerを使う

空白区切りで1行で渡される入力はStringsplitでリストを直接生成するというやり方を今までの例でも使ってきましたが、これで生成できるリストはイミュータブルなので、要素の入れ替えや変更などの破壊的操作ができません。KotlinのリストはtoArrayなど配列に変換するためのメソッドが用意されているため、ソートなどのリストの破壊的変更を行うときはそれを使う場合が多いと思います。
しかし、リストを配列に変換する処理は時間がかかるため、入力ケースによってはTLEが起こることもあります。
この場合は、入力のリストのケース2で紹介した方法とScannerを組み合わせて使うほうが早い可能性があります。

val scanner = Scanner(System.`in`)
val n = scanner.nextInt()
val array = IntArray(n) {
    scanner.nextInt()
}

scanner.nextInt()のかわりにInteger.parseInt(scanner.next())を使うとまた少し高速化できます。また、Javaの標準のScannerのかわりに独自にFastScannerのようなものを定義して使うのもいいと思います。

2. 出力でjoinToStringが使えない

自分でスタックやキュー、双方向連結リストなどを実装する問題など、リストを出力したいけどjoinToStringが使えない場合があります。しかし、1つずつprintしていくのは効率が悪いです。

StringBuilderを使う

僕が双方向連結リストの問題を解いたときのコードの一部を例として載せます。

override fun toString(): String {
    var element = nil.next
    val strBuilder = StringBuilder()
    while (element != nil) {
        strBuilder.append(element?.value)
        strBuilder.append(" ")
        element = element?.next
    }
    strBuilder.deleteCharAt(strBuilder.lastIndex)
    return strBuilder.toString()
}

StringBuilderappendで要素を追加していき、toString()でStringにしています。
末尾に空白文字を入れたくない場合もdeleteCharAtで簡単に削除できます。

あとがき

KotlinからはJavaの標準ライブラリを使うことができるので、Javaの競プロのTipsもそのまま使える場合が多いです。そちらも参考にしてみてください。
Java競技プログラミングメモ

また、今回紹介したTipsは実際の開発では可読性などの観点でそのまま使うのはよくないかもしれません。

「こういった場合はどうすればいいの?」という疑問があればコメントしていただけるとお答えできる…かも?しれません。

それでは良きKotlinライフを!

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

sam-cliを使ってKotlinでlambdaを書いた話

概要

最近流行りのsam-cliを使って、Kotlinでlambdaを書いてみました。
今回書いたのは簡単なGETとPOSTの処理になります。
完成物はこちら

環境

  • Kotlin1.3.20
  • Docker for Mac
  • SAM-CLI

開発

開発環境構築

sam-cliのインストール〜パッケージ作成

  1. brew tap aws/tap
  2. brew install aws-sam-cli
  3. sam init --runtime java8

mavenにKotlinの依存関係等の追記

  1. dependenciesに以下のライブラリを追加します。
    • kotlin-stdlib-jdk8
    • kotlin-test-junit
  2. pluginに以下のプラグインを追記します。
    • maven-shade-plugin
    • kotlin-maven-plugin
    • maven-compiler-plugin
  3. sourceDirectory, testSourceDirectoryのpathをsrc/main/kotlin, src/test/kotlinに変更する。

完成品はこちら

javaのソースをKotlinに変換

こちらはintellijを使っていれば、自動でjavaからKotlinに変更することができます。

最初の動作確認

$ mvn package
$ sam local start-api

これでlocal環境でlambdaが立ち上がり、localhostでアクセスできるようになるのでまずは動くことを確認します。
最初は localhost:3000/helloHello World になるはずです。

POST用のlambdaの作成

今回は新たにリクエストボディに人の名前を詰めると、レスポンスにそれを含めてHello Worldを返すAPIを作成します。

受け取るRequestのclassの作成

今回はbodyに以下のようなJsonを渡します。


{
    "firstName": "太郎",
    "lastName": "山田"
}

次にリクエストを受け取るためのクラスを作成します。
ターミナルにて、以下のコマンドを実行するとlambdaが受け取るeventのjsonが取得できるので、それに沿ってクラスを作ります。

$ sam local generate-event apigateway aws-proxy --method POST

{
  "body": "eyJ0ZXN0IjoiYm9keSJ9",
  "resource": "/{proxy+}",
  "path": "/path/to/resource",
  "httpMethod": "POST",
  "isBase64Encoded": true,
  "queryStringParameters": {
    "foo": "bar"
  },
  "pathParameters": {
    "proxy": "/path/to/resource"
  },
  "stageVariables": {
    "baz": "qux"
  },
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate, sdch",
    "Accept-Language": "en-US,en;q=0.8",
    "Cache-Control": "max-age=0",
    "CloudFront-Forwarded-Proto": "https",
    "CloudFront-Is-Desktop-Viewer": "true",
    "CloudFront-Is-Mobile-Viewer": "false",
    "CloudFront-Is-SmartTV-Viewer": "false",
    "CloudFront-Is-Tablet-Viewer": "false",
    "CloudFront-Viewer-Country": "US",
    "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Custom User Agent String",
    "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
    "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
    "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "requestContext": {
    "accountId": "123456789012",
    "resourceId": "123456",
    "stage": "prod",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
    "requestTime": "09/Apr/2015:12:34:56 +0000",
    "requestTimeEpoch": 1428582896000,
    "identity": {
      "cognitoIdentityPoolId": null,
      "accountId": null,
      "cognitoIdentityId": null,
      "caller": null,
      "accessKey": null,
      "sourceIp": "127.0.0.1",
      "cognitoAuthenticationType": null,
      "cognitoAuthenticationProvider": null,
      "userArn": null,
      "userAgent": "Custom User Agent String",
      "user": null
    },
    "path": "/prod/path/to/resource",
    "resourcePath": "/{proxy+}",
    "httpMethod": "POST",
    "apiId": "1234567890",
    "protocol": "HTTP/1.1"
  }
}

続いてこのJsonに対応するクラスを作っていきます。

class Request {
    lateinit var body: String |
}

data class Person(val firstName: String, val lastName: String)

Requestクラスの方は、 RequestHandler を継承したクラスでlambdaの関数を作成する場合、空のコンストラクタがないとパースできないので、今回は通常のclassにしています。
また、リクエストボディに関しては、Jsonではなく文字列で受け取るので、RequestクラスではString型で定義します。
今回はbody以外使わないので、classにはbody以外持たせていません。

lambdaの関数の実装

まずはJsonをパースするために jackson-module-kotlin をpom.xmlのdependencyに追記します。
続いていよいよ関数を実装していきます。
基本的な流れは自動生成されるgetのものと同様ですが、最初にリクエストボディをパースします。
実際のコードは以下の通りです。

class PostApp : RequestHandler<Request, GatewayResponse> {

    override fun handleRequest(input: Request, context: Context?): GatewayResponse {
        val headers = HashMap<String, String>()
        headers["Content-Type"] = "application/json"
        headers["X-Custom-Header"] = "application/json"

        val mapper = jacksonObjectMapper()
        val person = mapper.readValue<Person>(input.body)
        return try {
            val pageContents = this.getPageContents("https://checkip.amazonaws.com")
            val output = String.format("{ \"message\": \"hello %s\", \"location\": \"%s\" }", person.lastName, pageContents)
            GatewayResponse(output, headers, 200)
        } catch (e: IOException) {
            GatewayResponse("{}", headers, 500)
        }
    }

    @Throws(IOException::class)
    private fun getPageContents(address: String): String {
        val url = URL(address)
        BufferedReader(InputStreamReader(url.openStream())).use { br -> return br.lines().collect(Collectors.joining(System.lineSeparator())) }
    }
}

動作確認

$ mvn package
$ sam local start-api

これらを実行し、今回自分で作成したlambdaのエンドポイントを叩いてみます。

$ curl -X POST -d '{"firstName": "山田", lastName: "太郎"}' https://127.0.0.1:3000

これで期待したレスポンスが返ってくれば成功です!

最後に

「javaでかけるんだからKotlinでもかけるっしょ」の精神で書いてみました。
ただ書いてみて思うのは、あえてKotlinで書く必要性がそこまでなかったのではないだろうか。。。というものでした。
個人の趣味とか、会社で使うlambdaの一つをお試しで、とかなら良いかもしれません。

以上でsam-cliを使ってKotlinでlambdaを書いた話は終わりになります。
ありがとうございました。

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

Android Jetpack Component ~Navigation 編~

androidx についてネットを徘徊していたところ
面白そうな物を見つけたのでやってみました。

Navigation Architecture Component

Navigation Architecture Componentを使用すると、一般的ではあるが複雑なナビゲーション要件をアプリケーションに実装できるので、一貫した予測可能なエクスペリエンスをより簡単にユーザーに提供できます。

Navigation  |  Android Developers

要するに ナビゲーション=画面遷移 が簡単にできる様になったよって事なのかな?

No more fragment transaction!!!

めっちゃ書かれてました。

どれだけ簡単になるのか。
Fragment の遷移をやってみたいと思います。

環境

今回使用した環境です。
Navigation を使用するためには Android Studio 3.2 以上が必須です。

  • Android Studio 3.2.1
  • Kotlin 1.2.71

navigation の編集をするために
Preference -> Experimental -> Editor から
Enable Navigation Editor にチェックが入っていなければチェックして再起動しましょう。

build.gradle

現在の最新版は 1.0.0-rc02 ですが、最新版にすると gradle 3.3 が必須になるらしく
今回の環境でやろうとすると R ファイルが見つからなかったりワーニング出たりとよくわからん事象が発生してたので少しバージョン落としてます?

これか...??Android Studio のバージョン上げればちゃんと動くのかな...?

project/build.gradle
buildscript {
    dependencies {
        ...

        classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha11"
    }
app/build.gradle
apply plugin: "androidx.navigation.safeargs"

dependencies {
    ...

    implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha11'
    implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha11'
    implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha11'
}

画面遷移するだけなら必要なのは

implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha11'
implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha11'

ここだけです。

safeargs については後で出て来ますので必要であれば。。。

Navigation

Resource

まずはリソースファイルの作成をします。

いつも通りに Android Resource File を新規作成します。
ただし、今回選択する Resource Type は Navigation を選択します。

Enable Navigation Editor にチェックが入ってなければ選択できない様なので
選択肢に出てこない場合は設定しましょう。

名前はなんでもいいです。
今回は Get Started に書いてあるので nav_graph.xml にしました。

OK すると res/navigation ディレクトリが作成されてこんな感じの xml ができあがります。

nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android">

</navigation>

a04ae75b.png

Transition

Design タブの左上に + アイコンがあり、そこから Fragment を追加します。

Fragment はいつも通りに作成したものを準備しました。

  • FirstFragment
  • SecondFragment

+ アイコン から Create Blank Destination を選択すると Fragment の作成もできます。
Text タブから xml 直書きもできます。
今回はどんな風に出来上がるのかを見るために GUI で動かしていきますね。

まずは Fisrt Fragment を追加してみましょう。

nav_graph.xml
<!-- layout を追加すると Design で Fragment 内のレイアウトが確認できる -->
<fragment android:id="@+id/firstFragment"
          android:name="com.taku.navigation.fragments.FisrtFragment"
          android:label="FirstFragment"
          tools:layout="@layout/fragment_first"/>

fragment タグは新鮮な感じがしますねー。
同じ様に SecondFragment も追加します。

2つ揃ったらいよいよ遷移させてみます。
FirstFragment から矢印を SecondFragment に向けて引っ張るだけです。

a8984681.png

nav_graph.xml
<fragment android:id="@+id/firstFragment"
          android:name="com.taku.navigation.fragments.FisrtFragment"
          android:label="FirstFragment"
          tools:layout="@layout/fragment_first">

        <action android:id="@+id/action_firstFragment_to_SecondFragment"
                app:destination="@id/SecondFragment"/>

</fragment>

<fragment android:id="@+id/secondFragment"
          android:name="com.taku.navigation.fragments.SecondFragment"
          android:label="SecondFragment"
          tools:layout="@layout/fragment_second"/>

action タグが追加されてますね。
これで準備は整いました。

後は遷移させたいタイミングで

binding.button.setOnClickListener {
    Navigation.findNavController(it).navigate(R.id.action_firstFragment_to_secondFragment)
}

みたいにやると SecondFragment に遷移します。

最終的に xml 内はこうなります。
navigation タグに startDestination を追加すると初期表示画面を選択できます。

action タグに transition を設定するとアニメーションの設定ができたりします。

nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            android:id="@+id/nav_graph"
            app:startDestination="@id/firstFragment">

    <fragment android:id="@+id/firstFragment"
              android:name="com.taku.navigation.fragments.FisrtFragment"
              android:label="FisrtFragment"
              tools:layout="@layout/fragment_fisrt">

        <action android:id="@+id/action_firstFragment_to_SecondFragment"
                app:destination="@id/SecondFragment"/>

    </fragment>

    <fragment android:id="@+id/secondFragment"
              android:name="com.taku.navigation.fragments.SecondFragment"
              android:label="SecondFragment"
              tools:layout="@layout/fragment_second"/>
</navigation>

後は Activity の xml 内で fragment タグを追加します。
name には NavHostFragment を指定します。
NavHostFragment の中身を入れ替えて画面遷移を実現している様ですね。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">

        <fragment
                android:id="@+id/nav_host_fragment"
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                android:name="androidx.navigation.fragment.NavHostFragment"
                app:defaultNavHost="true"
                app:navGraph="@navigation/nav_graph"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

これでアプリを起動すれば FirstFragment からスタートします。

FragmentManager が出てこない!!しゅごい!!

実際の遷移処理は FragmentNavigator がやってくれています。

findNavController で渡された View から親を辿って NavController を探し出すみたいですね。
この NavController に nav_graph.xml が bind されてグラフに基づいて navigation を管理してくれているんですね。

NavController が Navigator に繋ぎます。
navigate 関数の中で className から instantiate で Fragment を作って FragmentManager.beginTransaction.replace ってやってます。
NavOptions を使ってアニメーションの設定もゴリゴリやってくれています。

要するに今回の Navigation ライブラリは Fragment 遷移に関して言うと今まで頭を捻っていた Transaction をモダンな仕様でいい感じにラッピングしてくれているんでしょう。

  • popBackStack

Back ボタンに対応させる時は Activity で

override fun onSupportNavigateUp() = findNavController(R.id.nav_host_fragment).navigateUp()

ってやると内部で popBackStack() してくれてるっぽいので画面が戻ります。

バックスタックの管理もできます。

app:popUpTo="@id/firstFragment"

ってやると指定した Fragment から上に積まれてる Fragment を pop してくれます。

Safe args

最初に入れた safeargs とか言う怪しいプラグインはなんだったのか。
先に言うとめっちゃ感動します。

順番に見て行きましょう。

まず、fragment タグの中に argument タグが入れれるんですね。
これで Bundle の受け渡しができます。
どんな感じでやるかというと

nav_graph.xml
<fragment android:id="@+id/firstFragment"
          android:name="com.taku.navigation.fragments.FisrtFragment"
          android:label="FirstFragment"
          tools:layout="@layout/fragment_first">

        <action android:id="@+id/action_firstFragment_to_SecondFragment"
                app:destination="@id/SecondFragment"/>
</fragment>

<fragment android:id="@+id/secondFragment"
          android:name="com.taku.navigation.fragments.SecondFragment"
          android:label="SecondFragment"
          tools:layout="@layout/fragment_second">

        <argument android:name="message"
                  android:defaultValue="default"
                  app:argType="string"/>
</fragment>

あとは遷移元で navigate の引数に Bundle を渡してあげると
遷移先でいつものように arguments から取得できます。

// from
class FirstFragment {
    ...
    val bundle = bundleOf("message" to "to destination")
    Navigation.findNavController(it).navigate(R.id.action_firstFragment_to_secondFragment, bundle)
}

// to
class SecondFragment {
    ...
    val message = arguments?.getString("message") ?: ""
}

...すてきやん?

個人的に今回の中で一番感動してます。
わざわざ newInstance 関数生やして setArgmunets してインスタンス返さなくてもよくなった!スッキリ!

さて、これだけでも結構感動してるんですけどここから涙がちょちょぎれます。

上のやり方って、key の管理とか type が String だから getString 使うとか色々と大変なこと残ってますよね。
デフォルト値設定できるのに arguments が Nullable やし...

そんな時に使うのが Safe args !!

これを使うとどう変わるのか。デデーン

// from
class FirstFragment {
    ...
    Navigation.findNavController(it).navigate(
        FirstFragmentDirections.actionFirstFragmentToSecondFragment().apply {
            message = "to destination"
        }
    )
}

// to
class SecondFragment {
    ...
    val message = SecondFragmentArgs.fromBundle(arguments ?: return).message
}

٩(๑❛ᴗ❛๑)۶

ん〜!!!!
煩わしいことがなくなって超絶スッキリ。

ここでのポイントは DirectionsArgs。付けられた ID を元に自動生成されます。
めっちゃわかりやすくなりますよね。各 Fragment にそれぞれ Directions と Args のサフィックスがつくだけです。
使う時も名前が入ってる方って覚えておけばどっちだっけってならなくなります。

で、遷移時の navigate() の引数気づきました?
Action の ID と Bundle じゃなくて Directions しか渡してない んですよね。
渡す ID を間違えた!なんてことがなくなります。

これすごくないですか。
Args で取得したメンバーは補完が効いててキャストいらないし、設定もメソッドを選ぶだけで key 違うじゃんとかもない。
arguments 使わなくても Directions 渡せば遷移できちゃうので乱用しそう。

これだけのために Navigation 入れてもいいんじゃないかな。。。
Percelable なクラスしかやりとりできないんですけど Json ライブラリとか入れれば data class のやりとりもできそうだよね。

こんな事もできちゃうみたい。可能性しか感じていない。

感想

すっごい、Xcode のストーリーボードだなって...
ただストーリーボードの xml と違ってややこしくないから Text から直書きマンには嬉しいです。

わざわざ Activity にイベント通知して FragmentManager 呼んで Transaction 準備して Commit して...
って煩わしい処理がなくなったので、謳い文句通り簡単に遷移ができてますね。
チームでの開発とかでも Transaction の管理とかで変な差が出にくくなりそうだからいいのかも??

Safe args はほんと神だと思うので積極的に使って行きたい!!stable 版に期待大!!
Jetpack 自体がですけどリリースノートを見る限り現状かなり頻繁に更新されてますね。

DeppLink とか navigation タグのネストとか他にもありそうなので色々やってみたいなー。

参考

Navigation Codelab

Android: Navigationのsafeargs Gradle pluginだけを使ってもいいかもしれない - stsnブログ

Navigation Architecture Componentを試してみる - Qiita

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