Created
December 5, 2019 04:32
-
-
Save elihart/74e465676ff6663729f52690661e37e0 to your computer and use it in GitHub Desktop.
Rough example code of a system for declaring routes for Fragments and their argument types. Allows for easily loading the Fragments individually or in a new activity.. Assumes usage with MvRx, but can be used with normal Fragments and Activities with basic modification.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The tools below can be used to easily start fragments and activities across module boundaries. | |
// Example usage - a "directory" is declared like this object. | |
// Then you can use it to: | |
// Create a new fragment: Search.results().newInstance(fragmentArguments) | |
// Create an intent for the Fragment: Search.results().newIntent(context, fragmentArguments) | |
// Start an intent for the Fragment: Search.results().startActivity(context, fragmentArguments) | |
object Search : Fragments("com.example.android.search") { | |
fun results() = create<SearchArguments>("SearchResultsFragment") | |
fun settings() = create("SearchSettingsFragment") | |
fun mapResults() = create("MapResultsFragment") | |
} | |
/** | |
* Usage: Create an "object" implementation to hold the fragments that live in another directory. | |
* The fully qualified fragment name is provided as a string, and the fragment is later looked up with reflection. | |
* This enables fragments to be used when a feature doesn't explicitly depend on them. | |
* | |
* If you are using fragments within the same module, prefer [LocalFragments] | |
* | |
*/ | |
open class Fragments(val packagePrefix: String) { | |
/** Combine package name and fragment name, without assuming that they have a period in the correct place at beginning or end. */ | |
@PublishedApi internal fun fqn(name: String) = "${packagePrefix.removeSuffix(".")}.${name.removePrefix(".")}" | |
protected inline fun <reified A : Parcelable> create(name: String) = MvRxFragmentFactory.create<A>(fqn(name)) | |
protected fun create(name: String) = MvRxFragmentFactory.create(fqn(name)) | |
} | |
/** | |
* Similar to [Fragments], but intended for use by fragments that are only used in the module they are declared in. | |
* This allows the fragments to be referenced via their class instead of a hardcoded string. | |
*/ | |
open class LocalFragments { | |
protected inline fun <reified A : Parcelable> create(clazz: KClass<*>) = MvRxFragmentFactory.create<A>(clazz.qualifiedName!!) | |
protected fun create(clazz: KClass<*>) = MvRxFragmentFactory.create(clazz.qualifiedName!!) | |
} | |
/** | |
* Helps to load MvRx Fragments and Activities. | |
* Assumes you have a base MvRxFragment that all other fragments extend. | |
* Also assume you have a common MvRxActivity that hosts MvRx Fragments. | |
*/ | |
sealed class MvRxFragmentFactory { | |
abstract val fragmentClassName: String | |
val fragmentClass by lazy { ClassRegistry.loadClassOrNull<MvRxFragment>(fragmentClassName) } | |
fun <T : Any> requireClass(ifNotNull: (Class<MvRxFragment>) -> T): T = ifNotNull(ClassRegistry.loadFragmentOrThrow(fragmentClassName)) | |
/** | |
* Creates an intent that will be started with this MvRxFragment. In debug builds if the MvRx fragment is missing a fragment that automatically | |
* finishes and toasts the missing fragment will be shown. | |
*/ | |
protected fun fragment(arg: Parcelable?): MvRxFragment { | |
val fragmentClass: Class<out MvRxFragment> = ClassRegistry.loadClassOrNull(fragmentClassName) ?: error("Fragment not found $fragmentClassName") | |
return fragmentClass.newInstance().also { fragment -> | |
if (arg != null) { | |
fragment.withArgs { addMvrxArgs(arg) } | |
} | |
} | |
} | |
/** | |
* Creates an intent that will be started with this MvRxFragment. In debug builds if the MvRx fragment is missing a fragment that automatically | |
* finishes and toasts the missing fragment will be shown. | |
*/ | |
protected fun fragmentIntent(context: Context, arg: Parcelable?): Intent { | |
val fragmentClass: Class<out Fragment>? = ClassRegistry.loadClassOrNull(fragmentClassName) | |
return MvRxActivity.newIntent(context, fragmentClass, arg) | |
} | |
companion object { | |
inline fun <reified A : Parcelable> create(name: String) = MvRxFragmentFactoryWithArgs<A>(name) | |
fun create(name: String) = MvRxFragmentFactoryWithoutArgs(name) | |
} | |
} | |
class MvRxFragmentFactoryWithArgs<A : Parcelable> @PublishedApi internal constructor(override val fragmentClassName: String) : MvRxFragmentFactory() { | |
fun newInstance(arg: A): MvRxFragment = fragment(arg) | |
@JvmOverloads | |
fun newIntent(context: Context, arg: A): Intent = fragmentIntent(context, arg) | |
@JvmOverloads | |
fun startActivity(context: Context, arg: A) = context.startActivity( | |
newIntent(context, arg) | |
) | |
@JvmOverloads | |
fun startActivityForResult(activity: Activity, arg: A, requestCode: Int) = activity.startActivityForResult( | |
newIntent(activity, arg), | |
requestCode | |
) | |
} | |
fun Bundle.addMvrxArgs(arg: Parcelable?): Bundle { | |
putParcelable(MvRx.KEY_ARG, arg) | |
return this | |
} | |
class MvRxFragmentFactoryWithoutArgs @PublishedApi internal constructor(override val fragmentClassName: String) : MvRxFragmentFactory() { | |
fun newInstance(): Fragment = fragment(null) | |
@JvmOverloads | |
fun newIntent(context: Context) = fragmentIntent(context, null,) | |
@JvmOverloads | |
fun startActivity(context: Context) = context.startActivity( | |
newIntent(context) | |
) | |
@JvmOverloads | |
fun startActivityForResult(activity: Activity, requestCode: Int) = activity.startActivityForResult( | |
newIntent(activity), | |
requestCode | |
) | |
} | |
/** | |
* Helps to load a class by fully qualified name, with a cache. | |
*/ | |
object ClassRegistry { | |
private val CLASS_MAP = ConcurrentHashMap<String, Class<*>>() | |
@JvmStatic fun <T : Activity> loadActivityOrThrow(className: String): Class<T> = loadClassOrThrow(className, Activity::class) | |
@JvmStatic fun <T : Fragment> loadFragmentOrThrow(className: String): Class<T> = loadClassOrThrow(className, Fragment::class) | |
@JvmStatic fun <T : Service> loadServiceOrThrow(className: String): Class<T> = loadClassOrThrow(className) | |
@JvmStatic fun <T : BroadcastReceiver> loadReceiverOrThrow(className: String): Class<T> = loadClassOrThrow(className) | |
fun <T> loadClassOrNull(className: String): Class<T>? { | |
return CLASS_MAP.getOrPut(className) { | |
try { | |
Class.forName(className) | |
} catch(e: ClassNotFoundException) { | |
// Can't store a null value in the concurrent map | |
return null | |
} | |
} as? Class<T> | |
} | |
@Throws(ClassNotFoundException::class) | |
private fun <T> loadClassOrThrow(className: String, type: KClass<*>? = null): Class<T> { | |
return loadClassOrNull(className) ?: throw ClassNotFoundException("Class not found $className") | |
} | |
/** Given a FQN class name, returns the simple name. */ | |
fun simpleName(className: String) = className.split(".").lastOrNull() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment