Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlinを始めようハンズオン #DroidKaigi #DroidKaigi6

Kotlinを始めようハンズオン #DroidKaigi #DroidKaigi6

DroidKaigi2017 (https://droidkaigi.github.io/2017/) で発表したスライドです。

Taro Nagasawa

March 10, 2017
Tweet

More Decks by Taro Nagasawa

Other Decks in Programming

Transcript

  1. もくじ 1. Kotlin概要 5分 2. 開発環境の準備 3分 3. AndroidでKotlinを始める 12分(7分)

    4. Githabクライアントを作る 13分(8分) 5. リポジトリ詳細画面への遷移 20分(13分) 6. Retrofitを使ってAPIを叩く 15分(12分) 7. DaggerでDIする 10分(5分) 8. Kotlin 1.1の機能を使う 10分(5分) 9. おわりに 2分 ※カッコ内は課題に取り組む時間
  2. エバンジェリストな私 • Kotlin歴 5年 • 日本Kotlinユーザグループ代表 • 講演実績多数 ◦ DroidKaigi

    2015, 2016 ◦ JJUG CCC 2015 Fall ◦ 福岡、京都など遠征も • 執筆実績多数 ◦ 単行本、商業誌、同人誌
  3. Kotlinの特徴 • 簡潔 ◦ モダンな文法、型推論、ラムダ式 ◦ 拡張関数、委譲プロパティ、コルーチン • 安全 ◦

    型安全 ◦ NULL安全 • Javaとの相互運用性 ◦ KotlinからJavaコードを呼び出せる。その逆も然り ◦ Java用ツールやFWからKotlinが呼び出されるときハマりやすい
  4. Kotlinコードを最速でAndroidで動かす方法 1. File -> New Project… 2. すべてデフォルトの設定でOK 3. ウィザードのFinishボタンを押す

    4. Tools -> Kotlin -> Configure Kotlin in Project 5. 「Kotlin 1.1」になっていることを確認してOK 6. Code -> Convert Java File to Kotlin File 7. javaディレクトリを「kotlin」にリネーム 8. ビルド&実行 やってみよう(7分)
  5. 簡単に解説 class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
  6. 簡単に解説 class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } 継承 + スーパクラスのコンストラクタ呼び出し
  7. 簡単に解説 class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } オーバライドするために必須
  8. 簡単に解説 class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } BundleのNullable型(null許容型)
  9. Kotlinでクラス定義 class Person(val id: Long?, val name: String) val taro

    = Person(123, "Taro") taro.id //=> 123 taro.name //=> Taro
  10. Kotlinでクラス定義 class Person(val id: Long?, val name: String) val taro

    = Person(123, "Taro") taro.id //=> 123 taro.name //=> Taro これがクラス
  11. Kotlinでクラス定義 class Person(val id: Long?, val name: String) val taro

    = Person(123, "Taro") taro.id //=> 123 taro.name //=> Taro プライマリコンストラクタ
  12. Kotlinでクラス定義 class Person(val id: Long?, val name: String) val taro

    = Person(123, "Taro") taro.id //=> 123 taro.name //=> Taro プロパティ キーワードvalがミソ
  13. Kotlinでクラス定義 class Person(val id: Long?, val name: String) val taro

    = Person(123, "Taro") taro.id //=> 123 taro.name //=> Taro コンストラクタを呼び出して インスタンスを変数に代入
  14. Kotlinでクラス定義 class Person(val id: Long?, val name: String) val taro

    = Person(123, "Taro") taro.id //=> 123 taro.name //=> Taro プロパティにアクセス
  15. 課題1. リポジトリクラスを作ろう(4分) • Github上に存在するリポジトリを表現するクラスを定義しま しょう。 • sample.githubclient.model.Repository プロパティ名 型 説明

    id Long ID fullName String フルネーム=「ユーザ名 /リポジトリ名」 description String リポジトリの説明 htmlUrl String 詳細URL stargazersCount Int スター数 owner User リポジトリの所有者。 Userクラスは定義済み language String? 言語
  16. 解答例 class Repository(val id: Long, val fullName: String, val description:

    String, val htmlUrl: String, val stargazersCount: Int, val owner: User, val language: String?)
  17. 課題2. ダミーデータをリスト表示しよう(4分) 1. 作成したRepositoryクラスのインスタンスをいくつか生成す る 2. 標準関数listOfを使用して、リストを生成する e.g.) val ints

    = listOf(1, 2, 3) 3. listAdapterのrepositoriesプロパティに、表示対象の リポジトリリストをセットする 4. RepositoryViewクラスのコメントアウトを外す
  18. 解答例 val user = User(1, "ntaro", "https://example.com") val repository =

    Repository(2, "ntaro/Sample", "sample Project", "https://example.com", 3, user, "Kotlin") listAdapter = listOf(repository)
  19. コンパニオンオブジェクト class Foo { companion object { val name: String

    = "Foo" } } Foo.name //=> Foo コンパニオンオブジェクト
  20. コンパニオンオブジェクト class Foo { companion object { val name: String

    = "Foo" } } Foo.name //=> Foo staticっぽい! フィールドっぽい! →でも違う
  21. JVMと仲良くするアノテーション class Foo { companion object { @JvmField val name:

    String = "Foo" } } staticフィールドとして見えるようになる
  22. よくあるインテント生成するやつ // Java class RepositoryActivity extends AppCompatActivity { static Intent

    intent(Context context, Repository repository) { return new Intent(context, RepositoryActivity.class) .putExtra("repository", repository) } ... }
  23. 課題4. 起動用インテント生成関数を提供する(4分) • sample.githubclient.RepositoryActivityクラス ヒント: 単一式関数 fun intent(context: Context): Intent

    { return Intent(...) } fun intent(context: Context: Intent = Intent(...) 同じ ヒント: Class<Foo>オブジェクトの取得 ずばりFoo::class.java • Foo:class ←Kotlin用リフレクション KClass<Foo> • Foo:class.java ←KClassの拡張プロパティ
  24. 便利な拡張関数を定義する intent(context, MyActivity::class.java) fun <T:Any> Context.intent(kClass: KClass<T>) = Intent(this, kClass.java)

    context.intent(MyActivity::class) inline fun <reified T:Any> Context.intent() = Intent(this, T::class.java) context.intent<MyActivity>()
  25. 便利な拡張関数を定義する intent(context, MyActivity::class.java) fun <T:Any> Context.intent(kClass: KClass<T>) = Intent(this, kClass.java)

    context.intent(MyActivity::class) inline fun <reified T:Any> Context.intent() = Intent(this, T::class.java) context.intent<MyActivity>() Contextにメソッドを生やすイメージ
  26. 便利な拡張関数を定義する intent(context, MyActivity::class.java) fun <T:Any> Context.intent(kClass: KClass<T>) = Intent(this, kClass.java)

    context.intent(MyActivity::class) inline fun <reified T:Any> Context.intent() = Intent(this, T::class.java) context.intent<MyActivity>() KClassを引数に、Classをここで取る ←記述がスッキリ
  27. 便利な拡張関数を定義する intent(context, MyActivity::class.java) fun <T:Any> Context.intent(kClass: KClass<T>) = Intent(this, kClass.java)

    context.intent(MyActivity::class) inline fun <reified T:Any> Context.intent() = Intent(this, T::class.java) context.intent<MyActivity>() 関数のインライン化により、消えるはずの型引数がランタイムで使える
  28. 便利な拡張関数を定義する intent(context, MyActivity::class.java) fun <T:Any> Context.intent(kClass: KClass<T>) = Intent(this, kClass.java)

    context.intent(MyActivity::class) inline fun <reified T:Any> Context.intent() = Intent(this, T::class.java) context.intent<MyActivity>() ←非常に目に優しい
  29. 課題5. 詳細画面を起動しよう(4分) • sample.githubclient.MainActivityクラス • listView.setOnItemClickListenerメソッドでリスナを登録 する • listAdapter.repositories[position]で、指定位置のリ ポジトリオブジェクトを取得できる

    ヒント: SAM変換 Javaで定義された「ただひとつの抽象メソッドを持った型を引数に取るメソッド」 に対して、ラムダ式を渡すことができる機能。 listView.setOnItemClickListener { _, _, position, _ -> /* クリックされたときの処理 */ }
  30. 課題7. 検索APIを叩くメソッドを提供しよう(4分) • sample.githubclient.GithubClientインタフェース • @GET("/search/repositories") • @Query("q") • 戻り型

    Call<Page<Repository>> • パッケージに注意 ◦ retrofit2.Call ◦ sample.githubclient.model.Page ヒント: インタフェース Javaのインタフェースと基本的に同じです。 抽象メソッドを宣言するのに、abstractキーワードは省略可能であり、普通 記述しません。
  31. 課題8. GithubClientで検索を実行しよう(8分) • sample.githubclient.MainActivityクラス • 既に設定済みのRetrofitオブジェクトが用意されている • retrofit.create(GithubClient::class.java)で GithubClientの実装を取得する •

    searchButtonのクリック時に検索を非同期で開始 ◦ Call#enqueueメソッドを呼び出す ◦ 引数にコールバックを指定 • 検索結果(Page<Repository>)からRepositoryリストを 取り出して、リストに反映する ◦ listAdapter.repositoriesプロパティ ◦ listAdapter.notifyDataSetChanged()メソッド
  32. フィールドインジェクション // Java @Inject GithubClient githubClient; // 暗黙的に初期値はnull // Kotlin

    @JvmField @field:Inject var githubClient: GithubClient? = null 無理やりフィールド化
  33. フィールドインジェクション // Java @Inject GithubClient githubClient; // 暗黙的に初期値はnull // Kotlin

    @JvmField @field:Inject var githubClient: GithubClient? = null フィールドに対してアノテート
  34. フィールドインジェクション // Java @Inject GithubClient githubClient; // 暗黙的に初期値はnull // Kotlin

    @JvmField @field:Inject var githubClient: GithubClient? = null 初期値を明示
  35. Kotlin 1.1が3/1にリリースされた! • コルーチン • Javaのようなメソッド参照 ◦ foo.map(bar::method) • ラムダ式の引数での分解

    ◦ Pair("one", 1).let { (name, value) -> ... } • 型エイリアス ◦ typealias OnClickListener = (View) -> Unit • ローカル委譲プロパティ ◦ fun foo() { val s: String by lazy { "hello" } println(s) }
  36. コルーチンを利用したライブラリを使う • 3rdパーティ製 async / await for Android • metalabdesign/AsyncAwait

    kotlin { experimental { coroutines 'enable' } } dependencies { compile 'co.metalab.asyncawait:asyncawait:1.0.0' }
  37. 使い方 button.setOnClickListener { async { val data = await {

    時間のかかる処理() } textView.text = data } } 処理の流れ コールバック地獄から解放される
  38. Retrofitでは async { val data1 = await { getData().execute().body() }

    val data2 = awaitSuccessful(getData()) textView.text = "$data1/$data2" }
  39. Retrofitでは async { val data1 = await { getData().execute().body() }

    val data2 = awaitSuccessful(getData()) textView.text = "$data1/$data2" } 別スレッドでの処理&待ち合わせ
  40. Retrofitでは async { val data1 = await { getData().execute().body() }

    val data2 = awaitSuccessful(getData()) textView.text = "$data1/$data2" } RetrofitのCall用メソッド
  41. 今日学んだこと • AndroidでKotlinを導入するのは楽チン♪ • Kotlinの文法がなんとなくわかった • まぁ普通にJavaライブラリが使えるよ! ◦ Retrofit ◦

    Dagger2 • Javaとの違い、共存の仕方 ◦ RepositoryActivity::class.java ◦ @JvmFieldによるフィールド化 ◦ Kotlinの弱点→Javaからの見え方を意識すべし • コード量が少なくなり、目に優しい! ◦ 拡張関数 ◦ コルーチン
  42. ご静聴ありがとうございました 質問や不明点があったら...  → Q&Aは時間取れないので  → このあとの懇親会で!  → Twitterでも何でも気軽に声かけてください!  → 日本語

    Kotlin Slackも! http://kotlinlang-jp.herokuapp.com  → teratailでQ&Aを共有 (Kotlinエキスパートユーザ) @ngsw_taro 再演・講演依頼、執 筆依頼などお待ちし てます!