1 /* <lambda>null2 * 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.codegen.toJavaPoet 21 import androidx.room.compiler.processing.ExperimentalProcessingApi 22 import androidx.room.compiler.processing.XMethodElement 23 import androidx.room.compiler.processing.XProcessingEnv 24 import androidx.room.compiler.processing.XTypeElement 25 import com.squareup.javapoet.ClassName 26 import dagger.hilt.android.processor.internal.AndroidClassNames 27 import dagger.hilt.processor.internal.ClassNames 28 import dagger.hilt.processor.internal.HiltCompilerOptions 29 import dagger.hilt.processor.internal.ProcessorErrors 30 import dagger.hilt.processor.internal.Processors 31 import dagger.internal.codegen.xprocessing.XAnnotations 32 import dagger.internal.codegen.xprocessing.XElements 33 import dagger.internal.codegen.xprocessing.XTypeElements 34 import dagger.internal.codegen.xprocessing.XTypes 35 36 /** Data class that represents a Hilt injected ViewModel */ 37 @OptIn(ExperimentalProcessingApi::class) 38 internal class ViewModelMetadata 39 private constructor(val viewModelElement: XTypeElement, val assistedFactory: XTypeElement) { 40 val className = viewModelElement.asClassName().toJavaPoet() 41 42 val assistedFactoryClassName: ClassName = assistedFactory.asClassName().toJavaPoet() 43 44 val modulesClassName = 45 ClassName.get( 46 viewModelElement.packageName, 47 "${className.simpleNames().joinToString("_")}_HiltModules" 48 ) 49 50 companion object { 51 52 private const val ASSISTED_FACTORY_VALUE = "assistedFactory" 53 54 fun getAssistedFactoryMethods(factory: XTypeElement?): List<XMethodElement> { 55 return XTypeElements.getAllNonPrivateInstanceMethods(factory) 56 .filter { it.isAbstract() } 57 .filter { !it.isJavaDefault() } 58 } 59 60 internal fun create( 61 processingEnv: XProcessingEnv, 62 viewModelElement: XTypeElement, 63 ): ViewModelMetadata? { 64 ProcessorErrors.checkState( 65 XTypes.isSubtype( 66 viewModelElement.type, 67 processingEnv.requireType(AndroidClassNames.VIEW_MODEL) 68 ), 69 viewModelElement, 70 "@HiltViewModel is only supported on types that subclass %s.", 71 AndroidClassNames.VIEW_MODEL 72 ) 73 74 val isAssistedInjectFeatureEnabled = 75 HiltCompilerOptions.isAssistedInjectViewModelsEnabled(viewModelElement) 76 77 val assistedFactoryType = 78 viewModelElement 79 .requireAnnotation(AndroidClassNames.HILT_VIEW_MODEL) 80 .getAsType(ASSISTED_FACTORY_VALUE) 81 val assistedFactory = assistedFactoryType.typeElement!! 82 83 if (assistedFactoryType.asTypeName() != XTypeName.ANY_OBJECT) { 84 ProcessorErrors.checkState( 85 isAssistedInjectFeatureEnabled, 86 viewModelElement, 87 "Specified assisted factory %s for %s in @HiltViewModel but compiler option 'enableAssistedInjectViewModels' was not enabled.", 88 assistedFactoryType.asTypeName().toJavaPoet(), 89 XElements.toStableString(viewModelElement), 90 ) 91 92 ProcessorErrors.checkState( 93 assistedFactory.hasAnnotation(ClassNames.ASSISTED_FACTORY), 94 viewModelElement, 95 "Class %s is not annotated with @AssistedFactory.", 96 assistedFactoryType.asTypeName().toJavaPoet() 97 ) 98 99 val assistedFactoryMethod = getAssistedFactoryMethods(assistedFactory).singleOrNull() 100 101 ProcessorErrors.checkState( 102 assistedFactoryMethod != null, 103 assistedFactory, 104 "Cannot find assisted factory method in %s.", 105 XElements.toStableString(assistedFactory) 106 ) 107 108 val assistedFactoryMethodType = assistedFactoryMethod!!.asMemberOf(assistedFactoryType) 109 110 ProcessorErrors.checkState( 111 assistedFactoryMethodType.returnType.asTypeName() 112 .equalsIgnoreNullability(viewModelElement.asClassName()), 113 assistedFactoryMethod, 114 "Class %s must have a factory method that returns a %s. Found %s.", 115 XElements.toStableString(assistedFactory), 116 XElements.toStableString(viewModelElement), 117 XTypes.toStableString(assistedFactoryMethodType.returnType) 118 ) 119 } 120 121 val injectConstructors = 122 viewModelElement.getConstructors().filter { constructor -> 123 if (isAssistedInjectFeatureEnabled) { 124 constructor.hasAnnotation(ClassNames.INJECT) || 125 constructor.hasAnnotation(ClassNames.ASSISTED_INJECT) 126 } else { 127 ProcessorErrors.checkState( 128 !constructor.hasAnnotation(ClassNames.ASSISTED_INJECT), 129 constructor, 130 "ViewModel constructor should be annotated with @Inject instead of @AssistedInject." 131 ) 132 constructor.hasAnnotation(ClassNames.INJECT) 133 } 134 } 135 136 val injectAnnotationsMessage = 137 if (isAssistedInjectFeatureEnabled) { 138 "@Inject or @AssistedInject" 139 } else { 140 "@Inject" 141 } 142 143 ProcessorErrors.checkState( 144 injectConstructors.size == 1, 145 viewModelElement, 146 "@HiltViewModel annotated class should contain exactly one %s annotated constructor.", 147 injectAnnotationsMessage 148 ) 149 150 val injectConstructor = injectConstructors.single() 151 152 ProcessorErrors.checkState( 153 !injectConstructor.isPrivate(), 154 injectConstructor, 155 "%s annotated constructors must not be private.", 156 injectAnnotationsMessage 157 ) 158 159 if (injectConstructor.hasAnnotation(ClassNames.ASSISTED_INJECT)) { 160 // If "enableAssistedInjectViewModels" is not enabled we'll get error: 161 // "ViewModel constructor should be annotated with @Inject instead of @AssistedInject." 162 163 ProcessorErrors.checkState( 164 assistedFactoryType.asTypeName() != XTypeName.ANY_OBJECT, 165 viewModelElement, 166 "%s must have a valid assisted factory specified in @HiltViewModel when used with assisted injection. Found %s.", 167 XElements.toStableString(viewModelElement), 168 XTypes.toStableString(assistedFactoryType) 169 ) 170 } else { 171 ProcessorErrors.checkState( 172 assistedFactoryType.asTypeName() == XTypeName.ANY_OBJECT, 173 injectConstructor, 174 "Found assisted factory %s in @HiltViewModel but the constructor was annotated with @Inject instead of @AssistedInject.", 175 XTypes.toStableString(assistedFactoryType), 176 ) 177 } 178 179 ProcessorErrors.checkState( 180 !viewModelElement.isNested() || viewModelElement.isStatic(), 181 viewModelElement, 182 "@HiltViewModel may only be used on inner classes if they are static." 183 ) 184 185 Processors.getScopeAnnotations(viewModelElement).let { scopeAnnotations -> 186 ProcessorErrors.checkState( 187 scopeAnnotations.isEmpty(), 188 viewModelElement, 189 "@HiltViewModel classes should not be scoped. Found: %s", 190 scopeAnnotations.joinToString { XAnnotations.toStableString(it) } 191 ) 192 } 193 194 return ViewModelMetadata(viewModelElement, assistedFactory) 195 } 196 } 197 } 198