Skip to content

Instantly share code, notes, and snippets.

@esensar
Created November 27, 2020 23:10
Show Gist options
  • Save esensar/4b1d317aec0fb5b20e5a022ed0487e7e to your computer and use it in GitHub Desktop.
Save esensar/4b1d317aec0fb5b20e5a022ed0487e7e to your computer and use it in GitHub Desktop.
Kotlin extensions way to handle fragment arguments
import java.io.Serializable
import java.util.UUID
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
abstract class ArgumentCompanion {
private val arguments: MutableMap<String, NullableArgument<*>> = hashMapOf()
fun <T : Serializable> ArgumentCompanion.nullableArgument(
key: String = UUID.randomUUID().toString(),
defaultValueProvider: () -> T? = { null }
): NullableArgumentPropertyDelegate<T> =
NullableArgumentPropertyDelegate(key, defaultValueProvider)
fun <T : Serializable> ArgumentCompanion.argument(
key: String = UUID.randomUUID().toString(),
defaultValueProvider: () -> T = { throw NullPointerException("Missing required value for argument $key") }
): ArgumentPropertyDelegate<T> = ArgumentPropertyDelegate(key, defaultValueProvider)
abstract inner class BaseArgumentPropertyDelegate<T : Serializable,
ARG_TYPE : NullableArgument<T>,
PROVIDER_TYPE : () -> T?>(
private val key: String,
private val defaultValueProvider: PROVIDER_TYPE,
private val argBuilder: (String, PROVIDER_TYPE) -> ARG_TYPE
) : ReadOnlyProperty<ArgumentCompanion, ARG_TYPE> {
override fun getValue(thisRef: ArgumentCompanion, property: KProperty<*>): ARG_TYPE {
return thisRef.arguments.getOrPut(key) {
argBuilder(
key,
defaultValueProvider
)
} as ARG_TYPE
}
}
inner class NullableArgumentPropertyDelegate<T : Serializable>(
key: String,
defaultValueProvider: () -> T?
) : BaseArgumentPropertyDelegate<T, NullableArgument<T>, () -> T?>(
key,
defaultValueProvider,
{ k, provider -> NullableArgumentImpl(k, provider) }
)
inner class ArgumentPropertyDelegate<T : Serializable>(
key: String,
defaultValueProvider: () -> T
) : BaseArgumentPropertyDelegate<T, Argument<T>, () -> T>(
key,
defaultValueProvider,
{ k, provider -> ArgumentImpl(k, provider) }
)
}
import androidx.fragment.app.Fragment
import java.io.Serializable
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
interface NullableArgument<T : Serializable> : ReadOnlyProperty<Fragment, T?> {
val key: String
fun getValue(fragRef: Fragment): T?
operator fun invoke(value: T): Pair<String, T>
override fun getValue(thisRef: Fragment, property: KProperty<*>): T? {
return getValue(thisRef)
}
}
interface Argument<T : Serializable> : NullableArgument<T> {
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
return getValue(thisRef)!!
}
}
internal open class NullableArgumentImpl<T : Serializable>(
override val key: String,
private val defaultValueProvider: () -> T?
) : NullableArgument<T> {
private var cachedValue: T? = null
override operator fun invoke(value: T): Pair<String, T> = key to value
override fun getValue(fragRef: Fragment): T? {
if (cachedValue == null) {
cachedValue = fragRef.getArgument(key, defaultValueProvider)
}
return cachedValue
}
}
internal class ArgumentImpl<T : Serializable>(
key: String,
defaultValueProvider: () -> T
) : Argument<T>, NullableArgumentImpl<T>(key, defaultValueProvider) {
override fun getValue(fragRef: Fragment): T {
return super<NullableArgumentImpl>.getValue(fragRef)!!
}
}
private fun <T : Serializable> Fragment.getArgument(
key: String,
defaultValue: () -> T?
): T? {
return (arguments?.getSerializable(key) ?: defaultValue().also {
arguments?.putSerializable(key, it)
}) as T?
}
abstract class BaseFragment : Fragment() {
val <T : Serializable> Argument<T>.value: T?
get() {
return this.getValue(this@BaseFragment)
}
}
class SourceFragment : BaseFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findNavController().navigate(
R.id.action_sourceFragment_to_targetFragment,
TargetFragment.Args("A title", "A subtitle")
)
}
}
class TargetFragment : BaseFragment() {
object Args : ArgumentCompanion() {
val title by argument<String>()
val subtitle by argument<String>()
operator fun invoke(title: String, subtitle: String) = bundleOf(
Args.title(title),
Args.subtitle(subtitle)
)
}
private val title by Args.title
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Title: $title")
println("Subtitle: ${Args.subtitle.value}")
findNavController().navigate(
R.id.action_targetFragment_to_targetFragmentTwo,
TargetFragmentTwo.Args.let {
bundleOf(
it.title("Second title"),
it.subtitle("Second subtitle")
)
}
)
}
}
class TargetFragmentTwo : BaseFragment() {
object Args : ArgumentCompanion() {
val title by argument<String>()
val subtitle by argument<String>()
}
private val title by Args.title
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Title: $title")
println("Subtitle: ${Args.subtitle.value}")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment