- 投稿日:2019-02-28T23:56:41+09:00
[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 で開く
3. デバッガをアタッチする
4. いえーい!
- 投稿日:2019-02-28T23:56:41+09:00
[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 で開く
3. デバッガをアタッチする
アプリが起動して画面が表示されたのを確認したら、アタッチする
Auto のまま、対象のデバイス & アプリ を指定する
4. いえーい!
- 投稿日:2019-02-28T21:54:27+09:00
Ktlintを使って特定のディレクトリにだけフォーマッターをかける
./gradlew ktlintFormatの後に
git diff --name-only | grep -v 特定ディレクトリまでのパス | xargs git checkout
- 投稿日:2019-02-28T21:41:17+09:00
【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
- 投稿日:2019-02-28T19:45:56+09:00
coroutineとViewModelを使ってDialogFragmentを実装する
https://qiita.com/FKbelm/items/f85949dff7a3c0e7781d で試した内容の改善版です。
上の記事では、メモリリークを引き起こしていたので、ViewModelの力を使って再実装を目指しました。
ViewModelの作成
MainActivityViewModel.ktclass 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.ktclass 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/b4f3c2e0a872544b97d0Prism 5とReactiveProperty
https://blog.okazuki.jp/entry/2014/05/09/083315Android Architecture ComponentsのViewModelとDialogFragment
http://sys1yagi.hatenablog.com/entry/2017/08/24/125416
- 投稿日:2019-02-28T19:32:15+09:00
coroutineとViewModelを使ってDialogFragmentを実装する
https://qiita.com/KyoPeeee/items/f85949dff7a3c0e7781d で試した内容の改善版です。
上の記事では、メモリリークを引き起こしていたので、ViewModelの力を使って再実装を目指しました。
ViewModelの作成
MainActivityViewModel.ktclass 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.ktclass 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/b4f3c2e0a872544b97d0Prism 5とReactiveProperty
https://blog.okazuki.jp/entry/2014/05/09/083315Android Architecture ComponentsのViewModelとDialogFragment
http://sys1yagi.hatenablog.com/entry/2017/08/24/125416
- 投稿日:2019-02-28T15:07:54+09:00
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() ?: 0Kotlinの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
で半角スペースを区切り文字に指定しています。split
はList<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 3C言語などの場合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 // FooBarKotlinの出力には改行なしの
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
Int
やDouble
などで桁数指定をしたいときは、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の
Integer
にparseInt
というstaticメソッドがあり、KotlinのString
のtoInt
より若干早いです。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.in
のBufferedReader
を生成してそこから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行で渡される入力は
String
のsplit
でリストを直接生成するというやり方を今までの例でも使ってきましたが、これで生成できるリストはイミュータブルなので、要素の入れ替えや変更などの破壊的操作ができません。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つずつ
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() }
StringBuilder
のappend
で要素を追加していき、toString()
でStringにしています。
末尾に空白文字を入れたくない場合もdeleteCharAt
で簡単に削除できます。あとがき
KotlinからはJavaの標準ライブラリを使うことができるので、Javaの競プロのTipsもそのまま使える場合が多いです。そちらも参考にしてみてください。
Java競技プログラミングメモまた、今回紹介したTipsは実際の開発では可読性などの観点でそのまま使うのはよくないかもしれません。
「こういった場合はどうすればいいの?」という疑問があればコメントしていただけるとお答えできる…かも?しれません。
それでは良きKotlinライフを!
- 投稿日:2019-02-28T13:53:49+09:00
sam-cliを使ってKotlinでlambdaを書いた話
概要
最近流行りのsam-cliを使って、Kotlinでlambdaを書いてみました。
今回書いたのは簡単なGETとPOSTの処理になります。
完成物はこちら環境
- Kotlin1.3.20
- Docker for Mac
- SAM-CLI
開発
開発環境構築
sam-cliのインストール〜パッケージ作成
- brew tap aws/tap
- brew install aws-sam-cli
- sam init --runtime java8
mavenにKotlinの依存関係等の追記
- dependenciesに以下のライブラリを追加します。
- kotlin-stdlib-jdk8
- kotlin-test-junit
- pluginに以下のプラグインを追記します。
- maven-shade-plugin
- kotlin-maven-plugin
- maven-compiler-plugin
- 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/hello
でHello 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を書いた話は終わりになります。
ありがとうございました。
- 投稿日:2019-02-28T10:46:31+09:00
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.gradlebuildscript { dependencies { ... classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha11" }app/build.gradleapply 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>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 に向けて引っ張るだけです。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 }٩(๑❛ᴗ❛๑)۶
ん〜!!!!
煩わしいことがなくなって超絶スッキリ。ここでのポイントは
Directions
とArgs
。付けられた 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 タグのネストとか他にもありそうなので色々やってみたいなー。
参考
Android: Navigationのsafeargs Gradle pluginだけを使ってもいいかもしれない - stsnブログ