/* * 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" } }