Skip to content

Instantly share code, notes, and snippets.

@arkivanov
Last active August 29, 2022 17:26
Show Gist options
  • Save arkivanov/7b0d20134cb6bdab4786f2002e765fbe to your computer and use it in GitHub Desktop.
Save arkivanov/7b0d20134cb6bdab4786f2002e765fbe to your computer and use it in GitHub Desktop.
Fragment IoC issue
//region UserFragment module
class UserFragment(dataSource: UserDataSource) : Fragment()
interface UserDataSource {
fun getUser(): User
}
//endregion UserFragment module
//region ParentFragment module, depends on the UserFragment module
class ParentFragment : Fragment() {
private val fragmentFactory = FragmentFactoryImpl()
override fun onCreate(savedInstanceState: Bundle?) {
childFragmentManager.fragmentFactory = fragmentFactory
super.onCreate(savedInstanceState)
}
// Call this method when you need to show UserFragment displaying current user's info
private fun showCurrentUser() {
childFragmentManager.commit {
add(R.id.content, fragmentFactory.currentUserFragment())
}
}
// Call this method when you need to show UserFragment displaying other user's info
private fun showOtherUser(id: String) {
childFragmentManager.commit {
add(R.id.content, fragmentFactory.otherUserFragment(id = id))
}
}
}
internal class FragmentFactoryImpl : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment =
when (loadFragmentClass(classLoader, className)) {
UserFragment::class.java ->
// We need somehow distinguish between current and other users here,
// and call either currentUserFragment() or otherUserFragment().
// In case of Other User we also need an id.
TODO()
else -> super.instantiate(classLoader, className)
}
fun currentUserFragment(): UserFragment = UserFragment(CurrentUserDataSource())
fun otherUserFragment(id: String): UserFragment = UserFragment(OtherUserDataSource(id = id))
}
internal class CurrentUserDataSource : UserDataSource {
override fun getUser(): User = TODO() // Load current user
}
internal class OtherUserDataSource(id: String) : UserDataSource {
override fun getUser(): User = TODO() // Load other user
}
//endregion ParentFragment module
//region UserFragment module
class UserFragment(dataSourceFactory: (Mode) -> UserDataSource) : Fragment() {
private val mode by lazy { requireArguments().getParcelable<Mode>(KEY_MODE)!! }
private val dataSource by lazy { dataSourceFactory(mode) }
companion object {
private const val KEY_MODE = "MODE"
operator fun invoke(mode: Mode, dataSourceFactory: (Mode) -> UserDataSource): UserFragment =
UserFragment(dataSourceFactory).apply {
arguments = bundleOf(KEY_MODE to mode)
}
}
// This makes the UserFragment aware of the Mode in one dimension - Current/Other.
// It also makes impossible to reuse the UserFragment in a different context,
// supplying the UserDataSource based on an another dimension (e.g. based on gender, relationship with current user, etc.).
// You will need to add more variants here.
sealed class Mode : Parcelable {
@Parcelize
object Current : Mode()
@Parcelize
data class Other(val id: String) : Mode()
}
}
interface UserDataSource {
fun getUser(): User
}
//endregion UserFragment module
//region ParentFragment module, depends on the UserFragment module
class ParentFragment : Fragment() {
private val fragmentFactory = FragmentFactoryImpl()
override fun onCreate(savedInstanceState: Bundle?) {
childFragmentManager.fragmentFactory = fragmentFactory
super.onCreate(savedInstanceState)
}
// Call this method when you need to show UserFragment displaying current user's info
private fun showCurrentUser() {
childFragmentManager.commit {
add(R.id.content, fragmentFactory.currentUserFragment())
}
}
// Call this method when you need to show UserFragment displaying other user's info
private fun showOtherUser(id: String) {
childFragmentManager.commit {
add(R.id.content, fragmentFactory.otherUserFragment(id = id))
}
}
}
internal class FragmentFactoryImpl : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment =
when (loadFragmentClass(classLoader, className)) {
UserFragment::class.java -> UserFragment(::userDataSource)
else -> super.instantiate(classLoader, className)
}
fun currentUserFragment(): UserFragment = UserFragment(UserFragment.Mode.Current, ::userDataSource)
fun otherUserFragment(id: String): UserFragment = UserFragment(UserFragment.Mode.Other(id), ::userDataSource)
private fun userDataSource(mode: UserFragment.Mode): UserDataSource =
when (mode) {
is UserFragment.Mode.Current -> CurrentUserDataSource()
is UserFragment.Mode.Other -> OtherUserDataSource(id = mode.id)
}
}
internal class CurrentUserDataSource : UserDataSource {
override fun getUser(): User = TODO() // Load current user
}
internal class OtherUserDataSource(id: String) : UserDataSource {
override fun getUser(): User = TODO() // Load other user
}
//endregion ParentFragment module
class ParentFragment : Fragment() {
private val fragmentFactory = FragmentFactoryImpl()
override fun onCreate(savedInstanceState: Bundle?) {
childFragmentManager.fragmentFactory = fragmentFactory
super.onCreate(savedInstanceState)
}
// Call this method when you need to show UserFragment displaying current user's info
private fun showCurrentUser() {
childFragmentManager.commit {
add(R.id.content, fragmentFactory.configurationBundle(FragmentFactoryImpl.Configuration.CurrentUser))
}
}
// Call this method when you need to show UserFragment displaying other user's info
private fun showOtherUser(id: String) {
childFragmentManager.commit {
add(R.id.content, fragmentFactory.configurationBundle(FragmentFactoryImpl.Configuration.OtherUser(id = id)))
}
}
}
internal class FragmentFactoryImpl : FragmentFactory() {
override fun instantiate(bundle: Bundle): Fragment =
when (val configuration = bundle.getParcelable<Configuration>(KEY_CONFIGURATION)!!) {
is Configuration.CurrentUser -> currentUserFragment()
is Configuration.OtherUser -> otherUserFragment(id = configuration.id)
}
fun configurationBundle(configuration: Configuration): Bundle =
bundleOf(KEY_CONFIGURATION to configuration)
private fun currentUserFragment(): UserFragment = UserFragment(CurrentUserDataSource())
private fun otherUserFragment(id: String): UserFragment = UserFragment(OtherUserDataSource(id = id))
private companion object {
private const val KEY_CONFIGURATION = "CONFIGURATION"
}
sealed class Configuration : Parcelable {
@Parcelize
object CurrentUser : Configuration()
@Parcelize
data class OtherUser(val id: String): Configuration()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment