Как стать автором
Обновить

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

Время на прочтение2 мин
Количество просмотров6.4K
image

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

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

вместо

@State String carName;

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

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

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). Даже искал место, где бы можно их искусственно засунуть, чтоб попробовать. Вот, нашёл =) Всем успехов!
Теги:
Хабы:
Всего голосов 11: ↑9 и ↓2+7
Комментарии6

Публикации

Истории

Работа

Ближайшие события

19 августа – 20 октября
RuCode.Финал. Чемпионат по алгоритмическому программированию и ИИ
МоскваНижний НовгородЕкатеринбургСтавропольНовосибрискКалининградПермьВладивостокЧитаКраснорскТомскИжевскПетрозаводскКазаньКурскТюменьВолгоградУфаМурманскБишкекСочиУльяновскСаратовИркутскДолгопрудныйОнлайн
24 – 25 октября
One Day Offer для AQA Engineer и Developers
Онлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
26 октября
ProIT Network Fest
Санкт-Петербург
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань