1 /* 2 * Copyright (C) 2020 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.hilt.android.processor.internal.viewmodel 18 19 import androidx.room.compiler.codegen.XTypeName 20 import androidx.room.compiler.processing.ExperimentalProcessingApi 21 import androidx.room.compiler.processing.XProcessingEnv 22 import androidx.room.compiler.processing.addOriginatingElement 23 import com.squareup.javapoet.AnnotationSpec 24 import com.squareup.javapoet.ClassName 25 import com.squareup.javapoet.JavaFile 26 import com.squareup.javapoet.MethodSpec 27 import com.squareup.javapoet.TypeName 28 import com.squareup.javapoet.TypeSpec 29 import dagger.hilt.android.processor.internal.AndroidClassNames 30 import dagger.hilt.processor.internal.ClassNames 31 import dagger.hilt.processor.internal.Processors 32 import javax.lang.model.element.Modifier 33 34 /** 35 * Source generator to support Hilt injection of ViewModels. 36 * 37 * Should generate: 38 * ``` 39 * public final class $_HiltModules { 40 * @Module 41 * @InstallIn(ViewModelComponent.class) 42 * public static abstract class BindsModule { 43 * @Binds 44 * @IntoMap 45 * @LazyClassKey(pkg.$) 46 * @HiltViewModelMap 47 * public abstract ViewModel bind($ vm) 48 * } 49 * @Module 50 * @InstallIn(ActivityRetainedComponent.class) 51 * public static final class KeyModule { 52 * private static String className = "pkg.$"; 53 * @Provides 54 * @IntoMap 55 * @HiltViewModelMap.KeySet 56 * @LazyClassKey(pkg.$) 57 * public static boolean provide() { 58 * return true; 59 * } 60 * } 61 * } 62 * ``` 63 */ 64 @OptIn(ExperimentalProcessingApi::class) 65 internal class ViewModelModuleGenerator( 66 private val processingEnv: XProcessingEnv, 67 private val viewModelMetadata: ViewModelMetadata, 68 ) { generatenull69 fun generate() { 70 val modulesTypeSpec = 71 TypeSpec.classBuilder(viewModelMetadata.modulesClassName) 72 .apply { 73 addOriginatingElement(viewModelMetadata.viewModelElement) 74 Processors.addGeneratedAnnotation(this, processingEnv, ViewModelProcessor::class.java) 75 addAnnotation( 76 AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT) 77 .addMember( 78 "topLevelClass", 79 "$T.class", 80 viewModelMetadata.className.topLevelClassName(), 81 ) 82 .build() 83 ) 84 addModifiers(Modifier.PUBLIC, Modifier.FINAL) 85 addType(getBindsModuleTypeSpec()) 86 addType(getKeyModuleTypeSpec()) 87 addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build()) 88 } 89 .build() 90 91 processingEnv.filer.write( 92 JavaFile.builder(viewModelMetadata.modulesClassName.packageName(), modulesTypeSpec).build() 93 ) 94 } 95 getBindsModuleTypeSpecnull96 private fun getBindsModuleTypeSpec() = 97 createModuleTypeSpec( 98 className = "BindsModule", 99 component = AndroidClassNames.VIEW_MODEL_COMPONENT, 100 ) 101 .addModifiers(Modifier.ABSTRACT) 102 .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build()) 103 .addMethod( 104 if (viewModelMetadata.assistedFactory.asClassName() != XTypeName.ANY_OBJECT) { 105 getAssistedViewModelBindsMethod() 106 } else { 107 getViewModelBindsMethod() 108 } 109 ) 110 .build() 111 getViewModelBindsMethodnull112 private fun getViewModelBindsMethod() = 113 MethodSpec.methodBuilder("binds") 114 .addAnnotation(ClassNames.BINDS) 115 .addAnnotation(ClassNames.INTO_MAP) 116 .addAnnotation( 117 AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY) 118 .addMember("value", "$T.class", viewModelMetadata.className) 119 .build() 120 ) 121 .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_MAP_QUALIFIER) 122 .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 123 .returns(AndroidClassNames.VIEW_MODEL) 124 .addParameter(viewModelMetadata.className, "vm") 125 .build() 126 127 private fun getKeyModuleTypeSpec() = 128 createModuleTypeSpec( 129 className = "KeyModule", 130 component = AndroidClassNames.ACTIVITY_RETAINED_COMPONENT, 131 ) 132 .addModifiers(Modifier.FINAL) 133 .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build()) 134 .addMethod(getViewModelKeyProvidesMethod()) 135 .build() 136 137 private fun getViewModelKeyProvidesMethod() = 138 MethodSpec.methodBuilder("provide") 139 .addAnnotation(ClassNames.PROVIDES) 140 .addAnnotation(ClassNames.INTO_MAP) 141 .addAnnotation( 142 AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY) 143 .addMember("value", "$T.class", viewModelMetadata.className) 144 .build() 145 ) 146 .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_KEYS_QUALIFIER) 147 .addModifiers(Modifier.PUBLIC, Modifier.STATIC) 148 .returns(Boolean::class.java) 149 .addStatement("return true") 150 .build() 151 152 /** 153 * Should generate: 154 * ``` 155 * @Binds 156 * @IntoMap 157 * @LazyClassKey(pkg.FooViewModel.class) 158 * @HiltViewModelAssistedMap 159 * public abstract Object bind(FooViewModelAssistedFactory factory); 160 * ``` 161 * 162 * So that we have a HiltViewModelAssistedMap that maps from fully qualified ViewModel names to 163 * its assisted factory instance. 164 */ 165 private fun getAssistedViewModelBindsMethod() = 166 MethodSpec.methodBuilder("bind") 167 .addAnnotation(ClassNames.BINDS) 168 .addAnnotation(ClassNames.INTO_MAP) 169 .addAnnotation( 170 AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY) 171 .addMember("value", "$T.class", viewModelMetadata.className) 172 .build() 173 ) 174 .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_ASSISTED_FACTORY_MAP_QUALIFIER) 175 .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 176 .addParameter(viewModelMetadata.assistedFactoryClassName, "factory") 177 .returns(TypeName.OBJECT) 178 .build() 179 180 private fun createModuleTypeSpec(className: String, component: ClassName) = 181 TypeSpec.classBuilder(className) 182 .addOriginatingElement(viewModelMetadata.viewModelElement) 183 .addAnnotation(ClassNames.MODULE) 184 .addAnnotation( 185 AnnotationSpec.builder(ClassNames.INSTALL_IN) 186 .addMember("value", "$T.class", component) 187 .build() 188 ) 189 .addModifiers(Modifier.PUBLIC, Modifier.STATIC) 190 191 companion object { 192 193 const val L = "\$L" 194 const val T = "\$T" 195 const val N = "\$N" 196 const val S = "\$S" 197 const val W = "\$W" 198 } 199 } 200