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

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

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

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

14c9795d267f5b85abb98ca5e8780646?s=128

Taro Nagasawa

March 10, 2017
Tweet

Transcript

  1. Kotlinを始めようハンズオン 2017-03-10 DroidKaigi 2017 長澤太郎 @ngsw_taro

  2. !!サンプルプロジェクト!! • クローンしておいてください • prepareブランチをビルドしておいてください ◦ ダウンロードが大量に発生します • Android Studio

    2.3対応のものを「-as23」というsuffixで各 ブランチを用意しています github.com/ntaro/github-client-for-droidkaigi
  3. 今日やること • AndroidプロジェクトにKotlinを導入する ◦ 実際に手を動かし、Kotlin導入の手軽さを体験する • KotlinをAndroidアプリ開発に活用する ◦ 簡単なサンプルアプリの開発を通じ、Kotlinの便利さを学ぶ •

    Kotlinの弱点を知り、フォローする ◦ 特にJavaとの相互運用性を理解し、弱点を回避する
  4. 今日やらないこと • Androidおさらい • その他の言語(Javaなど) • アプリの作り込み(あくまでサンプルアプリ) • プログラミングの考え方や手法(オブジェクト指向など) •

    ライブラリ、フレームワーク • データ構造、アルゴリズム • セキュリティ
  5. もくじ 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分 ※カッコ内は課題に取り組む時間
  6. • 長澤 太郎 たろーって呼んでね • @ngsw_taro 自己紹介

  7. エムスリー株式会社 jobs.m3.com/engineer Wantedly記事 goo.gl/BxK5ZZ

  8. エバンジェリストな私 • Kotlin歴 5年 • 日本Kotlinユーザグループ代表 • 講演実績多数 ◦ DroidKaigi

    2015, 2016 ◦ JJUG CCC 2015 Fall ◦ 福岡、京都など遠征も • 執筆実績多数 ◦ 単行本、商業誌、同人誌
  9. チューター紹介 • yy_yank • JavaとKotlin好きなプロ グラマ。仕事はゲーム開発 左右前後で困っている人がいたら助けてあげましょう! • @RyotaMurohoshi •

    お仕事はKotlinで Android。趣味はC#で Unity。
  10. 1. Kotlin概要

  11. Kotlinとは? • Java仮想マシンをターゲットとしたプログラミング言語 ◦ JavaScriptやAndroidもサポート • IntelliJ IDEAでおなじみのJetBrainsが開発 • 2011年に発表され、2016年2月にver1.0がリリース

    • 現在 ver1.1.0 • 静的型付けオブジェクト指向言語 • 簡潔、安全、Javaとの相互運用性
  12. Hello World package sample fun main(args: Array<String>) { if(args.isEmpty()) return

    val name = args[0] println("Hello, ${name}!") }
  13. Kotlinの特徴 • 簡潔 ◦ モダンな文法、型推論、ラムダ式 ◦ 拡張関数、委譲プロパティ、コルーチン • 安全 ◦

    型安全 ◦ NULL安全 • Javaとの相互運用性 ◦ KotlinからJavaコードを呼び出せる。その逆も然り ◦ Java用ツールやFWからKotlinが呼び出されるときハマりやすい
  14. 2. 開発環境の準備 目標 16:05

  15. 必要なもの • JDK, Android SDK • Android Studio • Kotlin

    Plugin
  16. サンプルプロジェクト • クローンしておいてください • ハンズオンではhandsonブランチを使用するので、これを チェックアウトしておいてください • Android Studio 2.3対応のものを「-as23」というsuffixで各

    ブランチを用意しています github.com/ntaro/github-client-for-droidkaigi
  17. 3. AndroidでKotlinを始める 目標 16:08

  18. 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分)
  19. おめでとう! これであなたも Kotlinプログラマ!

  20. 簡単に解説 class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

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

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

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

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } BundleのNullable型(null許容型)
  24. 4. Githubクライアントを作る 目標 16:20

  25. こういう簡単なやつ

  26. サンプルプロジェクト • クローンしておいてください • ハンズオンではhandsonブランチを使用するので、これを チェックアウトしておいてください • Android Studio 2.3対応のものを「-as23」というsuffixで各

    ブランチを用意しています github.com/ntaro/github-client-for-droidkaigi
  27. Kotlinでクラス定義 class Person(val id: Long?, val name: String) val taro

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

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

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

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

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

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

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

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

    = listOf(1, 2, 3) 3. listAdapterのrepositoriesプロパティに、表示対象の リポジトリリストをセットする 4. RepositoryViewクラスのコメントアウトを外す
  36. 解答例 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)
  37. 5. リポジトリ詳細画面への遷移 目標 16:33

  38. リスト項目をタップして、詳細画面へ

  39. リスト項目をタップして、詳細画面へ Repotiroyオブ ジェクトをインテ ントに載せて渡 す

  40. Parcelable • Parcelにデータを書くために実装すべき形式・ルール • 保存: Parcelableインタフェースを実装する • 復元: Parcelable.Creatorインタフェースを実装したオブ ジェクトをstaticフィールドCREATORとして公開する

  41. Parcelable • Parcelにデータを書くために実装すべき形式・ルール • 保存: Parcelableインタフェースを実装する • 復元: Parcelable.Creatorインタフェースを実装したオブ ジェクトをstaticフィールドCREATORとして公開する

    Kotlinには staticもフィールドもない!
  42. コンパニオンオブジェクト class Foo { companion object { val name: String

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

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

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

    String = "Foo" } }
  46. JVMと仲良くするアノテーション class Foo { companion object { @JvmField val name:

    String = "Foo" } } staticフィールドとして見えるようになる
  47. 課題3. Parcelableを実装しよう(6分) • sample.githubclient.model.Repositoryクラス • Userクラスが参考になる ヒント: オブジェクト式(匿名クラス) val onClickListener

    = object: View.OnClickListener { override fun onClick(view: View) { ... } }
  48. よくあるインテント生成するやつ // Java class RepositoryActivity extends AppCompatActivity { static Intent

    intent(Context context, Repository repository) { return new Intent(context, RepositoryActivity.class) .putExtra("repository", repository) } ... }
  49. 使う側 Intent intent = RepositoryActivity.intent(this, repository); startActivity(intent);

  50. 課題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の拡張プロパティ
  51. 便利な拡張関数を定義する 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>()
  52. 便利な拡張関数を定義する 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にメソッドを生やすイメージ
  53. 便利な拡張関数を定義する 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をここで取る ←記述がスッキリ
  54. 便利な拡張関数を定義する 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>() 関数のインライン化により、消えるはずの型引数がランタイムで使える
  55. 便利な拡張関数を定義する 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>() ←非常に目に優しい
  56. 課題5. 詳細画面を起動しよう(4分) • sample.githubclient.MainActivityクラス • listView.setOnItemClickListenerメソッドでリスナを登録 する • listAdapter.repositories[position]で、指定位置のリ ポジトリオブジェクトを取得できる

    ヒント: SAM変換 Javaで定義された「ただひとつの抽象メソッドを持った型を引数に取るメソッド」 に対して、ラムダ式を渡すことができる機能。 listView.setOnItemClickListener { _, _, position, _ -> /* クリックされたときの処理 */ }
  57. 課題6. リポジトリの情報を表示しよう(4分) • sample.githubclient.RepositoryActivityクラス • インテントから受け取ったリポジトリを表示する • findViewByIdでビューを取得(IDはコメントとして記載済) • repositoryView.setRepository(repository)

    • webView.loadUrl(repository.htmlUrl) ヒント: キャスト val textView = findViewById(R.id.text_view) as TextView
  58. 6. Retrofitを使ってAPIを叩く 目標 16:53

  59. 課題7. 検索APIを叩くメソッドを提供しよう(4分) • sample.githubclient.GithubClientインタフェース • @GET("/search/repositories") • @Query("q") • 戻り型

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

    searchButtonのクリック時に検索を非同期で開始 ◦ Call#enqueueメソッドを呼び出す ◦ 引数にコールバックを指定 • 検索結果(Page<Repository>)からRepositoryリストを 取り出して、リストに反映する ◦ listAdapter.repositoriesプロパティ ◦ listAdapter.notifyDataSetChanged()メソッド
  61. 7. DaggerでDIする 目標 17:08

  62. kapt • Daggerのようなコンパイル時にアノテーションを見て、面白い ことをしてくれるツールを、Kotlinでも使えるように apply plugin: 'kotlin-kapt' dependencies { compile

    "com.google.dagger:dagger:$version" kapt "com.google.dagger:dagger-compiler:$version" provided 'javax.annotation:jsr250-api:1.0' }
  63. フィールドインジェクション // Java @Inject GithubClient githubClient; // 暗黙的に初期値はnull // Kotlin

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

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

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

    @JvmField @field:Inject var githubClient: GithubClient? = null 初期値を明示
  67. lateinit修飾子 @Inject lateinit var githubClient: GithubClient

  68. 課題9. GithubClientの実装をInjectしよう(5分) • sample.githubclient.MainActivityクラス • githubClientプロパティを置いて、そこに実装をインジェクトさせ る ◦ @Inject ◦

    lateinit
  69. 8. Kotlin 1.1の機能を使う 目標 17:18

  70. 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) }
  71. コルーチンを利用したライブラリを使う • 3rdパーティ製 async / await for Android • metalabdesign/AsyncAwait

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

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

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

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

    val data2 = awaitSuccessful(getData()) textView.text = "$data1/$data2" } RetrofitのCall用メソッド
  76. 課題10. async/awaitを使ってみよう(5分) • sample.githubclient.MainActivityクラス • asyncブロックを展開する • awaitブロック内は別スレッドで実行され、結果を待つ • Retrofit用のawaitSuccessfulが便利

    • asyncブロックでビューの更新が可能 オプション: エラーハンドリング async { ... }.onError { e: Exception -> ... }
  77. 9. おわりに 目標 17:28

  78. 今日学んだこと • AndroidでKotlinを導入するのは楽チン♪ • Kotlinの文法がなんとなくわかった • まぁ普通にJavaライブラリが使えるよ! ◦ Retrofit ◦

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

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