Skip to content

Commit

Permalink
fix ViewModel duplicate bindings
Browse files Browse the repository at this point in the history
fixes #123 for real
  • Loading branch information
RBusarow committed Aug 4, 2021
1 parent ade58fa commit a02717b
Show file tree
Hide file tree
Showing 15 changed files with 103 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import java.io.File

fun interface FileGenerator<T> {

fun generate(codeGenDir: File, params: T): GeneratedFile
fun generate(codeGenDir: File, params: T): GeneratedFile?

/**
* Write [content] into a new file for the given [packageName] and [fileName]. [fileName] usually
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class ContributesFragmentGenerator : CodeGenerator {
val factoryImplClassName = ClassName(binding.packageName, fragmentFactoryClassNameString)

addFunction(
FunSpec.builder(name = "provide${binding.fragmentClassName.simpleNames.joinToString("_")}")
FunSpec.builder(name = "provide_${binding.fragmentClassName.simpleNames.joinToString("_")}")
.addAnnotation(ClassNames.provides)
.addAnnotation(ClassNames.tangleFragmentProviderMap)
.applyEach(binding.injectedParams) { argument ->
Expand Down Expand Up @@ -144,7 +144,7 @@ class ContributesFragmentGenerator : CodeGenerator {
.build()

addFunction(
FunSpec.builder(name = "bind${binding.fragmentClassName.simpleNames.joinToString("_")}")
FunSpec.builder(name = "bind_${binding.fragmentClassName.simpleNames.joinToString("_")}")
.addModifiers(KModifier.ABSTRACT)
.returns(ClassNames.androidxFragment)
.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ class FragmentInjectGenerator : CodeGenerator {
includeModule = false
)
addFunction(
"provide${params.factoryInterfaceClassName.simpleNames.joinToString("_")}"
"provide_${params.factoryInterfaceClassName.simpleNames.joinToString("_")}"
) {
addAnnotation(ClassNames.provides)
factoryConstructorParams.forEach { argument ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class TangleFragmentFactoryModuleGenerator : CodeGenerator {
}
.addType(
TypeSpec.companionObjectBuilder()
.addFunction("provide${ClassNames.tangleFragmentFactory.simpleName}") {
.addFunction("provide_${ClassNames.tangleFragmentFactory.simpleName}") {
addAnnotation(ClassNames.provides)
addParameter("providerMap", ClassNames.fragmentProviderMap)
addParameter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class ContributesFragmentGeneratorTest : BaseTest() {
val moduleClass = tangleUnitFragmentModuleClass.kotlin

moduleClass.companionObject!!.functions
.first { it.name == "provideMyFragment" }
.first { it.name == "provide_MyFragment" }
.call(moduleClass.companionObjectInstance)!!::class.java shouldBe myFragmentClass
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class FragmentInjectModuleGenerationTest : BaseTest() {
tangleUnitFragmentInjectModuleClass
.kotlin
.functions
.first { it.name == "provideMyFragment_Factory" }
.first { it.name == "provide_MyFragment_Factory" }
.call(tangleUnitFragmentInjectModuleClass.kotlin.objectInstance)!!::class.java shouldBe myFragmentFactoryImplClass
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,35 +162,10 @@ val Result.anyQualifier: Class<*>
get() = classLoader.loadClass("tangle.inject.tests.AnyQualifier")

val Result.bindMyFragment: Method
get() = tangleUnitFragmentModuleClass.getDeclaredMethod("bindMyFragment", myFragmentClass)
get() = tangleUnitFragmentModuleClass.getDeclaredMethod("bind_MyFragment", myFragmentClass)

val Result.provideMyFragment: Method
get() = tangleUnitFragmentModuleCompanionClass.getDeclaredMethod("provideMyFragment")

@Suppress("UNCHECKED_CAST")
val Result.bindingKey: Class<out Annotation>
get() = classLoader.loadClass("tangle.inject.tests.BindingKey") as Class<out Annotation>

private fun Class<*>.contributedProperties(packagePrefix: String): List<KClass<*>>? {
// The capitalize() doesn't make sense, I don't know where this is coming from. Maybe it's a
// bug in the compile testing library?
@Suppress("DEPRECATION")
val className = canonicalName.replace('.', '_')
.capitalize() + "Kt"

val clazz = try {
classLoader.loadClass("$packagePrefix.${packageName()}$className")
} catch (e: ClassNotFoundException) {
return null
}

return clazz.declaredFields
.map {
it.isAccessible = true
it.get(null)
}
.filterIsInstance<KClass<*>>()
}
get() = tangleUnitFragmentModuleCompanionClass.getDeclaredMethod("provide_MyFragment")

fun Method.annotationClasses() = annotations.map { it.annotationClass }
fun Class<*>.annotationClasses() = annotations.map { it.annotationClass }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ public object TangleGraph {

internal val tangleViewModelKeys by lazy {
get<TangleViewModelComponent>()
.tangleViewModelKeysSubcomponentFactories
.first()
.tangleViewModelKeysSubcomponentFactory
.create()
.viewModelKeys
}
Expand All @@ -40,8 +39,7 @@ public object TangleGraph {
}

internal fun tangleViewModelSubcomponentFactory() = get<TangleViewModelComponent>()
.tangleViewModelMapSubcomponentFactories
.first()
.tangleViewModelMapSubcomponentFactory

/**
* Used to retrieve a Component of a given type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface TangleViewModelComponent {
* @since 0.10.0
*/
@InternalTangleApi
public val tangleViewModelMapSubcomponentFactories: Set<TangleViewModelMapSubcomponent.Factory>
public val tangleViewModelMapSubcomponentFactory: TangleViewModelMapSubcomponent.Factory

/**
* Referenced by [TangleViewModelFactory] in order to create
Expand All @@ -26,5 +26,5 @@ public interface TangleViewModelComponent {
* @since 0.10.0
*/
@InternalTangleApi
public val tangleViewModelKeysSubcomponentFactories: Set<TangleViewModelKeysSubcomponent.Factory>
public val tangleViewModelKeysSubcomponentFactory: TangleViewModelKeysSubcomponent.Factory
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class ViewModelTangleAppScopeModuleGenerator : FileGenerator<TangleScopeModule>
.applyEach(params.viewModelParamsList) { viewModelParams ->

addFunction(
"provide${viewModelParams.viewModelClassName.simpleNames.joinToString("_")}Key"
"provide_${viewModelParams.viewModelClassName.simpleNames.joinToString("_")}Key"
) {
returns(ClassNames.javaClassOutVM)
addAnnotation(ClassNames.intoSet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ViewModelTangleScopeModuleGenerator : FileGenerator<TangleScopeModule> {
.applyEach(params.viewModelParamsList) { viewModelParams ->

addFunction(
"multibind${viewModelParams.viewModelClassName.simpleNames.joinToString("_")}"
"multibind_${viewModelParams.viewModelClassName.simpleNames.joinToString("_")}"
) {

addModifiers(ABSTRACT)
Expand All @@ -54,7 +54,7 @@ class ViewModelTangleScopeModuleGenerator : FileGenerator<TangleScopeModule> {
.applyEach(params.viewModelParamsList) { viewModelParams ->

addFunction(
"provide${viewModelParams.viewModelFactoryClassName.simpleNames.joinToString("_")}"
"provide_${viewModelParams.viewModelFactoryClassName.simpleNames.joinToString("_")}"
) {

addParameter("factory", viewModelParams.viewModelFactoryClassName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import tangle.inject.compiler.*
data class MergeComponentParams(
val module: ModuleDescriptor,
val packageName: String,
val subcomponentModulePackageName: String,
val scopeFqName: FqName,
val scopeClassName: ClassName,
val componentClass: KtClassOrObject,
Expand Down Expand Up @@ -54,12 +55,18 @@ data class MergeComponentParams(
val mergeComponentModuleClassName =
ClassName(packageName, "${base}_Tangle_ViewModel_Module")

val subcomponentModulePackageName = "tangle.viewmodel"

val subcomponentModuleClassName =
ClassName(packageName, "${base}_Tangle_ViewModel_Subcomponent_Module")
ClassName(
subcomponentModulePackageName,
"${base}_Tangle_ViewModel_SubcomponentFactory_Module"
)

return MergeComponentParams(
module = module,
packageName = packageName,
subcomponentModulePackageName = subcomponentModulePackageName,
scopeFqName = scopeFqName,
scopeClassName = scopeClassName,
componentClass = clazz,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ViewModelMergeComponentCodeGenerator : CodeGenerator {
val fileGenerators = listOf(
ViewModelMapSubcomponentGenerator(),
ViewModelKeysSubcomponentGenerator(),
ViewModelSubcomponentModuleGenerator(),
ViewModelSubcomponentFactoryModuleGenerator(),
ViewModelMergeComponentModuleGenerator(),
ViewModelComponentGenerator()
)
Expand All @@ -33,9 +33,9 @@ class ViewModelMergeComponentCodeGenerator : CodeGenerator {
.flatMap { it.classesAndInnerClasses(module) }
.filter { it.hasAnnotation(FqNames.mergeComponent, module) }
.map { MergeComponentParams.create(it, module) }
// .distinctBy { it.scopeFqName }
.distinctBy { it.scopeFqName }
.flatMap { params ->
fileGenerators.map { generator ->
fileGenerators.mapNotNull { generator ->
generator.generate(codeGenDir, params)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,41 @@ import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.TypeSpec
import org.jetbrains.kotlin.descriptors.resolveClassByFqName
import org.jetbrains.kotlin.incremental.components.NoLookupLocation.FROM_BACKEND
import org.jetbrains.kotlin.name.FqName
import tangle.inject.compiler.ClassNames
import tangle.inject.compiler.FileGenerator
import tangle.inject.compiler.addFunction
import tangle.inject.compiler.buildFile
import java.io.File

class ViewModelSubcomponentModuleGenerator : FileGenerator<MergeComponentParams> {
class ViewModelSubcomponentFactoryModuleGenerator : FileGenerator<MergeComponentParams> {

override fun generate(
codeGenDir: File,
params: MergeComponentParams
): GeneratedFile {
): GeneratedFile? {

val packageName = params.packageName
val moduleFqName =
FqName(
"${params.subcomponentModulePackageName}.${params.subcomponentModuleClassName.simpleName}"
)

// If the (Dagger) Module for this scope already exists in a different Gradle module,
// it can't be created again here without creating a duplicate binding
// for the TangleFragmentFactory.
val alreadyCreated = listOf(params.module)
.plus(params.module.allDependencyModules)
.any { depMod ->
depMod.resolveClassByFqName(moduleFqName, FROM_BACKEND) != null
}

if (alreadyCreated) {
return null
}

val packageName = params.subcomponentModulePackageName

val className = params.subcomponentModuleClassName

Expand All @@ -43,23 +64,21 @@ class ViewModelSubcomponentModuleGenerator : FileGenerator<MergeComponentParams>
.build()
)
.addFunction(
"bind${params.mapSubcomponentFactoryClassName.simpleNames.joinToString("_")}IntoSet"
"bind_${params.mapSubcomponentFactoryClassName.simpleNames.joinToString("_")}IntoSet"
) {
addModifiers(KModifier.ABSTRACT)
returns(ClassNames.tangleViewModelMapSubcomponentFactory)
addParameter("factory", params.mapSubcomponentFactoryClassName)
addAnnotation(ClassNames.binds)
addAnnotation(ClassNames.intoSet)
build()
}
.addFunction(
"bind${params.keysSubcomponentFactoryClassName.simpleNames.joinToString("_")}IntoSet"
"bind_${params.keysSubcomponentFactoryClassName.simpleNames.joinToString("_")}IntoSet"
) {
addModifiers(KModifier.ABSTRACT)
returns(ClassNames.tangleViewModelKeysSubcomponentFactory)
addParameter("factory", params.keysSubcomponentFactoryClassName)
addAnnotation(ClassNames.binds)
addAnnotation(ClassNames.intoSet)
build()
}
.build()
Expand All @@ -69,20 +88,3 @@ class ViewModelSubcomponentModuleGenerator : FileGenerator<MergeComponentParams>
return createGeneratedFile(codeGenDir, packageName, className.simpleName, content)
}
}

/*
@ContributesTo(Unit::class)
@Named("kotlin.Unit")
@Module(subcomponents = [UnitTangleViewModelMapSubcomponent::class, UnitTangleViewModelKeysSubcomponent::class])
public interface UnitTangleViewModelSubcomponentModule {
@Binds
public
fun bindUnitTangleViewModelMapSubcomponent_Factory(factory: UnitTangleViewModelMapSubcomponent.Factory):
TangleViewModelMapSubcomponent.Factory
@Binds
public
fun bindUnitTangleViewModelKeysSubcomponent_Factory(factory: UnitTangleViewModelKeysSubcomponent.Factory):
TangleViewModelKeysSubcomponent.Factory
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ class ViewModelIntegrationTest : BaseTest() {
.invoke(null)
.cast<TangleViewModelComponent>()

val mapSubcomponent = component.tangleViewModelMapSubcomponentFactories
.first()
val mapSubcomponent = component.tangleViewModelMapSubcomponentFactory
.create(SavedStateHandle())

val map = mapSubcomponent.viewModelProviderMap
Expand Down Expand Up @@ -68,8 +67,7 @@ class ViewModelIntegrationTest : BaseTest() {
.invoke(null)
.cast<TangleViewModelComponent>()

val keysSubcomponent = component.tangleViewModelKeysSubcomponentFactories
.first()
val keysSubcomponent = component.tangleViewModelKeysSubcomponentFactory
.create()

keysSubcomponent.viewModelKeys shouldBe setOf(myViewModelClass)
Expand Down Expand Up @@ -100,6 +98,54 @@ class ViewModelIntegrationTest : BaseTest() {
"""
)

@Test
fun `pre-existing Subcomponent factory Module in classpath should not get duplicate bindings`() =
compileWithDagger(
"""
package tangle.inject.tests
import com.squareup.anvil.annotations.MergeComponent
import androidx.fragment.app.Fragment
import tangle.fragment.*
import tangle.inject.*
import javax.inject.*
@Singleton
@MergeComponent(Unit::class)
interface AppComponent
@ContributesFragment(Unit::class)
class MyFragment @Inject constructor(
val factory: TangleFragmentFactory
) : Fragment()
""",
"""
package tangle.viewmodel
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import kotlin.Suppress
import kotlin.Unit
import tangle.inject.tests.Unit_Tangle_ViewModel_Keys_Subcomponent
import tangle.inject.tests.Unit_Tangle_ViewModel_Map_Subcomponent
@ContributesTo(Unit::class)
@Module(subcomponents = [Unit_Tangle_ViewModel_Map_Subcomponent::class, Unit_Tangle_ViewModel_Keys_Subcomponent::class])
public interface Unit_Tangle_ViewModel_SubcomponentFactory_Module {
@Binds
public
fun bindUnit_Tangle_ViewModel_Map_Subcomponent_FactoryIntoSet(factory: Unit_Tangle_ViewModel_Map_Subcomponent.Factory):
TangleViewModelMapSubcomponent.Factory
@Binds
public
fun bindUnit_Tangle_ViewModel_Keys_Subcomponent_FactoryIntoSet(factory: Unit_Tangle_ViewModel_Keys_Subcomponent.Factory):
TangleViewModelKeysSubcomponent.Factory
}
"""
)

@Test
fun `two components in same package with same scope should not get duplicate bindings`() =
compileWithDagger(
Expand Down

0 comments on commit a02717b

Please sign in to comment.