• 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()
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