blob: 6a156fa7925c9fa5a6384e23819aa9a81790ae96 [file] [log] [blame]
/*
* Copyright (C) 2020 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dagger.hilt.android.processor.internal.viewmodel
import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.ExperimentalProcessingApi
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.addOriginatingElement
import com.squareup.javapoet.AnnotationSpec
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
import dagger.hilt.android.processor.internal.AndroidClassNames
import dagger.hilt.processor.internal.ClassNames
import dagger.hilt.processor.internal.Processors
import javax.lang.model.element.Modifier
/**
* Source generator to support Hilt injection of ViewModels.
*
* Should generate:
* ```
* public final class $_HiltModules {
* @Module
* @InstallIn(ViewModelComponent.class)
* public static abstract class BindsModule {
* @Binds
* @IntoMap
* @LazyClassKey(pkg.$)
* @HiltViewModelMap
* public abstract ViewModel bind($ vm)
* }
* @Module
* @InstallIn(ActivityRetainedComponent.class)
* public static final class KeyModule {
* private static String className = "pkg.$";
* @Provides
* @IntoMap
* @HiltViewModelMap.KeySet
* @LazyClassKey(pkg.$)
* public static boolean provide() {
* return true;
* }
* }
* }
* ```
*/
@OptIn(ExperimentalProcessingApi::class)
internal class ViewModelModuleGenerator(
private val processingEnv: XProcessingEnv,
private val viewModelMetadata: ViewModelMetadata,
) {
fun generate() {
val modulesTypeSpec =
TypeSpec.classBuilder(viewModelMetadata.modulesClassName)
.apply {
addOriginatingElement(viewModelMetadata.viewModelElement)
Processors.addGeneratedAnnotation(this, processingEnv, ViewModelProcessor::class.java)
addAnnotation(
AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT)
.addMember(
"topLevelClass",
"$T.class",
viewModelMetadata.className.topLevelClassName(),
)
.build()
)
addModifiers(Modifier.PUBLIC, Modifier.FINAL)
addType(getBindsModuleTypeSpec())
addType(getKeyModuleTypeSpec())
addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
}
.build()
processingEnv.filer.write(
JavaFile.builder(viewModelMetadata.modulesClassName.packageName(), modulesTypeSpec).build()
)
}
private fun getBindsModuleTypeSpec() =
createModuleTypeSpec(
className = "BindsModule",
component = AndroidClassNames.VIEW_MODEL_COMPONENT,
)
.addModifiers(Modifier.ABSTRACT)
.addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
.addMethod(
if (viewModelMetadata.assistedFactory.asClassName() != XTypeName.ANY_OBJECT) {
getAssistedViewModelBindsMethod()
} else {
getViewModelBindsMethod()
}
)
.build()
private fun getViewModelBindsMethod() =
MethodSpec.methodBuilder("binds")
.addAnnotation(ClassNames.BINDS)
.addAnnotation(ClassNames.INTO_MAP)
.addAnnotation(
AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY)
.addMember("value", "$T.class", viewModelMetadata.className)
.build()
)
.addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_MAP_QUALIFIER)
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.returns(AndroidClassNames.VIEW_MODEL)
.addParameter(viewModelMetadata.className, "vm")
.build()
private fun getKeyModuleTypeSpec() =
createModuleTypeSpec(
className = "KeyModule",
component = AndroidClassNames.ACTIVITY_RETAINED_COMPONENT,
)
.addModifiers(Modifier.FINAL)
.addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
.addMethod(getViewModelKeyProvidesMethod())
.build()
private fun getViewModelKeyProvidesMethod() =
MethodSpec.methodBuilder("provide")
.addAnnotation(ClassNames.PROVIDES)
.addAnnotation(ClassNames.INTO_MAP)
.addAnnotation(
AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY)
.addMember("value", "$T.class", viewModelMetadata.className)
.build()
)
.addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_KEYS_QUALIFIER)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(Boolean::class.java)
.addStatement("return true")
.build()
/**
* Should generate:
* ```
* @Binds
* @IntoMap
* @LazyClassKey(pkg.FooViewModel.class)
* @HiltViewModelAssistedMap
* public abstract Object bind(FooViewModelAssistedFactory factory);
* ```
*
* So that we have a HiltViewModelAssistedMap that maps from fully qualified ViewModel names to
* its assisted factory instance.
*/
private fun getAssistedViewModelBindsMethod() =
MethodSpec.methodBuilder("bind")
.addAnnotation(ClassNames.BINDS)
.addAnnotation(ClassNames.INTO_MAP)
.addAnnotation(
AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY)
.addMember("value", "$T.class", viewModelMetadata.className)
.build()
)
.addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_ASSISTED_FACTORY_MAP_QUALIFIER)
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addParameter(viewModelMetadata.assistedFactoryClassName, "factory")
.returns(TypeName.OBJECT)
.build()
private fun createModuleTypeSpec(className: String, component: ClassName) =
TypeSpec.classBuilder(className)
.addOriginatingElement(viewModelMetadata.viewModelElement)
.addAnnotation(ClassNames.MODULE)
.addAnnotation(
AnnotationSpec.builder(ClassNames.INSTALL_IN)
.addMember("value", "$T.class", component)
.build()
)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
companion object {
const val L = "\$L"
const val T = "\$T"
const val N = "\$N"
const val S = "\$S"
const val W = "\$W"
}
}