• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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() == viewModelElement.asClassName(),
112           assistedFactoryMethod,
113           "Class %s must have a factory method that returns a %s. Found %s.",
114           XElements.toStableString(assistedFactory),
115           XElements.toStableString(viewModelElement),
116           XTypes.toStableString(assistedFactoryMethodType.returnType)
117         )
118       }
119 
120       val injectConstructors =
121         viewModelElement.getConstructors().filter { constructor ->
122           if (isAssistedInjectFeatureEnabled) {
123             constructor.hasAnnotation(ClassNames.INJECT) ||
124               constructor.hasAnnotation(ClassNames.ASSISTED_INJECT)
125           } else {
126             ProcessorErrors.checkState(
127               !constructor.hasAnnotation(ClassNames.ASSISTED_INJECT),
128               constructor,
129               "ViewModel constructor should be annotated with @Inject instead of @AssistedInject."
130             )
131             constructor.hasAnnotation(ClassNames.INJECT)
132           }
133         }
134 
135       val injectAnnotationsMessage =
136         if (isAssistedInjectFeatureEnabled) {
137           "@Inject or @AssistedInject"
138         } else {
139           "@Inject"
140         }
141 
142       ProcessorErrors.checkState(
143         injectConstructors.size == 1,
144         viewModelElement,
145         "@HiltViewModel annotated class should contain exactly one %s annotated constructor.",
146         injectAnnotationsMessage
147       )
148 
149       val injectConstructor = injectConstructors.single()
150 
151       ProcessorErrors.checkState(
152         !injectConstructor.isPrivate(),
153         injectConstructor,
154         "%s annotated constructors must not be private.",
155         injectAnnotationsMessage
156       )
157 
158       if (injectConstructor.hasAnnotation(ClassNames.ASSISTED_INJECT)) {
159         // If "enableAssistedInjectViewModels" is not enabled we'll get error:
160         // "ViewModel constructor should be annotated with @Inject instead of @AssistedInject."
161 
162         ProcessorErrors.checkState(
163           assistedFactoryType.asTypeName() != XTypeName.ANY_OBJECT,
164           viewModelElement,
165           "%s must have a valid assisted factory specified in @HiltViewModel when used with assisted injection. Found %s.",
166           XElements.toStableString(viewModelElement),
167           XTypes.toStableString(assistedFactoryType)
168         )
169       } else {
170         ProcessorErrors.checkState(
171           assistedFactoryType.asTypeName() == XTypeName.ANY_OBJECT,
172           injectConstructor,
173           "Found assisted factory %s in @HiltViewModel but the constructor was annotated with @Inject instead of @AssistedInject.",
174           XTypes.toStableString(assistedFactoryType),
175         )
176       }
177 
178       ProcessorErrors.checkState(
179         !viewModelElement.isNested() || viewModelElement.isStatic(),
180         viewModelElement,
181         "@HiltViewModel may only be used on inner classes if they are static."
182       )
183 
184       Processors.getScopeAnnotations(viewModelElement).let { scopeAnnotations ->
185         ProcessorErrors.checkState(
186           scopeAnnotations.isEmpty(),
187           viewModelElement,
188           "@HiltViewModel classes should not be scoped. Found: %s",
189           scopeAnnotations.joinToString { XAnnotations.toStableString(it) }
190         )
191       }
192 
193       return ViewModelMetadata(viewModelElement, assistedFactory)
194     }
195   }
196 }
197