Skip to content

Instantly share code, notes, and snippets.

@yanngx
Last active January 19, 2023 09:26
Show Gist options
  • Save yanngx/efdfbf777d21d6f0e73fab4efe47e924 to your computer and use it in GitHub Desktop.
Save yanngx/efdfbf777d21d6f0e73fab4efe47e924 to your computer and use it in GitHub Desktop.
Fragment arguments without hassle !
package be.brol
import android.os.Binder
import android.os.Bundle
import android.support.v4.app.BundleCompat
import android.support.v4.app.Fragment
/**
* Eases the Fragment.newInstance ceremony by marking the fragment's args with this delegate
* Just write the property in newInstance and read it like any other property after the fragment has been created
*
* Inspired by Adam Powell, he mentioned it during his IO/17 talk about Kotlin
*/
class FragmentArgumentDelegate<T : Any> : kotlin.properties.ReadWriteProperty<Fragment, T> {
var value: T? = null
override operator fun getValue(thisRef: android.support.v4.app.Fragment, property: kotlin.reflect.KProperty<*>): T {
if (value == null) {
val args = thisRef.arguments ?: throw IllegalStateException("Cannot read property ${property.name} if no arguments have been set")
@Suppress("UNCHECKED_CAST")
value = args.get(property.name) as T
}
return value ?: throw IllegalStateException("Property ${property.name} could not be read")
}
override operator fun setValue(thisRef: android.support.v4.app.Fragment, property: kotlin.reflect.KProperty<*>, value: T) {
if (thisRef.arguments == null) thisRef.arguments = android.os.Bundle()
val args = thisRef.arguments
val key = property.name
when (value) {
is String -> args.putString(key, value)
is Int -> args.putInt(key, value)
is Short -> args.putShort(key, value)
is Long -> args.putLong(key, value)
is Byte -> args.putByte(key, value)
is ByteArray -> args.putByteArray(key, value)
is Char -> args.putChar(key, value)
is CharArray -> args.putCharArray(key, value)
is CharSequence -> args.putCharSequence(key, value)
is Float -> args.putFloat(key, value)
is Bundle -> args.putBundle(key, value)
is Binder -> BundleCompat.putBinder(args, key, value)
is android.os.Parcelable -> args.putParcelable(key, value)
is java.io.Serializable -> args.putSerializable(key, value)
else -> throw IllegalStateException("Type ${value.javaClass.canonicalName} of property ${property.name} is not supported")
}
}
}
package be.brol
import android.os.Bundle
import android.support.v4.app.Fragment
import android.widget.Toast
/**
* Example usage of FragmentArgumentDelegate
*/
class WeatherCityFragment : Fragment() {
private var cityId by FragmentArgumentDelegate<String>()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
Toast.makeText(activity, cityId, Toast.LENGTH_SHORT).show()
}
companion object {
fun newInstance(cityId: String) = WeatherCityFragment().apply {
this.cityId = cityId
}
}
}
@kyhule
Copy link

kyhule commented Sep 14, 2018

This is a great solution. How would you handle optional args though? Seems like there is no way to have nullable properties from args this way.

@hixguru
Copy link

hixguru commented Oct 24, 2018

If you are using android-ktx you can do it a litter simpler.

    operator fun setValue(fragment: Fragment, property: KProperty<*>, value: T) {
        if (null == fragment.arguments) {
            fragment.arguments = Bundle()
        }

        val args = fragment.arguments!!
        val key = property.name

        args.putAll(bundleOf(key to value))
    }

@igorwojda
Copy link

igorwojda commented Nov 24, 2018

@hixguru good tip, however usage of !! is very ugly. Fortunately we can write code without it.

    override operator fun setValue(fragment: Fragment, property: KProperty<*>, value: T) {
        val key = property.name
        val args = fragment.arguments ?: Bundle()
        args.putAll(bundleOf(key to value))
        fragment.arguments = args
    }

or

    override operator fun setValue(fragment: Fragment, property: KProperty<*>, value: T) {
        val key = property.name
        (fragment.arguments ?: Bundle()).also {
            it.putAll(bundleOf(key to value))
            fragment.arguments = it
        }
    }

@ammojamo
Copy link

This is great - one question though, shouldn't setValue() update this.value? Otherwise a subsequent call to getValue() could return a stale value?

@kibotu
Copy link

kibotu commented Feb 25, 2019

how about declaring optional/nullable arguments?

e.g.:

var cityId by FragmentArgumentDelegate<String?>()

@mochadwi
Copy link

mochadwi commented Jul 6, 2020

this approach is using reflection, wouldn't it hurt performance? @yanngx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment