- PVSM.RU - https://www.pvsm.ru -

Избавляемся от библиотек сохранения состояния фрагмента с помощью чистого kotlin

image

Android библиотеки вспомогательной кодогенерации, такие как Android Annotations или мой любимый Icepick, которые разработчики привыкли использовать для упрощения написания, не готовы были сразу подружиться с Kotlin-кодом, так как большинство из них требует держать поля с модификатором package private. Конечно, ничего страшного писать

@JvmField @State
internal var carName: String? = null

вместо

@State String carName;

Но лучше вспомнить, что Kotlin к нам пришёл для упрощением кода, а не наоборот.

Для этого мы воспользуемся механизмом делегатов [1]. Нам потребуется следующий класс:

abstract class InstanceStateProvider<T>(protected val savable: Bundle) {

    protected var cache: T? = null

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        cache = value
        if (value == null) return
        when (value) {
            is Int -> savable.putInt(property.name, value)
            is Long -> savable.putLong(property.name, value)
            is Float -> savable.putFloat(property.name, value)
            is String -> savable.putString(property.name, value)
            is Bundle -> savable.putBundle(property.name, value)
            is Parcelable -> savable.putParcelable(property.name, value)
            // whatever you want
        }
    }
}

Он принимает на вход Bundle, в которое сохраняет поле, и переменную cache, чтоб не дёргать постоянно из Bundle.
Далее, для получения поля nallable и notnull реализации будут разичаться:

class Nullable<T>(savable: Bundle) : InstanceStateProvider<T>(savable) {
        operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
            if (cache != null) return cache
            if (!savable.containsKey(property.name)) return null
            return savable.get(property.name) as T
        }
    }

    class NotNull<T>(savable: Bundle, private val defaultValue: T) : InstanceStateProvider<T>(savable) {
        operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
            return cache ?: savable.get(property.name) as T ?: defaultValue
        }
    }

Для notNull полей потребуется передавать значение по умолчанию.
Теперь, во фрагменте мы создаём поле с пустым Bundle, которое будет хранить все наши поля, я разместил его в базовом фрагменте.

private val savable = Bundle()
override fun onCreate(savedInstanceState: Bundle?) {
        if(savedInstanceState != null) {
            savable.putAll(savedInstanceState.getBundle("_state"))
        }
        super.onCreate(savedInstanceState)
    }
override fun onSaveInstanceState(outState: Bundle) {
        outState.putBundle("_state", savable)
        super.onSaveInstanceState(outState)
    }

Осталось добавить две функции:

    protected fun <T> instanceState() = InstanceStateProvider.Nullable<T>(savable)
    protected fun <T> instanceState(defaultValue: T) = InstanceStateProvider.NotNull(savable, defaultValue)

Всё! Никакой кодогенерации, никакого рефлекшена, можно использовать приватные поля.

private var carName: String? by instanceState()
private var index by instanceState(0) 

p.s. Когда я читал про делегаты в Kotlin, меня тема сразу захватила, но я тогда не знал, как можно их применить, кроме очевидных ситуаций, для которых в стандартной библиотеке есть готовая реализация (lazy и observer). Даже искал место, где бы можно их искусственно засунуть, чтоб попробовать. Вот, нашёл =) Всем успехов!

Автор: Николай

Источник [2]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/android-development/263226

Ссылки в тексте:

[1] механизмом делегатов: https://kotlinlang.org/docs/reference/delegated-properties.html

[2] Источник: https://habrahabr.ru/post/336994/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best