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