Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

!!サンプルプロジェクト!! ● クローンしておいてください ● prepareブランチをビルドしておいてください ○ ダウンロードが大量に発生します ● Android Studio 2.3対応のものを「-as23」というsuffixで各 ブランチを用意しています github.com/ntaro/github-client-for-droidkaigi

Slide 3

Slide 3 text

今日やること ● AndroidプロジェクトにKotlinを導入する ○ 実際に手を動かし、Kotlin導入の手軽さを体験する ● KotlinをAndroidアプリ開発に活用する ○ 簡単なサンプルアプリの開発を通じ、Kotlinの便利さを学ぶ ● Kotlinの弱点を知り、フォローする ○ 特にJavaとの相互運用性を理解し、弱点を回避する

Slide 4

Slide 4 text

今日やらないこと ● Androidおさらい ● その他の言語(Javaなど) ● アプリの作り込み(あくまでサンプルアプリ) ● プログラミングの考え方や手法(オブジェクト指向など) ● ライブラリ、フレームワーク ● データ構造、アルゴリズム ● セキュリティ

Slide 5

Slide 5 text

もくじ 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分 ※カッコ内は課題に取り組む時間

Slide 6

Slide 6 text

● 長澤 太郎 たろーって呼んでね ● @ngsw_taro 自己紹介

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

エバンジェリストな私 ● Kotlin歴 5年 ● 日本Kotlinユーザグループ代表 ● 講演実績多数 ○ DroidKaigi 2015, 2016 ○ JJUG CCC 2015 Fall ○ 福岡、京都など遠征も ● 執筆実績多数 ○ 単行本、商業誌、同人誌

Slide 9

Slide 9 text

チューター紹介 ● yy_yank ● JavaとKotlin好きなプロ グラマ。仕事はゲーム開発 左右前後で困っている人がいたら助けてあげましょう! ● @RyotaMurohoshi ● お仕事はKotlinで Android。趣味はC#で Unity。

Slide 10

Slide 10 text

1. Kotlin概要

Slide 11

Slide 11 text

Kotlinとは? ● Java仮想マシンをターゲットとしたプログラミング言語 ○ JavaScriptやAndroidもサポート ● IntelliJ IDEAでおなじみのJetBrainsが開発 ● 2011年に発表され、2016年2月にver1.0がリリース ● 現在 ver1.1.0 ● 静的型付けオブジェクト指向言語 ● 簡潔、安全、Javaとの相互運用性

Slide 12

Slide 12 text

Hello World package sample fun main(args: Array) { if(args.isEmpty()) return val name = args[0] println("Hello, ${name}!") }

Slide 13

Slide 13 text

Kotlinの特徴 ● 簡潔 ○ モダンな文法、型推論、ラムダ式 ○ 拡張関数、委譲プロパティ、コルーチン ● 安全 ○ 型安全 ○ NULL安全 ● Javaとの相互運用性 ○ KotlinからJavaコードを呼び出せる。その逆も然り ○ Java用ツールやFWからKotlinが呼び出されるときハマりやすい

Slide 14

Slide 14 text

2. 開発環境の準備 目標 16:05

Slide 15

Slide 15 text

必要なもの ● JDK, Android SDK ● Android Studio ● Kotlin Plugin

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

3. AndroidでKotlinを始める 目標 16:08

Slide 18

Slide 18 text

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分)

Slide 19

Slide 19 text

おめでとう! これであなたも Kotlinプログラマ!

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

簡単に解説 class MainActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } BundleのNullable型(null許容型)

Slide 24

Slide 24 text

4. Githubクライアントを作る 目標 16:20

Slide 25

Slide 25 text

こういう簡単なやつ

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Kotlinでクラス定義 class Person(val id: Long?, val name: String) val taro = Person(123, "Taro") taro.id //=> 123 taro.name //=> Taro プロパティにアクセス

Slide 33

Slide 33 text

課題1. リポジトリクラスを作ろう(4分) ● Github上に存在するリポジトリを表現するクラスを定義しま しょう。 ● sample.githubclient.model.Repository プロパティ名 型 説明 id Long ID fullName String フルネーム=「ユーザ名 /リポジトリ名」 description String リポジトリの説明 htmlUrl String 詳細URL stargazersCount Int スター数 owner User リポジトリの所有者。 Userクラスは定義済み language String? 言語

Slide 34

Slide 34 text

解答例 class Repository(val id: Long, val fullName: String, val description: String, val htmlUrl: String, val stargazersCount: Int, val owner: User, val language: String?)

Slide 35

Slide 35 text

課題2. ダミーデータをリスト表示しよう(4分) 1. 作成したRepositoryクラスのインスタンスをいくつか生成す る 2. 標準関数listOfを使用して、リストを生成する e.g.) val ints = listOf(1, 2, 3) 3. listAdapterのrepositoriesプロパティに、表示対象の リポジトリリストをセットする 4. RepositoryViewクラスのコメントアウトを外す

Slide 36

Slide 36 text

解答例 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)

Slide 37

Slide 37 text

5. リポジトリ詳細画面への遷移 目標 16:33

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

コンパニオンオブジェクト class Foo { companion object { val name: String = "Foo" } } Foo.name //=> Foo staticっぽい! フィールドっぽい! →でも違う

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

JVMと仲良くするアノテーション class Foo { companion object { @JvmField val name: String = "Foo" } } staticフィールドとして見えるようになる

Slide 47

Slide 47 text

課題3. Parcelableを実装しよう(6分) ● sample.githubclient.model.Repositoryクラス ● Userクラスが参考になる ヒント: オブジェクト式(匿名クラス) val onClickListener = object: View.OnClickListener { override fun onClick(view: View) { ... } }

Slide 48

Slide 48 text

よくあるインテント生成するやつ // Java class RepositoryActivity extends AppCompatActivity { static Intent intent(Context context, Repository repository) { return new Intent(context, RepositoryActivity.class) .putExtra("repository", repository) } ... }

Slide 49

Slide 49 text

使う側 Intent intent = RepositoryActivity.intent(this, repository); startActivity(intent);

Slide 50

Slide 50 text

課題4. 起動用インテント生成関数を提供する(4分) ● sample.githubclient.RepositoryActivityクラス ヒント: 単一式関数 fun intent(context: Context): Intent { return Intent(...) } fun intent(context: Context: Intent = Intent(...) 同じ ヒント: Classオブジェクトの取得 ずばりFoo::class.java ● Foo:class ←Kotlin用リフレクション KClass ● Foo:class.java ←KClassの拡張プロパティ

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

便利な拡張関数を定義する intent(context, MyActivity::class.java) fun Context.intent(kClass: KClass) = Intent(this, kClass.java) context.intent(MyActivity::class) inline fun Context.intent() = Intent(this, T::class.java) context.intent() ←非常に目に優しい

Slide 56

Slide 56 text

課題5. 詳細画面を起動しよう(4分) ● sample.githubclient.MainActivityクラス ● listView.setOnItemClickListenerメソッドでリスナを登録 する ● listAdapter.repositories[position]で、指定位置のリ ポジトリオブジェクトを取得できる ヒント: SAM変換 Javaで定義された「ただひとつの抽象メソッドを持った型を引数に取るメソッド」 に対して、ラムダ式を渡すことができる機能。 listView.setOnItemClickListener { _, _, position, _ -> /* クリックされたときの処理 */ }

Slide 57

Slide 57 text

課題6. リポジトリの情報を表示しよう(4分) ● sample.githubclient.RepositoryActivityクラス ● インテントから受け取ったリポジトリを表示する ● findViewByIdでビューを取得(IDはコメントとして記載済) ● repositoryView.setRepository(repository) ● webView.loadUrl(repository.htmlUrl) ヒント: キャスト val textView = findViewById(R.id.text_view) as TextView

Slide 58

Slide 58 text

6. Retrofitを使ってAPIを叩く 目標 16:53

Slide 59

Slide 59 text

課題7. 検索APIを叩くメソッドを提供しよう(4分) ● sample.githubclient.GithubClientインタフェース ● @GET("/search/repositories") ● @Query("q") ● 戻り型 Call> ● パッケージに注意 ○ retrofit2.Call ○ sample.githubclient.model.Page ヒント: インタフェース Javaのインタフェースと基本的に同じです。 抽象メソッドを宣言するのに、abstractキーワードは省略可能であり、普通 記述しません。

Slide 60

Slide 60 text

課題8. GithubClientで検索を実行しよう(8分) ● sample.githubclient.MainActivityクラス ● 既に設定済みのRetrofitオブジェクトが用意されている ● retrofit.create(GithubClient::class.java)で GithubClientの実装を取得する ● searchButtonのクリック時に検索を非同期で開始 ○ Call#enqueueメソッドを呼び出す ○ 引数にコールバックを指定 ● 検索結果(Page)からRepositoryリストを 取り出して、リストに反映する ○ listAdapter.repositoriesプロパティ ○ listAdapter.notifyDataSetChanged()メソッド

Slide 61

Slide 61 text

7. DaggerでDIする 目標 17:08

Slide 62

Slide 62 text

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' }

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

lateinit修飾子 @Inject lateinit var githubClient: GithubClient

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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) }

Slide 71

Slide 71 text

コルーチンを利用したライブラリを使う ● 3rdパーティ製 async / await for Android ● metalabdesign/AsyncAwait kotlin { experimental { coroutines 'enable' } } dependencies { compile 'co.metalab.asyncawait:asyncawait:1.0.0' }

Slide 72

Slide 72 text

使い方 button.setOnClickListener { async { val data = await { 時間のかかる処理() } textView.text = data } } 処理の流れ コールバック地獄から解放される

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Retrofitでは async { val data1 = await { getData().execute().body() } val data2 = awaitSuccessful(getData()) textView.text = "$data1/$data2" } RetrofitのCall用メソッド

Slide 76

Slide 76 text

課題10. async/awaitを使ってみよう(5分) ● sample.githubclient.MainActivityクラス ● asyncブロックを展開する ● awaitブロック内は別スレッドで実行され、結果を待つ ● Retrofit用のawaitSuccessfulが便利 ● asyncブロックでビューの更新が可能 オプション: エラーハンドリング async { ... }.onError { e: Exception -> ... }

Slide 77

Slide 77 text

9. おわりに 目標 17:28

Slide 78

Slide 78 text

今日学んだこと ● AndroidでKotlinを導入するのは楽チン♪ ● Kotlinの文法がなんとなくわかった ● まぁ普通にJavaライブラリが使えるよ! ○ Retrofit ○ Dagger2 ● Javaとの違い、共存の仕方 ○ RepositoryActivity::class.java ○ @JvmFieldによるフィールド化 ○ Kotlinの弱点→Javaからの見え方を意識すべし ● コード量が少なくなり、目に優しい! ○ 拡張関数 ○ コルーチン

Slide 79

Slide 79 text

ご静聴ありがとうございました 質問や不明点があったら...  → Q&Aは時間取れないので  → このあとの懇親会で!  → Twitterでも何でも気軽に声かけてください!  → 日本語 Kotlin Slackも! http://kotlinlang-jp.herokuapp.com  → teratailでQ&Aを共有 (Kotlinエキスパートユーザ) @ngsw_taro 再演・講演依頼、執 筆依頼などお待ちし てます!