
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). Даже искал место, где бы можно их искусственно засунуть, чтоб попробовать. Вот, нашёл =) Всем успехов!
