📜 ⬆️ ⬇️

Getting rid of fragment state saving libraries using pure kotlin

image

Android auxiliary code generation libraries, such as Android Annotations or my favorite Icepick, which developers are used to using to simplify writing, were not ready to immediately make friends with Kotlin-code, since most of them require you to keep the fields with package modifier private. Of course it's okay to write

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

instead
')
 @State String carName; 

But it is better to remember that Kotlin came to us to simplify the code, and not vice versa.

For this we will use the mechanism of delegates . We will need the following class:

 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 } } } 

It takes the input Bundle, in which it stores the field, and the cache variable, so as not to constantly pull from the Bundle.

Further, to obtain the nallable and notnull fields, the implementation will be different:

 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 } } 

For notNull fields, you will need to pass a default value.
Now, in the fragment we create a field with an empty Bundle, which will store all our fields, I placed it in the base fragment.

 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) } 

It remains to add two functions:

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

Everything! No code generation, no reflection, you can use private fields.

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

ps When I read about the delegates at Kotlin, the topic immediately grabbed me, but I did not know how to apply them, except for obvious situations for which the standard library has a ready implementation (lazy and observer). I even looked for a place where I could shove them artificially to try. Here, I found =) All the success!

Source: https://habr.com/ru/post/336994/


All Articles