• 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 com.google.auto.common.GeneratedAnnotationSpecs
20 import com.squareup.javapoet.AnnotationSpec
21 import com.squareup.javapoet.ClassName
22 import com.squareup.javapoet.JavaFile
23 import com.squareup.javapoet.MethodSpec
24 import com.squareup.javapoet.TypeSpec
25 import dagger.hilt.android.processor.internal.AndroidClassNames
26 import dagger.hilt.processor.internal.ClassNames
27 import javax.annotation.processing.ProcessingEnvironment
28 import javax.lang.model.SourceVersion
29 import javax.lang.model.element.Modifier
30 import javax.lang.model.util.Elements
31 
32 /**
33  * Source generator to support Hilt injection of ViewModels.
34  *
35  * Should generate:
36  * ```
37  * public final class $_HiltModules {
38  *   @Module
39  *   @InstallIn(ViewModelComponent.class)
40  *   public static abstract class BindsModule {
41  *     @Binds
42  *     @IntoMap
43  *     @StringKey("pkg.$")
44  *     @HiltViewModelMap
45  *     public abstract ViewModel bind($ vm)
46  *   }
47  *   @Module
48  *   @InstallIn(ActivityRetainedComponent.class)
49  *   public static final class KeyModule {
50  *     @Provides
51  *     @IntoSet
52  *     @HiltViewModelMap.KeySet
53  *     public static String provide() {
54  *      return "pkg.$";
55  *     }
56  *   }
57  * }
58  * ```
59  */
60 internal class ViewModelModuleGenerator(
61   private val processingEnv: ProcessingEnvironment,
62   private val injectedViewModel: ViewModelMetadata
63 ) {
64   fun generate() {
65     val modulesTypeSpec = TypeSpec.classBuilder(injectedViewModel.modulesClassName)
66       .addOriginatingElement(injectedViewModel.typeElement)
67       .addGeneratedAnnotation(processingEnv.elementUtils, processingEnv.sourceVersion)
68       .addAnnotation(
69         AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT)
70           .addMember(
71             "topLevelClass",
72             "$T.class",
73             injectedViewModel.className.topLevelClassName()
74           )
75           .build()
76       )
77       .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
78       .addType(getBindsModuleTypeSpec())
79       .addType(getKeyModuleTypeSpec())
80       .addMethod(
81         MethodSpec.constructorBuilder()
82           .addModifiers(Modifier.PRIVATE)
83           .build()
84       )
85       .build()
86     JavaFile.builder(injectedViewModel.modulesClassName.packageName(), modulesTypeSpec)
87       .build()
88       .writeTo(processingEnv.filer)
89   }
90 
91   private fun getBindsModuleTypeSpec() = createModuleTypeSpec(
92     className = "BindsModule",
93     component = AndroidClassNames.VIEW_MODEL_COMPONENT
94   )
95     .addModifiers(Modifier.ABSTRACT)
96     .addMethod(
97       MethodSpec.constructorBuilder()
98         .addModifiers(Modifier.PRIVATE)
99         .build()
100     )
101     .addMethod(getViewModelBindsMethod())
102     .build()
103 
104   private fun getViewModelBindsMethod() =
105     MethodSpec.methodBuilder("binds")
106       .addAnnotation(ClassNames.BINDS)
107       .addAnnotation(ClassNames.INTO_MAP)
108       .addAnnotation(
109         AnnotationSpec.builder(ClassNames.STRING_KEY)
110           .addMember("value", S, injectedViewModel.className.reflectionName())
111           .build()
112       )
113       .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_MAP_QUALIFIER)
114       .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
115       .returns(AndroidClassNames.VIEW_MODEL)
116       .addParameter(injectedViewModel.className, "vm")
117       .build()
118 
119   private fun getKeyModuleTypeSpec() = createModuleTypeSpec(
120     className = "KeyModule",
121     component = AndroidClassNames.ACTIVITY_RETAINED_COMPONENT
122   )
123     .addModifiers(Modifier.FINAL)
124     .addMethod(
125       MethodSpec.constructorBuilder()
126         .addModifiers(Modifier.PRIVATE)
127         .build()
128     )
129     .addMethod(getViewModelKeyProvidesMethod())
130     .build()
131 
132   private fun getViewModelKeyProvidesMethod() =
133     MethodSpec.methodBuilder("provide")
134       .addAnnotation(ClassNames.PROVIDES)
135       .addAnnotation(ClassNames.INTO_SET)
136       .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_KEYS_QUALIFIER)
137       .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
138       .returns(String::class.java)
139       .addStatement("return $S", injectedViewModel.className.reflectionName())
140       .build()
141 
142   private fun createModuleTypeSpec(className: String, component: ClassName) =
143     TypeSpec.classBuilder(className)
144       .addOriginatingElement(injectedViewModel.typeElement)
145       .addAnnotation(ClassNames.MODULE)
146       .addAnnotation(
147         AnnotationSpec.builder(ClassNames.INSTALL_IN)
148           .addMember("value", "$T.class", component)
149           .build()
150       )
151       .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
152 
153   companion object {
154 
155     const val L = "\$L"
156     const val T = "\$T"
157     const val N = "\$N"
158     const val S = "\$S"
159     const val W = "\$W"
160 
161     private fun TypeSpec.Builder.addGeneratedAnnotation(
162       elements: Elements,
163       sourceVersion: SourceVersion
164     ) = apply {
165       GeneratedAnnotationSpecs.generatedAnnotationSpec(
166         elements,
167         sourceVersion,
168         ViewModelProcessor::class.java
169       ).ifPresent { generatedAnnotation ->
170         addAnnotation(generatedAnnotation)
171       }
172     }
173   }
174 }
175