$30 off During Our Annual Pro Sale. View Details »

1年半プロジェクトでKotlinを使ってみて良かったこと、良くなかったこと(2017-01 CA.apk)

AAkira
January 12, 2017

1年半プロジェクトでKotlinを使ってみて良かったこと、良くなかったこと(2017-01 CA.apk)

AAkira

January 12, 2017
Tweet

More Decks by AAkira

Other Decks in Technology

Transcript

  1. 1年半プロジェクトでKotlinを使ってみて
    良かったこと、良くなかったこと
    CA.apk

    View Slide

  2. About me
    @_a_akira
    AAkira
    CyberAgent, Inc.
    Akira Aratani

    View Slide

  3. About FRESH!
    • 生放送配信プラットフォーム
    • FRESH! ≒ AbemaTV

    View Slide

  4. Released map
    M1 2012-04-12
    M11 2015-03-19
    M14 2015-10-01
    1.0-beta4 2015-12-22
    M13 2015-09-16
    1.0 2016-02-16
    1.0-RC 2016-02-04
    2016-01-21 Release
    2015-04 開発開始
    kotlin
    FRESH

    View Slide

  5. 良かったこと

    View Slide

  6. コード量が減る

    View Slide

  7. コード量が減る
    for (int i = 0; i < 10; i++) {
    Log.v("tag", "i=" + i);
    }
    int[] array = {0, 10, 20, 30, 40, 50, 60};
    for (int item : array) {
    Log.v("tag", "item=" + item);
    }
    for (int i = 0; i < array.length; i++) {
    Log.v("tag", "item[" + i + "]=" + array[i]);
    }

    View Slide

  8. コード量が減る
    convert to Kotlin
    for (i in 0..9) { Log.v("tag", "i=" + i) }
    val array = intArrayOf(0, 10, 20, 30, 40, 50, 60)
    array.forEach { Log.v("tag", "item=" + it) }
    array.forEachIndexed { index, item ->
    Log.v("tag", "item[$index]=$item")
    }

    View Slide

  9. コード量が減る
    Everything is stream
    https://speakerdeck.com/aakira/flux-with-kotlin-abema-dev-con-2016

    View Slide

  10. コード量が減る
    Observable.from(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
    .filter(new Func1() {
    @Override
    public Boolean call(Integer i) {
    return (i % 2) == 0;
    }
    })
    .map(new Func1() {
    @Override
    public Integer call(Integer i) {
    return i * 10;
    }
    })
    .subscribe(new Observer() {
    @Override
    public void onNext(Integer integer) {
    Log.d("TAG", integer.toString());
    }
    @Override
    public void onCompleted() {
    }
    @Override
    public void onError(Throwable e) {
    }
    });

    View Slide

  11. コード量が減る
    Observable.from(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
    .filter(new Func1() {
    @Override
    public Boolean call(Integer i) {
    return (i % 2) == 0;
    }
    })
    .map(new Func1() {
    @Override
    public Integer call(Integer i) {
    return i * 10;
    }
    })
    .subscribe(new Observer() {
    @Override
    public void onNext(Integer integer) {
    Log.d("TAG", integer.toString());
    }
    @Override
    public void onCompleted() {
    }
    @Override
    public void onError(Throwable e) {
    }
    });
    20, 40, 60, 80, 100

    View Slide

  12. コード量が減る
    convert to Kotlin
    Observable.from(arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
    .filter { i -> i % 2 == 0 }
    .map { i -> i * 10 }
    .subscribe(object : Observer {
    override fun onNext(integer: Int?) {
    Log.d("TAG", integer.toString())
    }
    override fun onCompleted() {}
    override fun onError(e: Throwable) {}
    })

    View Slide

  13. コード量が減る
    Observable.from(arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
    .filter { it % 2 == 0 }
    .map { it * 10 }
    .subscribe(object : Observer {
    override fun onNext(integer: Int?) {
    Log.d("TAG", integer.toString())
    }
    override fun onCompleted() {}
    override fun onError(e: Throwable) {}
    })

    View Slide

  14. コード量が減る
    Observable.from(arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
    .filter { it % 2 == 0 }
    .map { it * 10 }
    .subscribe({
    Log.d("TAG", it.toString())
    }, {
    // onError
    }, {
    // onCompleted
    })

    View Slide

  15. 拡張関数便利

    View Slide

  16. 拡張関数便利
    forEachの正体もこれ
    /**
    * Performs the given [action] on each element, providing sequential index with the
    element.
    * @param [action] function that takes the index of an element and the element itself
    * and performs the desired action on the element.
    */
    public inline fun Iterable.forEachIndexed(action: (Int, T) -> Unit): Unit {
    var index = 0
    for (item in this) action(index++, item)
    }
    /**
    * Performs the given [action] on each element.
    */
    @kotlin.internal.HidesMembers
    public inline fun Iterable.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
    }

    View Slide

  17. 拡張関数便利
    • run
    • let
    • apply
    • (with)
    Standard.kt

    View Slide

  18. 拡張関数便利
    run, let, apply, withの使用回数

    View Slide

  19. 拡張関数便利
    • run
    • let
    • apply
    • (with)
    146 箇所

    View Slide

  20. 拡張関数便利
    • run
    • let
    • apply
    • (with)
    146 箇所
    442 箇所

    View Slide

  21. 拡張関数便利
    • run
    • let
    • apply
    • (with)
    146 箇所
    442 箇所
    293 箇所

    View Slide

  22. 拡張関数便利
    • run
    • let
    • apply
    • (with)
    146 箇所
    442 箇所
    293 箇所
    0 箇所

    View Slide

  23. 拡張関数便利
    FRESH!内で定義されている
    拡張関数の数

    View Slide

  24. 拡張関数便利
    58 個
    fun\s[A-z]*\.[a-z]*\(.*\)\s\{

    View Slide

  25. Swiftなんとなくわかる

    View Slide

  26. Swiftなんとなくわかる
    iOSの方が優先されがち
    → 先に実装終わってる
    この仕様どうしよう?

    View Slide

  27. Swiftなんとなくわかる
    iOSのコードパクろう

    View Slide

  28. コードの意図が伝わる

    View Slide

  29. コードの意図が伝わる
    師匠 < コードは小説だ

    View Slide

  30. コードの意図が伝わる
    val hoge = 10
    var foo = 100
    val bar: String
    if (hoge > 5) {
    foo = 200
    bar = “fresh!"
    } else {
    foo = 300
    bar = "abema"
    }
    Log.v("TAG", "foo=$foo, bar=$bar")

    View Slide

  31. コードの意図が伝わる
    val hoge = 10
    var foo = 100
    val bar: String
    if (hoge > 5) {
    foo = 200
    bar = “fresh!"
    } else {
    foo = 300
    bar = "abema"
    }
    Log.v("TAG", "foo=$foo, bar=$bar")
    定数が一目瞭然

    View Slide

  32. コードの意図が伝わる
    class HogeActivity : Activity {
    companion object {
    private const val EXTRA_HOGE = "extra_hoge"
    private const val EXTRA_FOO = "extra_foo"
    private const val EXTRA_BAR = "extra_bar"
    fun createIntent(activity: Activity, hoge: Int) =
    Intent(activity, HogeActivity::class.java).apply {
    putExtra(EXTRA_HOGE, hoge)
    }
    fun createIntent(activity: Activity, foo: String, bar: String? = null) =
    Intent(activity, HogeActivity::class.java).apply {
    putExtra(EXTRA_FOO, foo)
    putExtra(EXTRA_BAR, bar)
    }
    }
    ……
    }

    View Slide

  33. コードの意図が伝わる
    class HogeActivity : Activity {
    companion object {
    private const val EXTRA_HOGE = "extra_hoge"
    private const val EXTRA_FOO = "extra_foo"
    private const val EXTRA_BAR = "extra_bar"
    fun createIntent(activity: Activity, hoge: Int) =
    Intent(activity, HogeActivity::class.java).apply {
    putExtra(EXTRA_HOGE, hoge)
    }
    fun createIntent(activity: Activity, foo: String, bar: String? = null) =
    Intent(activity, HogeActivity::class.java).apply {
    putExtra(EXTRA_FOO, foo)
    putExtra(EXTRA_BAR, bar)
    }
    }
    ……
    } nullに意味がある

    View Slide

  34. アプリがcrashしない

    View Slide

  35. アプリがcrashしない

    View Slide

  36. アプリがcrashしない
    Androidのcrash ranking
    https://www.apteligent.com/developer-resources/top-5-crashes-on-android/
    1. java.lang.NullPointerException
    2. java.lang.OutOfMemoryError
    3. android.view.WindowManager.BadTokenException
    4. java.lang.IllegalException
    5. android.database.sqlite.SQLiteException

    View Slide

  37. アプリがcrashしない
    Androidのcrash ranking
    https://www.apteligent.com/developer-resources/top-5-crashes-on-android/
    1. java.lang.NullPointerException
    2. java.lang.OutOfMemoryError
    3. android.view.WindowManager.BadTokenException
    4. java.lang.IllegalException
    5. android.database.sqlite.SQLiteException

    View Slide

  38. アプリがcrashしない
    var hoge: String? = “hoge"
    // ΋͠hoge͕nullͩͬͨΒ0
    if (hoge?.length ?: 0 > 0) hoge = "replace"
    // ΋͠hoge͕nullͩͬͨΒॳظԽ
    hoge = hoge ?: "init"

    View Slide

  39. アプリがcrashしない
    class Hoge {
    var onHogeChangedListener: (() -> Unit)? = null
    fun doSomething() {
    onHogeChangedListener?.invoke()
    }
    }
    class HogeActivity: Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val hoge = Hoge()
    hoge.onHogeChangedListener = {
    }
    }
    }

    View Slide

  40. 良くなかったこと

    View Slide

  41. ない

    View Slide

  42. 強いて言うなら…

    View Slide

  43. メソッド数が微妙に増える

    View Slide

  44. メソッド数が微妙に増える
    5142 kotlin

    2 kotlin._Assertions

    12 kotlin.annotation

    2519 kotlin.collections

    65 kotlin.comparisons

    31 kotlin.concurrent

    6 kotlin.internal

    296 kotlin.io

    549 kotlin.jvm

    24 kotlin.jvm.functions

    506 kotlin.jvm.internal

    2 kotlin.jvm.internal.unsafe

    23 kotlin.properties

    227 kotlin.ranges

    61 kotlin.reflect

    423 kotlin.sequences

    3 kotlin.system

    762 kotlin.text
    https://github.com/KeepSafe/dexcount-gradle-plugin
    Kotlin version : 1.06

    View Slide

  45. 動作が不安定(だった)

    View Slide

  46. 動作が不安定(だった)
    Kotlin 86.5%
    Java 13.4%
    Other 0.1%

    View Slide

  47. 人材の流動性

    View Slide

  48. 人材の流動性
    < Kotlinだからなぁ〜
    ɹ FRESHに来てください!
    (実話)

    View Slide

  49. 人材の流動性
    < Welcome!!
    ɹ Kotlin書きたい!
    (実話)

    View Slide

  50. まとめ

    View Slide

  51. まとめ
    メリットの方が多いので
    Kotlinで開発した方が良い

    View Slide

  52. でしょうね

    View Slide

  53. 何が言いたいか

    View Slide

  54. まとめ
    Kotlin

    View Slide

  55. まとめ
    Kotlin
    RxJava

    View Slide

  56. まとめ
    Dagger
    Kotlin
    RxJava

    View Slide

  57. まとめ
    好きなLibraryなんでも
    Dagger
    Kotlin
    RxJava

    View Slide

  58. まとめ
    Androidエンジニアを募集しております
    &

    View Slide

  59. View Slide

  60. まだ時間ある…?

    View Slide

  61. 時間あったらおまけ

    View Slide

  62. よく使ってる拡張関数

    View Slide

  63. Animator Listener
    fun Animator.setListener(
    onAnimationStart: (Animator) -> Unit = {},
    onAnimationEnd: (Animator) -> Unit = {},
    onAnimationCancel: (Animator) -> Unit = {},
    onAnimationRepeat: (Animator) -> Unit = {}
    ) = apply {
    addListener(object : Animator.AnimatorListener {
    override fun onAnimationStart(animation: Animator) {
    onAnimationStart(animation)
    }
    override fun onAnimationEnd(animation: Animator) {
    onAnimationEnd(animation)
    }
    override fun onAnimationCancel(animation: Animator) {
    onAnimationCancel(animation)
    }
    override fun onAnimationRepeat(animation: Animator) {
    onAnimationRepeat(animation)
    }
    })
    }

    View Slide

  64. Animator Listener
    ObjectAnimator.ofFloat(this, "alpha", 1.0, 0.0)
    .setDuration(300)
    .setListener(onAnimationStart = { backgroundView.toVisible() })
    .start()

    View Slide

  65. Visibility
    fun View.toVisible() {
    visibility = View.VISIBLE
    }
    fun View.toInvisible() {
    visibility = View.INVISIBLE
    }
    fun View.toGone() {
    visibility = View.GONE
    }

    View Slide

  66. Visibility
    view.toGone()

    View Slide

  67. Apply margin
    fun View.applyMargin(left: Int = 0, top: Int = 0,
    right: Int = 0, bottom: Int = 0) =
    (layoutParams as ViewGroup.MarginLayoutParams).apply {
    leftMargin = left
    topMargin = top
    rightMargin = right
    bottomMargin = bottom
    }

    View Slide

  68. Apply margin
    view.applyMargin(top = 16, right = 48)

    View Slide