• 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       builder.addMethod(init());
100       if (!metadata.overridesAndroidEntryPointClass()) {
101         builder
102             .addField(SAVED_STATE_HANDLE_HOLDER_FIELD)
103             .addMethod(initSavedStateHandleHolderMethod())
104             .addMethod(onCreateComponentActivity())
105             .addMethod(onDestroyComponentActivity());
106       }
107 
108     metadata.baseElement().getTypeParameters().stream()
109         .map(XTypeParameterElement::getTypeVariableName)
110         .forEachOrdered(builder::addTypeVariable);
111 
112     Generators.addComponentOverride(metadata, builder);
113     Generators.copyLintAnnotations(metadata.element(), builder);
114     Generators.copySuppressAnnotations(metadata.element(), builder);
115 
116     Generators.addInjectionMethods(metadata, builder);
117 
118     if (Processors.isAssignableFrom(metadata.baseElement(), AndroidClassNames.COMPONENT_ACTIVITY)
119         && !metadata.overridesAndroidEntryPointClass()) {
120       builder.addMethod(getDefaultViewModelProviderFactory());
121     }
122 
123     env.getFiler()
124         .write(
125             JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
126             XFiler.Mode.Isolating);
127   }
128 
129   // private void init() {
130   //   addOnContextAvailableListener(new OnContextAvailableListener() {
131   //     @Override
132   //     public void onContextAvailable(Context context) {
133   //       inject();
134   //     }
135   //   });
136   // }
init()137   private MethodSpec init() {
138     return MethodSpec.methodBuilder("_initHiltInternal")
139         .addModifiers(Modifier.PRIVATE)
140         .addStatement(
141             "addOnContextAvailableListener($L)",
142             TypeSpec.anonymousClassBuilder("")
143                 .addSuperinterface(AndroidClassNames.ON_CONTEXT_AVAILABLE_LISTENER)
144                 .addMethod(
145                     MethodSpec.methodBuilder("onContextAvailable")
146                         .addAnnotation(Override.class)
147                         .addModifiers(Modifier.PUBLIC)
148                         .addParameter(AndroidClassNames.CONTEXT, "context")
149                         .addStatement("inject()")
150                         .build())
151                 .build())
152         .build();
153   }
154 
155   // @Override
156   // public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
157   //   return DefaultViewModelFactories.getActivityFactory(
158   //       this, super.getDefaultViewModelProviderFactory());
159   // }
getDefaultViewModelProviderFactory()160   private MethodSpec getDefaultViewModelProviderFactory() {
161     MethodSpec.Builder builder = MethodSpec.methodBuilder("getDefaultViewModelProviderFactory")
162         .addAnnotation(Override.class)
163         .addModifiers(Modifier.PUBLIC)
164         .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY);
165 
166     if (metadata.allowsOptionalInjection()) {
167       builder
168           .beginControlFlow("if (!optionalInjectParentUsesHilt(optionalInjectGetParent()))")
169           .addStatement("return super.getDefaultViewModelProviderFactory()")
170           .endControlFlow();
171     }
172 
173     return builder
174         .addStatement(
175             "return $T.getActivityFactory(this, super.getDefaultViewModelProviderFactory())",
176             AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES)
177         .build();
178   }
179 
180   // @Override
181   // public void onCreate(Bundle bundle) {
182   //   super.onCreate(savedInstanceState);
183   //   initSavedStateHandleHolder();
184   // }
185   //
onCreateComponentActivity()186   private MethodSpec onCreateComponentActivity() {
187     XMethodElement nearestOverrideMethod =
188         requireNearestOverrideMethod(ActivityMethod.ON_CREATE, metadata);
189     if (nearestOverrideMethod.isFinal()) {
190       env.getMessager()
191           .printMessage(
192               Diagnostic.Kind.ERROR,
193               "Do not mark onCreate as final in base Activity class, as Hilt needs to override it"
194                   + " to inject SavedStateHandle.",
195               nearestOverrideMethod);
196     }
197     ParameterSpec.Builder parameterBuilder =
198         ParameterSpec.builder(AndroidClassNames.BUNDLE, "savedInstanceState");
199     MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("onCreate");
200     // If the sub class is overriding onCreate with @Nullable parameter, then this generated
201     // method will also prefix the parameter with @Nullable.
202     if (isNullable(nearestOverrideMethod.getParameters().get(0))) {
203       parameterBuilder.addAnnotation(AndroidClassNames.NULLABLE);
204     }
205     if (nearestOverrideMethod.hasAnnotation(AndroidClassNames.UI_THREAD)) {
206       methodBuilder.addAnnotation(AndroidClassNames.UI_THREAD);
207     }
208     return methodBuilder
209         .addAnnotation(AndroidClassNames.CALL_SUPER)
210         .addAnnotation(Override.class)
211         .addModifiers(XElements.getModifiers(nearestOverrideMethod))
212         .addParameter(parameterBuilder.build())
213         .addStatement("super.onCreate(savedInstanceState)")
214         .addStatement("initSavedStateHandleHolder()")
215         .build();
216   }
217 
218   // private void initSavedStateHandleHolder() {
219   //   savedStateHandleHolder = componentManager().getSavedStateHandleHolder();
220   //   if (savedStateHandleHolder.isInvalid()) {
221   //     savedStateHandleHolder.setExtras(getDefaultViewModelCreationExtras());
222   //   }
223   // }
initSavedStateHandleHolderMethod()224   private static MethodSpec initSavedStateHandleHolderMethod() {
225     return MethodSpec.methodBuilder("initSavedStateHandleHolder")
226         .addModifiers(Modifier.PRIVATE)
227         .beginControlFlow(
228             "if (getApplication() instanceof $T)", ClassNames.GENERATED_COMPONENT_MANAGER)
229         .addStatement(
230             "$N = componentManager().getSavedStateHandleHolder()", SAVED_STATE_HANDLE_HOLDER_FIELD)
231         .beginControlFlow("if ($N.isInvalid())", SAVED_STATE_HANDLE_HOLDER_FIELD)
232         .addStatement(
233             "$N.setExtras(getDefaultViewModelCreationExtras())", SAVED_STATE_HANDLE_HOLDER_FIELD)
234         .endControlFlow()
235         .endControlFlow()
236         .build();
237   }
238 
isNullable(XExecutableParameterElement element)239   private static boolean isNullable(XExecutableParameterElement element) {
240     return hasNullableAnnotation(element) || hasNullableAnnotation(element.getType());
241   }
242 
hasNullableAnnotation(XAnnotated element)243   private static boolean hasNullableAnnotation(XAnnotated element) {
244     return element.getAllAnnotations().stream()
245         .anyMatch(annotation -> annotation.getClassName().simpleName().equals("Nullable"));
246   }
247 
248   // @Override
249   // public void onDestroy() {
250   //   super.onDestroy();
251   //   if (savedStateHandleHolder != null) {
252   //     savedStateHandleHolder.clear();
253   //   }
254   // }
onDestroyComponentActivity()255   private MethodSpec onDestroyComponentActivity() {
256     XMethodElement nearestOverrideMethod =
257         requireNearestOverrideMethod(ActivityMethod.ON_DESTROY, metadata);
258     if (nearestOverrideMethod.isFinal()) {
259       env.getMessager()
260           .printMessage(
261               Diagnostic.Kind.ERROR,
262               "Do not mark onDestroy as final in base Activity class, as Hilt needs to override it"
263                   + " to clean up SavedStateHandle.",
264               nearestOverrideMethod);
265     }
266     return MethodSpec.methodBuilder("onDestroy")
267         .addAnnotation(Override.class)
268         .addModifiers(XElements.getModifiers(nearestOverrideMethod))
269         .addStatement("super.onDestroy()")
270         .beginControlFlow("if ($N != null)", SAVED_STATE_HANDLE_HOLDER_FIELD)
271         .addStatement("$N.clear()", SAVED_STATE_HANDLE_HOLDER_FIELD)
272         .endControlFlow()
273         .build();
274   }
275 
requireNearestOverrideMethod( ActivityMethod activityMethod, AndroidEntryPointMetadata metadata)276   private static XMethodElement requireNearestOverrideMethod(
277       ActivityMethod activityMethod, AndroidEntryPointMetadata metadata) {
278     XMethodElement methodOnAndroidEntryPointElement =
279         metadata.element().getDeclaredMethods().stream()
280             .filter(method -> MethodSignature.of(method).equals(activityMethod.signature))
281             .findFirst()
282             .orElse(null);
283     if (methodOnAndroidEntryPointElement != null) {
284       return methodOnAndroidEntryPointElement;
285     }
286 
287     ImmutableList<XMethodElement> methodOnBaseElement =
288         asStream(metadata.baseElement().getAllMethods())
289             .filter(method -> MethodSignature.of(method).equals(activityMethod.signature))
290             .collect(toImmutableList());
291     checkState(methodOnBaseElement.size() >= 1);
292     return Iterables.getLast(methodOnBaseElement);
293   }
294 }
295