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

Krate ft. Moshi (Droidcon Italy 2020)

Marton Braun
November 27, 2020

Krate ft. Moshi (Droidcon Italy 2020)

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.

Marton Braun

November 27, 2020
Tweet

More Decks by Marton Braun

Other Decks in Programming

Transcript

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

    View full-size slide

  2. Krate
    AutSoft/Krate
    Moshi
    square/moshi

    View full-size slide

  3. Krate
    AutSoft/Krate

    View full-size slide

  4. Krate
    interface Krate {
    val sharedPreferences: SharedPreferences
    }

    View full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

  20. Krate
    AutSoft/Krate

    View full-size slide

  21. Moshi
    square/moshi

    View full-size slide

  22. Moshi
    › Gson v3 in everything but name

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  27. 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 full-size 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,
    )
    val moshi = Moshi.Builder().build()

    View full-size 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()
    val burgerAdapter =
    moshi.adapter(Burger::class.java)

    View full-size 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)
    val burger = burgerAdapter.fromJson(
    "{\"name\": \"Cheeseburger\"}"
    )

    View full-size 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\"}"
    )
    Exception in thread "main"
    com.squareup.moshi.JsonDataException:
    Required value 'description' missing at $

    View full-size 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\"}"
    )

    View full-size 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\"}"
    )
    Burger(name=Cheeseburger, description=null)
    ?,

    View full-size 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\"}"
    )
    › Default parameter values
    ?,

    View full-size slide

  35. 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 full-size 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\"}"
    )
    Burger(name=Cheeseburger, description=Yummy!)
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String
    )
    ,
    = "Yummy!"

    View full-size slide

  37. 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 full-size 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
    data class Burger(
    val name: String,
    val description: String = "Yummy!",
    )
    val moshi = Moshi.Builder().build()

    View full-size 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()
    .add(KotlinJsonAdapterFactory())
    .build()

    View full-size 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
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String = "Yummy!",
    )
    val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()

    View full-size 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
    › Always calls constructors
    @JsonClass(generateAdapter = true)
    data class Burger(
    val name: String,
    val description: String = "Yummy!",
    ) {
    init {
    println("$name coming up...")
    }
    }

    View full-size slide

  42. 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 full-size 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 full-size slide

  44. Storing complex data

    View full-size slide

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

    View full-size slide

  46. Gson delegate

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  49. 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 full-size slide

  50. 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 full-size 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?) {
    val gson = Gson()
    thisRef.sharedPreferences.edit()
    .putString(key, gson.toJson(value))
    .apply()
    }
    }
    fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key)
    }

    View full-size 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)
    }
    java.lang.ClassCastException:
    com.google.gson.internal.LinkedTreeMap
    cannot be cast to
    hu.autsoft.krate.gson.TestModel

    View full-size 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)
    }

    View full-size 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 full-size slide

  55. 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 full-size slide

  56. 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 full-size slide

  57. 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 full-size slide

  58. Gson delegate
    inline fun Krate.gsonPref(key: String): ReadWriteProperty {
    return GsonDelegate(key, 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()
    }
    }
    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

    View full-size slide

  59. 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 full-size slide

  60. Testing Krate

    View full-size slide

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

    View full-size slide

  62. 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 full-size 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)
    val krate = GsonTestKrate(targetContext)

    View full-size slide

  64. 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 full-size 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 full-size 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 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 full-size 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 full-size slide

  68. ft
    Krate Moshi

    View full-size slide

  69. 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 full-size slide

  70. 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 full-size slide

  71. 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 full-size slide

  72. 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 full-size 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.moshiPref(key: String): ReadWriteProperty {
    return MoshiDelegate(key, object : TypeToken() {}.type)
    }
    Moshi delegate

    View full-size slide

  74. 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
    , object : TypeToken() {}.type)

    View full-size slide

  75. 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 full-size slide

  76. java.lang.Class

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  81. 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 full-size slide

  82. 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 full-size slide

  83. 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 full-size slide

  84. java.lang.reflect.Type

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  87. 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 full-size slide

  88. 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 full-size slide

  89. Class Type
    No generics Supports generics
    Java reflection

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  92. kotlin.reflect.KClass

    View full-size slide

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

    View full-size slide

  94. 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 full-size slide

  95. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  105. 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 full-size slide

  106. 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 full-size slide

  107. 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 full-size slide

  108. 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 full-size slide

  109. 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 full-size slide

  110. 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 full-size 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 = MoshiTestKrate(targetContext)

    View full-size 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 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 full-size 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 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 full-size slide

  114. 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 full-size slide

  115. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  120. 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 full-size slide

  121. 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 full-size slide

  122. 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 full-size slide

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

    View full-size slide

  124. 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 full-size slide

  125. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  130. 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 full-size slide

  131. 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 full-size 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 full-size 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 full-size slide

  134. 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 full-size slide

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

    View full-size slide

  136. * 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 full-size slide

  137. * 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 full-size slide

  138. 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 full-size slide

  139. 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 full-size slide

  140. 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 full-size slide

  141. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  144. : 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 full-size slide

  145. 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 full-size slide

  146. 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 full-size slide

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

    View full-size slide

  148. 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 full-size slide

  149. 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 full-size slide

  150. 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 full-size slide

  151. 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 full-size slide

  152. 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 full-size slide

  153. 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 full-size slide

  154. 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 full-size slide

  155. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  158. 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 full-size slide

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

    View full-size slide

  160. Resources
    • 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

    View full-size slide

  161. Resources
    • 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/

    View full-size slide

  162. zsmb13
    zsmb.co/talks

    View full-size slide

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

    View full-size slide