• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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