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

Krate ft. Moshi (Kotlin Budapest Meetup 2021 June)

Krate ft. Moshi (Kotlin Budapest Meetup 2021 June)

Krate is a SharedPreferences wrapper library based on Kotlin's delegates. This talk takes you on a journey where you'll learn how the new Moshi-based addon for the library was implemented. We'll touch on type erasure and reflection, testing, debugging, and various advanced Kotlin features along the way.

Resources & more info: https://zsmb.co/talks/krate-ft-moshi/

Marton Braun

June 29, 2021
Tweet

More Decks by Marton Braun

Other Decks in Programming

Transcript

  1. zsmb.co
    zsmb13
    A Kotlin library design case study
    ft
    Krate Moshi
    Márton Braun

    View Slide

  2. Krate
    AutSoft/Krate
    Moshi
    square/moshi

    View Slide

  3. Krate
    AutSoft/Krate

    View Slide

  4. Krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }

    View Slide

  5. Krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }
    abstract class SimpleKrate(context: Context) : Krate {
    override val sharedPreferences =
    PreferenceManager.getDefaultSharedPreferences(context)
    }

    View Slide

  6. interface Krate {
    val sharedPreferences: SharedPreferences
    }
    Krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var onboarded by booleanPref("onboarded")
    var appOpenCount by intPref("appOpenCount")
    var username by stringPref("username")
    }
    abstract class SimpleKrate(context: Context) : Krate {
    override val sharedPreferences =
    PreferenceManager.getDefaultSharedPreferences(context)
    }

    View Slide

  7. interface Krate {
    val sharedPreferences: SharedPreferences
    }
    Krate
    myKrate.onboarded = true
    myKrate.appOpenCount++
    myKrate.username = "t1gg3r"
    class MyKrate(context: Context) : SimpleKrate(context) {
    var onboarded by booleanPref("onboarded")
    var appOpenCount by intPref("appOpenCount")
    var username by stringPref("username")
    }
    abstract class SimpleKrate(context: Context) : Krate {
    override val sharedPreferences =
    PreferenceManager.getDefaultSharedPreferences(context)
    }

    View Slide

  8. Krate
    myKrate.onboarded = true
    myKrate.appOpenCount++
    myKrate.username = "t1gg3r"
    var onboarded by booleanPref("onboarded")
    var username by stringPref("username")
    abstract class SimpleKrate(context: Context) : Krate {
    override val sharedPreferences =
    PreferenceManager.getDefaultSharedPreferences(context)
    }
    }
    var appOpenCount by intPref("appOpenCount")
    class MyKrate(context: Context) : SimpleKrate(context) {

    View Slide

  9. Krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount by intPref("appOpenCount")
    }

    View Slide

  10. Krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount by IntDelegate("appOpenCount")
    }

    View Slide

  11. Krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount by IntDelegate("appOpenCount")
    }
    class IntDelegate(private val key: String)

    View Slide

  12. Krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount by IntDelegate("appOpenCount")
    }
    class IntDelegate(private val key: String) : ReadWriteProperty {
    }

    View Slide

  13. Krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount by IntDelegate("appOpenCount")
    }
    class IntDelegate(private val key: String) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    }
    }

    View Slide

  14. Krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount by IntDelegate("appOpenCount")
    }
    class IntDelegate(private val key: String) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    }
    }

    View Slide

  15. Krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount: Int by IntDelegate("appOpenCount")
    }
    class IntDelegate(private val key: String) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    }
    }

    View Slide

  16. Krate
    class IntDelegate(private val key: String) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    }
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount by IntDelegate("appOpenCount")
    }

    View Slide

  17. Krate
    class IntDelegate(private val key: String) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    return thisRef.sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    thisRef.sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount by IntDelegate("appOpenCount")
    }

    View Slide

  18. Krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount by IntDelegate("appOpenCount")
    }
    class IntDelegate(private val key: String) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    return thisRef.sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    thisRef.sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    fun Krate.intPref(key: String): ReadWriteProperty {
    return IntDelegate(key)
    }

    View Slide

  19. Krate
    class MyKrate(context: Context) : SimpleKrate(context) {
    var appOpenCount by intPref("appOpenCount")
    }
    class IntDelegate(private val key: String) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int {
    return thisRef.sharedPreferences.getInt(key, 0)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) {
    thisRef.sharedPreferences.edit().putInt(key, value).apply()
    }
    }
    fun Krate.intPref(key: String): ReadWriteProperty {
    return IntDelegate(key)
    }

    View Slide

  20. Krate
    AutSoft/Krate

    View Slide

  21. Moshi
    square/moshi

    View Slide

  22. Moshi

    View Slide

  23. Moshi
    › Gson v3 in everything but name

    View Slide

  24. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    Okio
    Retrofit
    OkHttp
    Moshi

    View Slide

  25. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    Okio
    Retrofit
    OkHttp
    Moshi

    View Slide

  26. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API

    View Slide

  27. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values

    View Slide

  28. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String,
    )

    View Slide

  29. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String,
    )
    val moshi = Moshi.Builder().build()

    View Slide

  30. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String,
    )
    val moshi = Moshi.Builder().build()
    val burgerAdapter =
    moshi.adapter(Burger::class.java)

    View Slide

  31. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String,
    )
    val moshi = Moshi.Builder().build()
    val burgerAdapter =
    moshi.adapter(Burger::class.java)
    val burger = burgerAdapter.fromJson(
    "{\"name\": \"Cheeseburger\"}"
    )

    View Slide

  32. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String,
    )
    val moshi = Moshi.Builder().build()
    val burgerAdapter =
    moshi.adapter(Burger::class.java)
    val burger = burgerAdapter.fromJson(
    "{\"name\": \"Cheeseburger\"}"
    )
    Exception in thread "main"
    com.squareup.moshi.JsonDataException:
    Required value 'description' missing at $

    View Slide

  33. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String?,
    )
    val moshi = Moshi.Builder().build()
    val burgerAdapter =
    moshi.adapter(Burger::class.java)
    val burger = burgerAdapter.fromJson(
    "{\"name\": \"Cheeseburger\"}"
    )

    View Slide

  34. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String
    )
    val moshi = Moshi.Builder().build()
    val burgerAdapter =
    moshi.adapter(Burger::class.java)
    val burger = burgerAdapter.fromJson(
    "{\"name\": \"Cheeseburger\"}"
    )
    Burger(name=Cheeseburger, description=null)
    ?,

    View Slide

  35. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String
    )
    val moshi = Moshi.Builder().build()
    val burgerAdapter =
    moshi.adapter(Burger::class.java)
    val burger = burgerAdapter.fromJson(
    "{\"name\": \"Cheeseburger\"}"
    )
    › Default parameter values
    ?,

    View Slide

  36. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    › Default parameter values
    val moshi = Moshi.Builder().build()
    val burgerAdapter =
    moshi.adapter(Burger::class.java)
    val burger = burgerAdapter.fromJson(
    "{\"name\": \"Cheeseburger\"}"
    )
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String
    )
    ,
    = "Yummy!"

    View Slide

  37. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    › Default parameter values
    val moshi = Moshi.Builder().build()
    val burgerAdapter =
    moshi.adapter(Burger::class.java)
    val burger = burgerAdapter.fromJson(
    "{\"name\": \"Cheeseburger\"}"
    )
    Burger(name=Cheeseburger, description=Yummy!)
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String
    )
    ,
    = "Yummy!"

    View Slide

  38. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    › Default parameter values
    › Codegen, reflection, or both
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String = "Yummy!",
    )
    val moshi = Moshi.Builder().build()

    View Slide

  39. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    › Default parameter values
    › Codegen, reflection, or both
    data class Burger(
    val name: String,
    val description: String = "Yummy!",
    )
    val moshi = Moshi.Builder().build()

    View Slide

  40. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    › Default parameter values
    › Codegen, reflection, or both
    data class Burger(
    val name: String,
    val description: String = "Yummy!",
    )
    val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()

    View Slide

  41. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    › Default parameter values
    › Codegen, reflection, or both
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String = "Yummy!",
    )
    val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()

    View Slide

  42. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    › Default parameter values
    › Codegen, reflection, or both
    › Always calls constructors
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String = "Yummy!",
    ) {
    init {
    println("$name coming up...")
    }
    }

    View Slide

  43. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    › Default parameter values
    › Always calls constructors
    › Named after Jake’s dog
    › Codegen, reflection, or both

    View Slide

  44. Moshi
    › Gson v3 in everything but name
    › Part of the Ok libraries
    › Streaming API
    › Nullable values
    › Default parameter values
    › Always calls constructors
    › Named after Jake’s dog
    › Codegen, reflection, or both

    View Slide

  45. Storing complex data

    View Slide

  46. DISCLAIMER
    Consider using a database before storing
    data like this in SharedPreferences

    View Slide

  47. Gson delegate

    View Slide

  48. Gson delegate
    fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key)
    }

    View Slide

  49. Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty
    fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key)
    }

    View Slide

  50. Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    }
    }
    fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key)
    }

    View Slide

  51. Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, object : TypeToken() {}.type)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    }
    }
    fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key)
    }

    View Slide

  52. Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, object : TypeToken() {}.type)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key)
    }

    View Slide

  53. Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, object : TypeToken() {}.type)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key)
    }
    java.lang.ClassCastException:
    com.google.gson.internal.LinkedTreeMap
    cannot be cast to
    hu.autsoft.krate.gson.TestModel

    View Slide

  54. Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, object : TypeToken() {}.type)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key)
    }

    View Slide

  55. Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, object : TypeToken() {}.type)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key)
    }

    View Slide

  56. Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): Object? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, object : TypeToken() {}.type)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Object?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key)
    }

    View Slide

  57. Object
    T
    Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, object : TypeToken() {}.type)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    inline fun ?> {
    return GsonDelegate(key)
    }
    Krate.gsonPref(key: String): ReadWriteProperty

    View Slide

  58. inline fun ?> {
    return GsonDelegate(key)
    }
    Object
    T
    Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, object : TypeToken() {}.type)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    Krate.gsonPref(key: String): ReadWriteProperty

    View Slide

  59. Gson delegate
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, object : TypeToken() {}.type)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    inline fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }

    View Slide

  60. inline fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    Gson delegate
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    class GsonDelegate(
    private val key: String,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, )
    private val type: Type,
    type

    View Slide

  61. Testing Krate

    View Slide

  62. Gson delegate tests
    data class TestModel(val x: Int, val y: Double, val z: String)

    View Slide

  63. Gson delegate tests
    class GsonTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by gsonPref("simpleValue")
    var listOfValues: List? by gsonPref("listOfValues")
    }
    data class TestModel(val x: Int, val y: Double, val z: String)

    View Slide

  64. Gson delegate tests
    class GsonTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by gsonPref("simpleValue")
    var listOfValues: List? by gsonPref("listOfValues")
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = GsonTestKrate(targetContext)

    View Slide

  65. Gson delegate tests
    class GsonTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by gsonPref("simpleValue")
    var listOfValues: List? by gsonPref("listOfValues")
    }
    @Test
    fun testSimpleValue() {
    val model = TestModel(x = 42, y = 3.141592, z = "shibboleth")
    krate.simpleValue = model
    assertEquals(model, krate.simpleValue)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = GsonTestKrate(targetContext)

    View Slide

  66. Gson delegate tests
    class GsonTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by gsonPref("simpleValue")
    var listOfValues: List? by gsonPref("listOfValues")
    }
    @Test
    fun testSimpleValue() {
    val model = TestModel(x = 42, y = 3.141592, z = "shibboleth")
    krate.simpleValue = model
    assertEquals(model, krate.simpleValue)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = GsonTestKrate(targetContext)

    View Slide

  67. Gson delegate tests
    class GsonTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by gsonPref("simpleValue")
    var listOfValues: List? by gsonPref("listOfValues")
    }
    @Test
    fun testListOfValues() {
    val list = listOf(
    TestModel(x = 42, y = 3.141592, z = "shibboleth"),
    TestModel(x = 13, y = 6.283185, z = "signal"),
    )
    krate.listOfValues = list
    assertEquals(list, krate.listOfValues)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = GsonTestKrate(targetContext)

    View Slide

  68. Gson delegate tests
    class GsonTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by gsonPref("simpleValue")
    var listOfValues: List? by gsonPref("listOfValues")
    }
    @Test
    fun testListOfValues() {
    val list = listOf(
    TestModel(x = 42, y = 3.141592, z = "shibboleth"),
    TestModel(x = 13, y = 6.283185, z = "signal"),
    )
    krate.listOfValues = list
    assertEquals(list, krate.listOfValues)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = GsonTestKrate(targetContext)

    View Slide

  69. ft
    Krate Moshi

    View Slide

  70. class GsonDelegate(
    private val key: String,
    private val type: Type,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val gson = Gson()
    val string = thisRef.sharedPreferences.getString(key, null)
    return gson.fromJson(string, type)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    inline fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    Moshi delegate

    View Slide

  71. inline fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    Moshi delegate
    gson Gson()
    =
    .fromJson(string )
    , type
    gson
    class MoshiDelegate(
    private val key: String,
    private val type: Type,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val
    val string = thisRef.sharedPreferences.getString(key, null)
    return
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }

    View Slide

  72. inline fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    Moshi delegate
    moshi.adapter(type)
    =
    adapter
    .fromJson(string)
    adapter
    class MoshiDelegate(
    private val key: String,
    private val type: Type,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val
    val string = thisRef.sharedPreferences.getString(key, null)
    return
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }

    View Slide

  73. class MoshiDelegate(
    private val key: String,
    private val type: Type,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val adapter = moshi.adapter(type)
    val string = thisRef.sharedPreferences.getString(key, null)
    return adapter.fromJson(string)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val adapter = moshi.adapter(type)
    thisRef.sharedPreferences.edit()
    .putString(key, adapter.toJson(value))
    .apply()
    }
    }
    inline fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key, object : TypeToken() {}.type)
    }
    Moshi delegate

    View Slide

  74. class MoshiDelegate(
    private val key: String,
    private val type: Type,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val adapter = moshi.adapter(type)
    val string = thisRef.sharedPreferences.getString(key, null)
    return adapter.fromJson(string)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val adapter = moshi.adapter(type)
    thisRef.sharedPreferences.edit()
    .putString(key, adapter.toJson(value))
    .apply()
    }
    }
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, object : TypeToken() {}.type)
    }
    Moshi delegate

    View Slide

  75. class MoshiDelegate(
    private val key: String,
    private val type: Type,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val adapter = moshi.adapter(type)
    val string = thisRef.sharedPreferences.getString(key, null)
    return adapter.fromJson(string)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val adapter = moshi.adapter(type)
    thisRef.sharedPreferences.edit()
    .putString(key, adapter.toJson(value))
    .apply()
    }
    }
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, object : TypeToken() {}.type)
    }
    Moshi delegate

    View Slide

  76. inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key,
    }
    class MoshiDelegate(
    private val key: String,
    private val type: Type,
    ) : ReadWriteProperty {
    override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? {
    val adapter = moshi.adapter(type)
    val string = thisRef.sharedPreferences.getString(key, null)
    return adapter.fromJson(string)
    }
    override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T?) {
    val adapter = moshi.adapter(type)
    thisRef.sharedPreferences.edit()
    .putString(key, adapter.toJson(value))
    .apply()
    }
    }
    Moshi delegate
    ???)

    View Slide

  77. Class

    View Slide

  78. java.lang.Class

    View Slide

  79. java.lang.Class
    classes and interfaces in a running Java application

    View Slide

  80. java.lang.Class
    classes and interfaces in a running Java application
    Class c1 = object.getClass();

    View Slide

  81. java.lang.Class
    classes and interfaces in a running Java application
    Class c1 = object.getClass(); any.javaClass // Kotlin

    View Slide

  82. java.lang.Class
    classes and interfaces in a running Java application
    Class c1 = object.getClass();
    Class c2 = String.class;
    any.javaClass // Kotlin

    View Slide

  83. java.lang.Class
    classes and interfaces in a running Java application
    Class c1 = object.getClass();
    Class c2 = String.class;
    any.javaClass
    "hello world".getClass(); // class java.lang.String
    new ArrayList().getClass(); // class java.util.ArrayList
    new Serializable() {}.getClass(); // class com.example.Main$1
    // Kotlin

    View Slide

  84. java.lang.Class
    classes and interfaces in a running Java application
    Class c1 = object.getClass();
    Class c2 = String.class;
    any.javaClass
    c.getName();
    c.getSimpleName();
    c.getCanonicalName();
    c.getTypeName();
    // Kotlin
    [I
    int[]
    int[]
    int[]
    java.lang.String
    String
    java.lang.String
    java.lang.String
    com.example.Main$1
    null
    com.example.Main$1

    View Slide

  85. java.lang.Class
    classes and interfaces in a running Java application
    Class c1 = object.getClass();
    Class c2 = String.class;
    any.javaClass
    c.getName();
    c.getSimpleName();
    c.getCanonicalName();
    c.getTypeName();
    Annotation[] annotations = c.getAnnotations();
    Class[] interfaces = c.getInterfaces();
    Constructor[] constructors = c.getConstructors();
    Field[] fields = c.getFields();
    Method[] methods = c.getMethods();
    // Kotlin
    [I
    int[]
    int[]
    int[]
    java.lang.String
    String
    java.lang.String
    java.lang.String
    com.example.Main$1
    null
    com.example.Main$1

    View Slide

  86. java.lang.reflect.Type

    View Slide

  87. java.lang.reflect.Type
    all types in the Java programming language
    - raw types
    - parameterized types
    - array types
    - type variables
    - primitive types

    View Slide

  88. java.lang.reflect.Type
    all types in the Java programming language
    - raw types
    - parameterized types
    - array types
    - type variables
    - primitive types
    String getTypeName();

    View Slide

  89. java.lang.reflect.Type
    all types in the Java programming language
    - raw types
    - parameterized types
    - array types
    - type variables
    - primitive types
    Class
    String getTypeName();

    View Slide

  90. java.lang.reflect.Type
    all types in the Java programming language
    - raw types
    - parameterized types
    - array types
    - type variables
    - primitive types
    Class ParameterizedType
    Type getRawType();
    Type[] getActualTypeArguments();
    String getTypeName();

    View Slide

  91. Class Type
    No generics Supports generics
    Java reflection

    View Slide

  92. Class Type
    No generics Supports generics
    Java reflection
    Kotlin reflection

    View Slide

  93. Class Type
    No generics Supports generics
    Java reflection
    Kotlin reflection
    KClass

    View Slide

  94. kotlin.reflect.KClass

    View Slide

  95. kotlin.reflect.KClass
    val k1: KClass<*> = any::class
    val k2: KClass<*> = String::class

    View Slide

  96. kotlin.reflect.KClass
    val simpleName: String?
    val qualifiedName: String?
    val members: Collection>
    val constructors: Collection>
    val nestedClasses: Collection>
    val typeParameters: List
    val supertypes: List
    val sealedSubclasses: List>
    val visibility: KVisibility?
    val k1: KClass<*> = any::class
    val k2: KClass<*> = String::class

    View Slide

  97. kotlin.reflect.KClass
    val k1: KClass<*> = any::class
    val k2: KClass<*> = String::class
    val isFinal: Boolean
    val isOpen: Boolean
    val isAbstract: Boolean
    val isSealed: Boolean
    val isData: Boolean
    val isInner: Boolean
    val isCompanion: Boolean
    val isFun: Boolean

    View Slide

  98. val c: Class<*> = String::class.java
    kotlin.reflect.KClass
    val k1: KClass<*> = any::class
    val k2: KClass<*> = String::class

    View Slide

  99. val c: Class<*> = String::class.java
    kotlin.reflect.
    val k1: KClass<*> = any::class
    val k2: KClass<*> = String::class
    String.class // Java
    // Kotlin
    KClass

    View Slide

  100. Class Type
    No generics Supports generics
    Java reflection
    Kotlin reflection
    KClass

    View Slide

  101. Class Type
    No generics Supports generics
    Java reflection
    Kotlin reflection
    KClass KType
    ParameterizedType

    View Slide

  102. Class Type
    No generics Supports generics
    Java reflection
    Kotlin reflection
    KClass KType
    ParameterizedType

    View Slide

  103. Class Type
    No generics Supports generics
    Java reflection
    Kotlin reflection
    KClass KType
    rawType
    actualTypeArguments
    ParameterizedType

    View Slide

  104. Class Type
    No generics Supports generics
    Java reflection
    Kotlin reflection
    KClass KType
    rawType
    actualTypeArguments
    ParameterizedType
    classifier
    arguments

    View Slide

  105. rawType
    actualTypeArguments
    ParameterizedType
    classifier
    arguments
    isMarkedNullable
    Class Type
    No generics Supports generics
    Java reflection
    Kotlin reflection
    KClass KType

    View Slide

  106. rawType
    actualTypeArguments
    ParameterizedType
    classifier
    arguments
    Class Type
    No generics Supports generics
    Java reflection
    Kotlin reflection
    KClass KType

    View Slide

  107. Looking for a Type
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, ???)
    }
    class MoshiDelegate(
    private val key: String,
    private val type: Type,
    ) : ReadWriteProperty {
    ...
    }

    View Slide

  108. Looking for a Type
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    class MoshiDelegate(
    private val key: String,
    private val type: Type,
    ) : ReadWriteProperty {
    ...
    }

    View Slide

  109. Looking for a Type
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    class MoshiDelegate(
    private val key: String,
    private val type: Type,
    ) : ReadWriteProperty {
    ...
    }

    View Slide

  110. Moshi delegate tests
    class GsonTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by gsonPref("simpleValue")
    var listOfValues: List? by gsonPref("listOfValues")
    }
    @Test
    fun testSimpleValue() {
    val model = TestModel(x = 42, y = 3.141592, z = "shibboleth")
    krate.simpleValue = model
    assertEquals(model, krate.simpleValue)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = GsonTestKrate(targetContext)

    View Slide

  111. Moshi delegate tests
    class MoshiTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by moshiPref("simpleValue")
    var listOfValues: List? by moshiPref("listOfValues")
    }
    @Test
    fun testSimpleValue() {
    val model = TestModel(x = 42, y = 3.141592, z = "shibboleth")
    krate.simpleValue = model
    assertEquals(model, krate.simpleValue)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = GsonTestKrate(targetContext)

    View Slide

  112. Moshi delegate tests
    class MoshiTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by moshiPref("simpleValue")
    var listOfValues: List? by moshiPref("listOfValues")
    }
    @Test
    fun testSimpleValue() {
    val model = TestModel(x = 42, y = 3.141592, z = "shibboleth")
    krate.simpleValue = model
    assertEquals(model, krate.simpleValue)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = MoshiTestKrate(targetContext)

    View Slide

  113. Moshi delegate tests
    class MoshiTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by moshiPref("simpleValue")
    var listOfValues: List? by moshiPref("listOfValues")
    }
    @Test
    fun testSimpleValue() {
    val model = TestModel(x = 42, y = 3.141592, z = "shibboleth")
    krate.simpleValue = model
    assertEquals(model, krate.simpleValue)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = MoshiTestKrate(targetContext)

    View Slide

  114. Moshi delegate tests
    class MoshiTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by moshiPref("simpleValue")
    var listOfValues: List? by moshiPref("listOfValues")
    }
    @Test
    fun testListOfValues() {
    val list = listOf(
    TestModel(x = 42, y = 3.141592, z = "shibboleth"),
    TestModel(x = 13, y = 6.283185, z = "signal"),
    )
    krate.listOfValues = list
    assertEquals(list, krate.listOfValues)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = MoshiTestKrate(targetContext)

    View Slide

  115. Moshi delegate tests
    class MoshiTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by moshiPref("simpleValue")
    var listOfValues: List? by moshiPref("listOfValues")
    }
    @Test
    fun testListOfValues() {
    val list = listOf(
    TestModel(x = 42, y = 3.141592, z = "shibboleth"),
    TestModel(x = 13, y = 6.283185, z = "signal"),
    )
    krate.listOfValues = list
    assertEquals(list, krate.listOfValues)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = MoshiTestKrate(targetContext)

    View Slide

  116. class MoshiTestKrate(context: Context) : SimpleKrate(context) {
    var simpleValue: TestModel? by moshiPref("simpleValue")
    var listOfValues: List? by moshiPref("listOfValues")
    }
    @Test
    fun testListOfValues() {
    val list = listOf(
    TestModel(x = 42, y = 3.141592, z = "shibboleth"),
    TestModel(x = 13, y = 6.283185, z = "signal"),
    )
    krate.listOfValues = list
    assertEquals(list, krate.listOfValues)
    }
    data class TestModel(val x: Int, val y: Double, val z: String)
    val krate = MoshiTestKrate(targetContext)
    Moshi delegate tests
    java.lang.AssertionError:
    Expected :[
    TestModel(x=42, y=3.141592, z=shibboleth),
    TestModel(x=13, y=6.283185, z=signal)
    ]
    Actual :[
    {x=42.0, y=3.141592, z=shibboleth},
    {x=13.0, y=6.283185, z=signal}
    ]

    View Slide

  117. java.lang.AssertionError:
    Expected :[
    TestModel(x=42, y=3.141592, z=shibboleth),
    TestModel(x=13, y=6.283185, z=signal)
    ]
    Actual :[
    {x=42.0, y=3.141592, z=shibboleth},
    {x=13.0, y=6.283185, z=signal}
    ]

    View Slide

  118. Actual :[
    {x=42.0, y=3.141592, z=shibboleth},
    {x=13.0, y=6.283185, z=signal}
    ]

    View Slide

  119. inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    Moshi delegate tests

    View Slide

  120. inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    Moshi delegate tests

    View Slide

  121. inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    Moshi delegate tests

    View Slide

  122. var simpleValue by moshiPref("simpleValue")
    var listOfValues by moshiPref>("listOfValues")
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    Moshi delegate tests

    View Slide

  123. var simpleValue by moshiPref("simpleValue")
    var listOfValues by moshiPref>("listOfValues")
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    class hu.autsoft.krate.moshi.TestModel
    Moshi delegate tests

    View Slide

  124. class hu.autsoft.krate.moshi.TestModel
    interface java.util.List
    var simpleValue by moshiPref("simpleValue")
    var listOfValues by moshiPref>("listOfValues")
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    Moshi delegate tests

    View Slide

  125. inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    Looking for a Type

    View Slide

  126. inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    Types.newParameterizedType(List::class.java, TestModel::class.java)
    Looking for a Type

    View Slide

  127. inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, T::class.java)
    }
    Types.newParameterizedType(List::class.java, TestModel::class.java)
    java.util.List
    Looking for a Type

    View Slide

  128. inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(
    key,
    Types.newParameterizedType(T::class.java, ???::class.java)
    )
    }
    Types.newParameterizedType(List::class.java, TestModel::class.java)
    java.util.List
    Looking for a Type

    View Slide

  129. Looking for a Type
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key,
    }
    )
    ???

    View Slide

  130. Looking for a Type
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, object : TypeToken() {}.type)
    }

    View Slide

  131. TypeToken object : TypeToken() {}.type

    View Slide

  132. TypeToken
    public class TypeToken {
    final Type type = getSuperclassTypeParameter(getClass());
    protected TypeToken() {}
    static Type getSuperclassTypeParameter(Class> subclass) {
    Type superclass = subclass.getGenericSuperclass();
    if (superclass instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    return canonicalize(parameterized.getActualTypeArguments()[0]);
    }
    /**
    * Returns a type that is functionally equal but not necessarily equal
    * according to Object.equals().
    */
    public static Type canonicalize(Type type) { ... }
    }
    object : TypeToken() {}.type

    View Slide

  133. TypeToken
    public class TypeToken {
    final Type type = getSuperclassTypeParameter(getClass());
    protected TypeToken() {}
    static Type getSuperclassTypeParameter(Class> subclass) {
    Type superclass = subclass.getGenericSuperclass();
    if (superclass instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    return canonicalize(parameterized.getActualTypeArguments()[0]);
    }
    /**
    * Returns a type that is functionally equal but not necessarily equal
    * according to Object.equals().
    */
    public static Type canonicalize(Type type) { ... }
    }
    object : TypeToken() {}.type

    View Slide

  134. TypeToken
    public class TypeToken {
    final Type type = getSuperclassTypeParameter(getClass());
    protected TypeToken() {}
    static Type getSuperclassTypeParameter(Class> subclass) {
    Type superclass = subclass.getGenericSuperclass();
    if (superclass instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    return canonicalize(parameterized.getActualTypeArguments()[0]);
    }
    /**
    * Returns a type that is functionally equal but not necessarily equal
    * according to Object.equals().
    */
    public static Type canonicalize(Type type) { ... }
    }
    object : TypeToken() {}.type

    View Slide

  135. TypeToken
    public class TypeToken {
    final Type type = getSuperclassTypeParameter(getClass());
    protected TypeToken() {}
    static Type getSuperclassTypeParameter(Class> subclass) {
    Type superclass = subclass.getGenericSuperclass();
    if (superclass instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    return canonicalize(parameterized.getActualTypeArguments()[0]);
    }
    /**
    * Returns a type that is functionally equal but not necessarily equal
    * according to Object.equals().
    */
    public static Type canonicalize(Type type) { ... }
    }
    object : TypeToken() {}.type

    View Slide

  136. TypeToken
    public class TypeToken {
    final Type type = getSuperclassTypeParameter(getClass());
    protected TypeToken() {}
    static Type getSuperclassTypeParameter(Class> subclass) {
    Type superclass = subclass.
    if (superclass instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    return canonicalize(parameterized.getActualTypeArguments()[0]);
    }
    /**
    * Returns a type that is functionally equal but not necessarily equal
    * according to Object.equals().
    */
    public static Type canonicalize(Type type) { ... }
    }
    object : TypeToken() {}.type
    getGenericSuperclass();

    View Slide

  137. getGenericSuperclass();
    * ...
    */
    public Type
    /**
    * Returns the Type representing the direct superclass of the entity
    * represented by this Class.
    *

    View Slide

  138. * If the superclass is a parameterized type, the Type object returned must
    * accurately reflect the actual type parameters used in the source code.
    *
    getGenericSuperclass();
    * ...
    */
    public Type
    /**
    * Returns the Type representing the direct superclass of the entity
    * represented by this Class.
    *

    View Slide

  139. * Non-parameterized -> Class
    * Parameterized -> ParameterizedType
    * If the superclass is a parameterized type, the Type object returned must
    * accurately reflect the actual type parameters used in the source code.
    *
    getGenericSuperclass();
    * ...
    */
    public Type
    /**
    * Returns the Type representing the direct superclass of the entity
    * represented by this Class.
    *

    View Slide

  140. TypeToken
    public class TypeToken {
    final Type type = getSuperclassTypeParameter(getClass());
    protected TypeToken() {}
    static Type getSuperclassTypeParameter(Class> subclass) {
    Type superclass = subclass.
    if (superclass instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    return canonicalize(parameterized.getActualTypeArguments()[0]);
    }
    /**
    * Returns a type that is functionally equal but not necessarily equal
    * according to Object.equals().
    */
    public static Type canonicalize(Type type) { ... }
    }
    object : TypeToken() {}.type
    getGenericSuperclass();

    View Slide

  141. TypeToken
    public class TypeToken {
    final Type type = getSuperclassTypeParameter(getClass());
    protected TypeToken() {}
    static Type getSuperclassTypeParameter(Class> subclass) {
    Type superclass = subclass.getGenericSuperclass();
    if (superclass instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    return canonicalize(parameterized.getActualTypeArguments()[0]);
    }
    /**
    * Returns a type that is functionally equal but not necessarily equal
    * according to Object.equals().
    */
    public static Type canonicalize(Type type) { ... }
    }
    object : TypeToken() {}.type

    View Slide

  142. TypeToken
    public class TypeToken {
    final Type type = getSuperclassTypeParameter(getClass());
    protected TypeToken() {}
    static Type getSuperclassTypeParameter(Class> subclass) {
    Type superclass = subclass.getGenericSuperclass();
    if (superclass instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    return canonicalize(parameterized.getActualTypeArguments()[0]);
    }
    /**
    * Returns a type that is functionally equal but not necessarily equal
    * according to Object.equals().
    */
    public static Type canonicalize(Type type) { ... }
    }
    object : TypeToken() {}.type

    View Slide

  143. TypeToken object : TypeToken() {}.type
    public class TypeToken {
    final Type type = getSuperclassTypeParameter(getClass());
    protected TypeToken() {}
    static Type getSuperclassTypeParameter(Class> subclass) {
    Type superclass = subclass.getGenericSuperclass();
    if (superclass instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    parameterized.getActualTypeArguments()[0]
    canonicalize( );
    return
    }
    }
    /**
    * Returns a type that is functionally equal but not necessarily equal
    * according to Object.equals().
    */
    public static Type canonicalize(Type type) { ... }

    View Slide

  144. parameterized.getActualTypeArguments()[0]
    TypeToken object : TypeToken() {}.type
    ;
    public class TypeToken {
    final Type type = getSuperclassTypeParameter(getClass());
    protected TypeToken() {}
    static Type getSuperclassTypeParameter(Class> subclass) {
    Type superclass = subclass.getGenericSuperclass();
    if (superclass instanceof Class) {
    throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    return
    }
    }

    View Slide

  145. open constructor
    val
    fun : * :
    val
    is <*>
    val as
    getGenericSuperclass()
    getActualTypeArguments()
    TypeToken object : TypeToken() {}.type
    subclass
    getSuperclassTypeParameter Class< >) Type
    (
    type = getSuperclassTypeParameter( )
    class TypeToken
    {
    {
    {
    }
    }
    }
    return parameterized.
    parameterized =
    ()
    protected
    javaClass
    if (superclass Class
    superclass = subclass.
    )
    superclass ParameterizedType
    RuntimeException("Missing type parameter.")
    throw
    [0]
    actualTypeArguments
    genericSuperclass

    View Slide

  146. : Type requireNotNull token::class.java
    if (superclass is Class<*>) {
    throw RuntimeException("Missing type parameter.")
    }
    val parameterized = superclass as ParameterizedType
    return parameterized.actualTypeArguments[0]
    }
    TypeToken object : TypeToken() {}.type
    abstract
    ): Type {
    .genericSuperclass
    ( )
    class TypeToken
    val token = object : TypeToken() {}
    inline makeType
    =
    val superclass
    (
    fun

    View Slide

  147. TypeToken makeType()
    : Type requireNotNull token::class.java
    if (superclass is Class<*>) {
    throw RuntimeException("Missing type parameter.")
    }
    val parameterized = superclass as ParameterizedType
    return parameterized.actualTypeArguments[0]
    }
    abstract
    ): Type {
    .genericSuperclass
    ( )
    class TypeToken
    val token = object : TypeToken() {}
    inline makeType
    =
    val superclass
    (
    fun

    View Slide

  148. TypeToken
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, )
    }
    makeType()
    : Type requireNotNull token::class.java
    if (superclass is Class<*>) {
    throw RuntimeException("Missing type parameter.")
    }
    val parameterized = superclass as ParameterizedType
    return parameterized.actualTypeArguments[0]
    }
    abstract
    ): Type {
    .genericSuperclass
    ( )
    class TypeToken
    val token = object : TypeToken() {}
    inline makeType
    =
    val superclass
    (
    fun

    View Slide

  149. TypeToken

    View Slide

  150. typeOf
    inline fun makeType(): Type
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, makeType())
    }

    View Slide

  151. typeOf
    inline fun makeType(): Type
    /**
    * Returns a runtime representation of the given
    * reified type [T] as an instance of [KType].
    */
    inline fun typeOf(): KType
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, makeType())
    }

    View Slide

  152. typeOf
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, typeOf())
    }
    /**
    * Returns a runtime representation of the given
    * reified type [T] as an instance of [KType].
    */
    inline fun typeOf(): KType

    View Slide

  153. typeOf
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, typeOf())
    }
    /**
    * Returns a runtime representation of the given
    * reified type [T] as an instance of [KType].
    */
    inline fun typeOf(): KType

    View Slide

  154. typeOf
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, typeOf())
    }
    /**
    * Returns a runtime representation of the given
    * reified type [T] as an instance of [KType].
    */
    inline fun typeOf(): KType
    /**
    * Returns a Java [Type] instance corresponding
    * to the given Kotlin type.
    */
    val KType.javaType: Type

    View Slide

  155. typeOf
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, typeOf().javaType)
    }
    /**
    * Returns a runtime representation of the given
    * reified type [T] as an instance of [KType].
    */
    inline fun typeOf(): KType
    /**
    * Returns a Java [Type] instance corresponding
    * to the given Kotlin type.
    */
    val KType.javaType: Type

    View Slide

  156. typeOf
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, typeOf().javaType)
    }
    /**
    * Returns a runtime representation of the given
    * reified type [T] as an instance of [KType].
    */
    inline fun typeOf(): KType
    /**
    * Returns a Java [Type] instance corresponding
    * to the given Kotlin type.
    */
    val KType.javaType: Type
    NotImplementedError:
    Java type is not yet supported for types
    created with createType

    View Slide

  157. typeOf
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, typeOf().javaType)
    }
    /**
    * Returns a runtime representation of the given
    * reified type [T] as an instance of [KType].
    */
    /**
    * Returns a Java [Type] instance corresponding
    * to the given Kotlin type.
    */
    val KType.javaType: Type
    inline fun typeOf(): KType
    NotImplementedError:
    Java type is not yet supported for types
    created with createType

    View Slide

  158. makeType
    typeOf
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, typeOf().javaType)
    }
    inline fun typeOf(): KType
    val KType.javaType: Type
    fun KClassifier.createType(...): KType {
    ...
    return KTypeImpl(kotlinType) {
    TODO("Java type is not yet supported for types created with createType")
    }
    }

    View Slide

  159. typeOf
    inline fun typeOf(): KType
    val KType.javaType: Type
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, makeType())
    }

    View Slide

  160. typeOf
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, typeOf().javaType)
    }
    inline fun typeOf(): KType
    val KType.javaType: Type

    View Slide

  161. typeOf
    @OptIn(ExperimentalStdlibApi::class)
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, typeOf().javaType)
    }
    inline fun typeOf(): KType
    val KType.javaType: Type

    View Slide

  162. typeOf
    @OptIn(ExperimentalStdlibApi::class)
    inline fun Krate.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, typeOf().javaType)
    }
    inline fun typeOf(): KType
    val KType.javaType: Type

    View Slide

  163. Resources

    View Slide

  164. • Krate
     https://github.com/AutSoft/Krate
    • Moshi
     https://github.com/square/moshi
    Resources

    View Slide

  165. • Krate
     https://github.com/AutSoft/Krate
    • Moshi
     https://github.com/square/moshi
    • Delightful Delegate Design
     https://www.droidcon.com/media-detail?video=481186381
    • Say “hi” to Moshi!
     https://www.youtube.com/watch?v=qadYVYc-8mc
    Resources

    View Slide

  166. • Krate
     https://github.com/AutSoft/Krate
    • Moshi
     https://github.com/square/moshi
    • Delightful Delegate Design
     https://www.droidcon.com/media-detail?video=481186381
    • Say “hi” to Moshi!
     https://www.youtube.com/watch?v=qadYVYc-8mc
    • Library design articles
     https://zsmb.co/maintaining-compatibility-in-kotlin-libraries/
     https://zsmb.co/mastering-api-visibility-in-kotlin/
     https://zsmb.co/talks/how-to-build-awesome-android-libraries/
    Resources

    View Slide

  167. zsmb13
    zsmb.co/talks

    View Slide

  168. ft
    Krate Moshi
    A Kotlin library design case study
    zsmb.co/talks
    zsmb13
    Márton Braun
    › Krate is great
    › Moshi is awesome
    › Type erasure is evil
    › Tests are your friend

    View Slide