• 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 com.squareup.javapoet.AnnotationSpec;
20 import com.squareup.javapoet.ClassName;
21 import com.squareup.javapoet.FieldSpec;
22 import com.squareup.javapoet.JavaFile;
23 import com.squareup.javapoet.MethodSpec;
24 import com.squareup.javapoet.TypeName;
25 import com.squareup.javapoet.TypeSpec;
26 import com.squareup.javapoet.TypeVariableName;
27 import dagger.hilt.android.processor.internal.AndroidClassNames;
28 import dagger.hilt.processor.internal.ClassNames;
29 import dagger.hilt.processor.internal.Processors;
30 import java.io.IOException;
31 import javax.annotation.processing.ProcessingEnvironment;
32 import javax.lang.model.element.Modifier;
33 
34 /** Generates an Hilt Fragment class for the @AndroidEntryPoint annotated class. */
35 public final class FragmentGenerator {
36   private static final FieldSpec COMPONENT_CONTEXT_FIELD =
37       FieldSpec.builder(AndroidClassNames.CONTEXT_WRAPPER, "componentContext")
38           .addModifiers(Modifier.PRIVATE)
39           .build();
40 
41   private static final FieldSpec DISABLE_GET_CONTEXT_FIX_FIELD =
42       FieldSpec.builder(TypeName.BOOLEAN, "disableGetContextFix")
43           .addModifiers(Modifier.PRIVATE)
44           .build();
45 
46   private final ProcessingEnvironment env;
47   private final AndroidEntryPointMetadata metadata;
48   private final ClassName generatedClassName;
49 
FragmentGenerator( ProcessingEnvironment env, AndroidEntryPointMetadata metadata )50   public FragmentGenerator(
51       ProcessingEnvironment env,
52       AndroidEntryPointMetadata metadata ) {
53     this.env = env;
54     this.metadata = metadata;
55     generatedClassName = metadata.generatedClassName();
56   }
57 
generate()58   public void generate() throws IOException {
59     JavaFile.builder(generatedClassName.packageName(), createTypeSpec())
60         .build()
61         .writeTo(env.getFiler());
62   }
63 
64   // @Generated("FragmentGenerator")
65   // abstract class Hilt_$CLASS extends $BASE implements ComponentManager<?> {
66   //   ...
67   // }
createTypeSpec()68   TypeSpec createTypeSpec() {
69     TypeSpec.Builder builder =
70         TypeSpec.classBuilder(generatedClassName.simpleName())
71             .addOriginatingElement(metadata.element())
72             .superclass(metadata.baseClassName())
73             .addModifiers(metadata.generatedClassModifiers())
74             .addField(COMPONENT_CONTEXT_FIELD)
75             .addMethod(onAttachContextMethod())
76             .addMethod(onAttachActivityMethod())
77             .addMethod(initializeComponentContextMethod())
78             .addMethod(getContextMethod())
79             .addMethod(inflatorMethod())
80             .addField(DISABLE_GET_CONTEXT_FIX_FIELD);
81 
82     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
83     Processors.addGeneratedAnnotation(builder, env, getClass());
84     Generators.copyLintAnnotations(metadata.element(), builder);
85     Generators.copySuppressAnnotations(metadata.element(), builder);
86     Generators.copyConstructors(metadata.baseElement(), builder);
87 
88     metadata.baseElement().getTypeParameters().stream()
89         .map(TypeVariableName::get)
90         .forEachOrdered(builder::addTypeVariable);
91 
92     Generators.addComponentOverride(metadata, builder);
93 
94     Generators.addInjectionMethods(metadata, builder);
95 
96     if (!metadata.overridesAndroidEntryPointClass() ) {
97       builder.addMethod(getDefaultViewModelProviderFactory());
98     }
99 
100     return builder.build();
101   }
102 
103   // @CallSuper
104   // @Override
105   // public void onAttach(Context context) {
106   //   super.onAttach(context);
107   //   initializeComponentContext();
108   //   inject();
109   // }
onAttachContextMethod()110   private static MethodSpec onAttachContextMethod() {
111     return MethodSpec.methodBuilder("onAttach")
112         .addAnnotation(Override.class)
113         .addAnnotation(AndroidClassNames.CALL_SUPER)
114         .addModifiers(Modifier.PUBLIC)
115         .addParameter(AndroidClassNames.CONTEXT, "context")
116         .addStatement("super.onAttach(context)")
117         .addStatement("initializeComponentContext()")
118         // The inject method will internally check if injected already
119         .addStatement("inject()")
120         .build();
121   }
122 
123   // @CallSuper
124   // @Override
125   // @SuppressWarnings("deprecation")
126   // public void onAttach(Activity activity) {
127   //   super.onAttach(activity);
128   //   Preconditions.checkState(
129   //       componentContext == null || FragmentComponentManager.findActivity(
130   //           componentContext) == activity, "...");
131   //   initializeComponentContext();
132   //   inject();
133   // }
onAttachActivityMethod()134   private static MethodSpec onAttachActivityMethod() {
135     return MethodSpec.methodBuilder("onAttach")
136         .addAnnotation(Override.class)
137         .addAnnotation(
138             AnnotationSpec.builder(ClassNames.SUPPRESS_WARNINGS)
139                 .addMember("value", "\"deprecation\"")
140                 .build())
141         .addAnnotation(AndroidClassNames.CALL_SUPER)
142         .addAnnotation(AndroidClassNames.MAIN_THREAD)
143         .addModifiers(Modifier.PUBLIC)
144         .addParameter(AndroidClassNames.ACTIVITY, "activity")
145         .addStatement("super.onAttach(activity)")
146         .addStatement(
147             "$T.checkState($N == null || $T.findActivity($N) == activity, $S)",
148             ClassNames.PRECONDITIONS,
149             COMPONENT_CONTEXT_FIELD,
150             AndroidClassNames.FRAGMENT_COMPONENT_MANAGER,
151             COMPONENT_CONTEXT_FIELD,
152             "onAttach called multiple times with different Context! "
153         + "Hilt Fragments should not be retained.")
154         .addStatement("initializeComponentContext()")
155         // The inject method will internally check if injected already
156         .addStatement("inject()")
157         .build();
158   }
159 
160   // private void initializeComponentContext() {
161   //   if (componentContext == null) {
162   //     // Note: The LayoutInflater provided by this componentContext may be different from super
163   //     // Fragment's because we are getting it from base context instead of cloning from super
164   //     // Fragment's LayoutInflater.
165   //     componentContext = FragmentComponentManager.createContextWrapper(super.getContext(), this);
166   //     disableGetContextFix = FragmentGetContextFix.isFragmentGetContextFixDisabled(
167   //         super.getContext());
168   //   }
169   // }
initializeComponentContextMethod()170   private MethodSpec initializeComponentContextMethod() {
171     MethodSpec.Builder builder = MethodSpec.methodBuilder("initializeComponentContext")
172         .addModifiers(Modifier.PRIVATE)
173         .beginControlFlow("if ($N == null)", COMPONENT_CONTEXT_FIELD)
174         .addComment(
175             "Note: The LayoutInflater provided by this componentContext may be different from"
176                 + " super Fragment's because we getting it from base context instead of cloning"
177                 + " from the super Fragment's LayoutInflater.")
178         .addStatement(
179             "$N = $T.createContextWrapper(super.getContext(), this)",
180             COMPONENT_CONTEXT_FIELD,
181             metadata.componentManager());
182     if (metadata.allowsOptionalInjection()) {
183       // When optionally injected, since the runtime flag is only available in Hilt, we need to
184       // check that the parent uses Hilt first.
185       builder.beginControlFlow("if (optionalInjectParentUsesHilt(optionalInjectGetParent()))");
186     }
187 
188     builder
189         .addStatement("$N = $T.isFragmentGetContextFixDisabled(super.getContext())",
190             DISABLE_GET_CONTEXT_FIX_FIELD,
191             AndroidClassNames.FRAGMENT_GET_CONTEXT_FIX);
192 
193     if (metadata.allowsOptionalInjection()) {
194       // If not attached to a Hilt parent, just disable the fix for now since this is the current
195       // default. There's not a good way to flip this at runtime without Hilt, so after we flip
196       // the default we may just have to flip this and hope that the Hilt usage is already enough
197       // coverage as this should be a fairly rare case.
198       builder.nextControlFlow("else")
199           .addStatement("$N = true", DISABLE_GET_CONTEXT_FIX_FIELD)
200           .endControlFlow();
201     }
202 
203     return builder
204         .endControlFlow()
205         .build();
206   }
207 
208   // @Override
209   // public Context getContext() {
210   //   if (super.getContext() == null && !disableGetContextFix) {
211   //     return null;
212   //   }
213   //   initializeComponentContext();
214   //   return componentContext;
215   // }
getContextMethod()216   private MethodSpec getContextMethod() {
217     return MethodSpec.methodBuilder("getContext")
218         .returns(AndroidClassNames.CONTEXT)
219         .addAnnotation(Override.class)
220         .addModifiers(Modifier.PUBLIC)
221         // Note that disableGetContext can only be true if componentContext is set, so if it is
222         // true we don't need to check whether componentContext is set or not.
223         .beginControlFlow(
224             "if (super.getContext() == null && !$N)",
225             DISABLE_GET_CONTEXT_FIX_FIELD)
226         .addStatement("return null")
227         .endControlFlow()
228         .addStatement("initializeComponentContext()")
229         .addStatement("return $N", COMPONENT_CONTEXT_FIELD)
230         .build();
231   }
232 
233   // @Override
234   // public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
235   //   LayoutInflater inflater = super.onGetLayoutInflater(savedInstanceState);
236   //   return LayoutInflater.from(FragmentComponentManager.createContextWrapper(inflater, this));
237   // }
inflatorMethod()238   private MethodSpec inflatorMethod() {
239     return MethodSpec.methodBuilder("onGetLayoutInflater")
240         .addAnnotation(Override.class)
241         .addModifiers(Modifier.PUBLIC)
242         .addParameter(AndroidClassNames.BUNDLE, "savedInstanceState")
243         .returns(AndroidClassNames.LAYOUT_INFLATER)
244         .addStatement(
245             "$T inflater = super.onGetLayoutInflater(savedInstanceState)",
246             AndroidClassNames.LAYOUT_INFLATER)
247         .addStatement(
248             "return $T.from($T.createContextWrapper(inflater, this))",
249             AndroidClassNames.LAYOUT_INFLATER,
250             metadata.componentManager())
251         .build();
252   }
253 
254   // @Override
255   // public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
256   //   return DefaultViewModelFactories.getFragmentFactory(
257   //       this, super.getDefaultViewModelProviderFactory());
258   // }
getDefaultViewModelProviderFactory()259   private MethodSpec getDefaultViewModelProviderFactory() {
260     MethodSpec.Builder builder = MethodSpec.methodBuilder("getDefaultViewModelProviderFactory")
261         .addAnnotation(Override.class)
262         .addModifiers(Modifier.PUBLIC)
263         .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY);
264 
265     if (metadata.allowsOptionalInjection()) {
266       builder
267           .beginControlFlow("if (!optionalInjectParentUsesHilt(optionalInjectGetParent()))")
268           .addStatement("return super.getDefaultViewModelProviderFactory()")
269           .endControlFlow();
270     }
271 
272     return builder
273         .addStatement(
274             "return $T.getFragmentFactory(this, super.getDefaultViewModelProviderFactory())",
275             AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES)
276         .build();
277   }
278 }
279