• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.androidentrypoint;
18 
19 import static com.google.common.base.Preconditions.checkState;
20 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
21 import static kotlin.streams.jdk8.StreamsKt.asStream;
22 
23 import androidx.room.compiler.processing.JavaPoetExtKt;
24 import androidx.room.compiler.processing.XAnnotated;
25 import androidx.room.compiler.processing.XExecutableParameterElement;
26 import androidx.room.compiler.processing.XFiler;
27 import androidx.room.compiler.processing.XMethodElement;
28 import androidx.room.compiler.processing.XProcessingEnv;
29 import androidx.room.compiler.processing.XTypeParameterElement;
30 import com.google.common.base.CaseFormat;
31 import com.google.common.collect.ImmutableList;
32 import com.google.common.collect.Iterables;
33 import com.squareup.javapoet.ClassName;
34 import com.squareup.javapoet.CodeBlock;
35 import com.squareup.javapoet.FieldSpec;
36 import com.squareup.javapoet.JavaFile;
37 import com.squareup.javapoet.MethodSpec;
38 import com.squareup.javapoet.ParameterSpec;
39 import com.squareup.javapoet.TypeName;
40 import com.squareup.javapoet.TypeSpec;
41 import dagger.hilt.android.processor.internal.AndroidClassNames;
42 import dagger.hilt.processor.internal.ClassNames;
43 import dagger.hilt.processor.internal.MethodSignature;
44 import dagger.hilt.processor.internal.Processors;
45 import dagger.internal.codegen.xprocessing.XElements;
46 import java.io.IOException;
47 import javax.lang.model.element.Modifier;
48 import javax.tools.Diagnostic;
49 
50 /** Generates an Hilt Activity class for the @AndroidEntryPoint annotated class. */
51 public final class ActivityGenerator {
52   private enum ActivityMethod {
53     ON_CREATE(AndroidClassNames.BUNDLE),
54     ON_STOP(),
55     ON_DESTROY();
56 
57     @SuppressWarnings("ImmutableEnumChecker")
58     private final MethodSignature signature;
59 
ActivityMethod(TypeName... parameterTypes)60     ActivityMethod(TypeName... parameterTypes) {
61       String methodName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name());
62       this.signature = MethodSignature.of(methodName, parameterTypes);
63     }
64   }
65 
66   private static final FieldSpec SAVED_STATE_HANDLE_HOLDER_FIELD =
67       FieldSpec.builder(AndroidClassNames.SAVED_STATE_HANDLE_HOLDER, "savedStateHandleHolder")
68           .addModifiers(Modifier.PRIVATE)
69           .build();
70 
71   private final XProcessingEnv env;
72   private final AndroidEntryPointMetadata metadata;
73   private final ClassName generatedClassName;
74 
ActivityGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata)75   public ActivityGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) {
76     this.env = env;
77     this.metadata = metadata;
78     generatedClassName = metadata.generatedClassName();
79   }
80 
81   // @Generated("ActivityGenerator")
82   // abstract class Hilt_$CLASS extends $BASE implements ComponentManager<?> {
83   //   ...
84   // }
generate()85   public void generate() throws IOException {
86     TypeSpec.Builder builder =
87         TypeSpec.classBuilder(generatedClassName.simpleName())
88             .superclass(metadata.baseClassName())
89             .addModifiers(metadata.generatedClassModifiers());
90 
91     JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
92     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
93     Processors.addGeneratedAnnotation(builder, env, getClass());
94 
95       Generators.copyConstructors(
96           metadata.baseElement(),
97           CodeBlock.builder().addStatement("_initHiltInternal()").build(),
98           builder,
99           metadata.element());
100       builder.addMethod(init());
101       if (!metadata.overridesAndroidEntryPointClass()) {
102         builder
103             .addField(SAVED_STATE_HANDLE_HOLDER_FIELD)
104             .addMethod(initSavedStateHandleHolderMethod())
105             .addMethod(onCreateComponentActivity())
106             .addMethod(onDestroyComponentActivity());
107       }
108 
109     metadata.baseElement().getTypeParameters().stream()
110         .map(XTypeParameterElement::getTypeVariableName)
111         .forEachOrdered(builder::addTypeVariable);
112 
113     Generators.addComponentOverride(metadata, builder);
114     Generators.copyLintAnnotations(metadata.element(), builder);
115     Generators.copySuppressAnnotations(metadata.element(), builder);
116 
117     Generators.addInjectionMethods(metadata, builder);
118 
119     if (Processors.isAssignableFrom(metadata.baseElement(), AndroidClassNames.COMPONENT_ACTIVITY)
120         && !metadata.overridesAndroidEntryPointClass()) {
121       builder.addMethod(getDefaultViewModelProviderFactory());
122     }
123 
124     env.getFiler()
125         .write(
126             JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
127             XFiler.Mode.Isolating);
128   }
129 
130   // private void init() {
131   //   addOnContextAvailableListener(new OnContextAvailableListener() {
132   //     @Override
133   //     public void onContextAvailable(Context context) {
134   //       inject();
135   //     }
136   //   });
137   // }
init()138   private MethodSpec init() {
139     return MethodSpec.methodBuilder("_initHiltInternal")
140         .addModifiers(Modifier.PRIVATE)
141         .addStatement(
142             "addOnContextAvailableListener($L)",
143             TypeSpec.anonymousClassBuilder("")
144                 .addSuperinterface(AndroidClassNames.ON_CONTEXT_AVAILABLE_LISTENER)
145                 .addMethod(
146                     MethodSpec.methodBuilder("onContextAvailable")
147                         .addAnnotation(Override.class)
148                         .addModifiers(Modifier.PUBLIC)
149                         .addParameter(AndroidClassNames.CONTEXT, "context")
150                         .addStatement("inject()")
151                         .build())
152                 .build())
153         .build();
154   }
155 
156   // @Override
157   // public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
158   //   return DefaultViewModelFactories.getActivityFactory(
159   //       this, super.getDefaultViewModelProviderFactory());
160   // }
getDefaultViewModelProviderFactory()161   private MethodSpec getDefaultViewModelProviderFactory() {
162     MethodSpec.Builder builder = MethodSpec.methodBuilder("getDefaultViewModelProviderFactory")
163         .addAnnotation(Override.class)
164         .addModifiers(Modifier.PUBLIC)
165         .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY);
166 
167     if (metadata.allowsOptionalInjection()) {
168       builder
169           .beginControlFlow("if (!optionalInjectParentUsesHilt(optionalInjectGetParent()))")
170           .addStatement("return super.getDefaultViewModelProviderFactory()")
171           .endControlFlow();
172     }
173 
174     return builder
175         .addStatement(
176             "return $T.getActivityFactory(this, super.getDefaultViewModelProviderFactory())",
177             AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES)
178         .build();
179   }
180 
181   // @Override
182   // public void onCreate(Bundle bundle) {
183   //   super.onCreate(savedInstanceState);
184   //   initSavedStateHandleHolder();
185   // }
186   //
onCreateComponentActivity()187   private MethodSpec onCreateComponentActivity() {
188     XMethodElement nearestOverrideMethod =
189         requireNearestOverrideMethod(ActivityMethod.ON_CREATE, metadata);
190     if (nearestOverrideMethod.isFinal()) {
191       env.getMessager()
192           .printMessage(
193               Diagnostic.Kind.ERROR,
194               "Do not mark onCreate as final in base Activity class, as Hilt needs to override it"
195                   + " to inject SavedStateHandle.",
196               nearestOverrideMethod);
197     }
198     ParameterSpec.Builder parameterBuilder =
199         ParameterSpec.builder(AndroidClassNames.BUNDLE, "savedInstanceState");
200     MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("onCreate");
201     // If the sub class is overriding onCreate with @Nullable parameter, then this generated
202     // method will also prefix the parameter with @Nullable.
203     if (isNullable(nearestOverrideMethod.getParameters().get(0))) {
204       parameterBuilder.addAnnotation(AndroidClassNames.NULLABLE);
205     }
206     if (nearestOverrideMethod.hasAnnotation(AndroidClassNames.UI_THREAD)) {
207       methodBuilder.addAnnotation(AndroidClassNames.UI_THREAD);
208     }
209     return methodBuilder
210         .addAnnotation(AndroidClassNames.CALL_SUPER)
211         .addAnnotation(Override.class)
212         .addModifiers(XElements.getModifiers(nearestOverrideMethod))
213         .addParameter(parameterBuilder.build())
214         .addStatement("super.onCreate(savedInstanceState)")
215         .addStatement("initSavedStateHandleHolder()")
216         .build();
217   }
218 
219   // private void initSavedStateHandleHolder() {
220   //   savedStateHandleHolder = componentManager().getSavedStateHandleHolder();
221   //   if (savedStateHandleHolder.isInvalid()) {
222   //     savedStateHandleHolder.setExtras(getDefaultViewModelCreationExtras());
223   //   }
224   // }
initSavedStateHandleHolderMethod()225   private static MethodSpec initSavedStateHandleHolderMethod() {
226     return MethodSpec.methodBuilder("initSavedStateHandleHolder")
227         .addModifiers(Modifier.PRIVATE)
228         .beginControlFlow(
229             "if (getApplication() instanceof $T)", ClassNames.GENERATED_COMPONENT_MANAGER)
230         .addStatement(
231             "$N = componentManager().getSavedStateHandleHolder()", SAVED_STATE_HANDLE_HOLDER_FIELD)
232         .beginControlFlow("if ($N.isInvalid())", SAVED_STATE_HANDLE_HOLDER_FIELD)
233         .addStatement(
234             "$N.setExtras(getDefaultViewModelCreationExtras())", SAVED_STATE_HANDLE_HOLDER_FIELD)
235         .endControlFlow()
236         .endControlFlow()
237         .build();
238   }
239 
isNullable(XExecutableParameterElement element)240   private static boolean isNullable(XExecutableParameterElement element) {
241     return hasNullableAnnotation(element) || hasNullableAnnotation(element.getType());
242   }
243 
hasNullableAnnotation(XAnnotated element)244   private static boolean hasNullableAnnotation(XAnnotated element) {
245     return element.getAllAnnotations().stream()
246         .anyMatch(annotation -> annotation.getClassName().simpleName().equals("Nullable"));
247   }
248 
249   // @Override
250   // public void onDestroy() {
251   //   super.onDestroy();
252   //   if (savedStateHandleHolder != null) {
253   //     savedStateHandleHolder.clear();
254   //   }
255   // }
onDestroyComponentActivity()256   private MethodSpec onDestroyComponentActivity() {
257     XMethodElement nearestOverrideMethod =
258         requireNearestOverrideMethod(ActivityMethod.ON_DESTROY, metadata);
259     if (nearestOverrideMethod.isFinal()) {
260       env.getMessager()
261           .printMessage(
262               Diagnostic.Kind.ERROR,
263               "Do not mark onDestroy as final in base Activity class, as Hilt needs to override it"
264                   + " to clean up SavedStateHandle.",
265               nearestOverrideMethod);
266     }
267     return MethodSpec.methodBuilder("onDestroy")
268         .addAnnotation(Override.class)
269         .addModifiers(XElements.getModifiers(nearestOverrideMethod))
270         .addStatement("super.onDestroy()")
271         .beginControlFlow("if ($N != null)", SAVED_STATE_HANDLE_HOLDER_FIELD)
272         .addStatement("$N.clear()", SAVED_STATE_HANDLE_HOLDER_FIELD)
273         .endControlFlow()
274         .build();
275   }
276 
requireNearestOverrideMethod( ActivityMethod activityMethod, AndroidEntryPointMetadata metadata)277   private static XMethodElement requireNearestOverrideMethod(
278       ActivityMethod activityMethod, AndroidEntryPointMetadata metadata) {
279     XMethodElement methodOnAndroidEntryPointElement =
280         metadata.element().getDeclaredMethods().stream()
281             .filter(method -> MethodSignature.of(method).equals(activityMethod.signature))
282             .findFirst()
283             .orElse(null);
284     if (methodOnAndroidEntryPointElement != null) {
285       return methodOnAndroidEntryPointElement;
286     }
287 
288     ImmutableList<XMethodElement> methodOnBaseElement =
289         asStream(metadata.baseElement().getAllMethods())
290             .filter(method -> MethodSignature.of(method).equals(activityMethod.signature))
291             .collect(toImmutableList());
292     checkState(methodOnBaseElement.size() >= 1);
293     return Iterables.getLast(methodOnBaseElement);
294   }
295 }
296