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.ClassName;
20 import com.squareup.javapoet.FieldSpec;
21 import com.squareup.javapoet.JavaFile;
22 import com.squareup.javapoet.MethodSpec;
23 import com.squareup.javapoet.TypeSpec;
24 import com.squareup.javapoet.TypeVariableName;
25 import dagger.hilt.android.processor.internal.AndroidClassNames;
26 import dagger.hilt.processor.internal.ClassNames;
27 import dagger.hilt.processor.internal.Processors;
28 import java.io.IOException;
29 import javax.annotation.processing.ProcessingEnvironment;
30 import javax.lang.model.element.Modifier;
31 
32 /** Generates an Hilt Fragment class for the @AndroidEntryPoint annotated class. */
33 public final class FragmentGenerator {
34   private static final FieldSpec COMPONENT_CONTEXT_FIELD =
35       FieldSpec.builder(AndroidClassNames.CONTEXT_WRAPPER, "componentContext")
36           .addModifiers(Modifier.PRIVATE)
37           .build();
38 
39   private final ProcessingEnvironment env;
40   private final AndroidEntryPointMetadata metadata;
41   private final ClassName generatedClassName;
42 
FragmentGenerator( ProcessingEnvironment env, AndroidEntryPointMetadata metadata )43   public FragmentGenerator(
44       ProcessingEnvironment env,
45       AndroidEntryPointMetadata metadata ) {
46     this.env = env;
47     this.metadata = metadata;
48     generatedClassName = metadata.generatedClassName();
49   }
50 
generate()51   public void generate() throws IOException {
52     JavaFile.builder(generatedClassName.packageName(), createTypeSpec())
53         .build()
54         .writeTo(env.getFiler());
55   }
56 
57   // @Generated("FragmentGenerator")
58   // abstract class Hilt_$CLASS extends $BASE implements ComponentManager<?> {
59   //   ...
60   // }
createTypeSpec()61   TypeSpec createTypeSpec() {
62     TypeSpec.Builder builder =
63         TypeSpec.classBuilder(generatedClassName.simpleName())
64             .addOriginatingElement(metadata.element())
65             .superclass(metadata.baseClassName())
66             .addModifiers(metadata.generatedClassModifiers())
67             .addField(COMPONENT_CONTEXT_FIELD)
68             .addMethod(onAttachContextMethod())
69             .addMethod(onAttachActivityMethod())
70             .addMethod(initializeComponentContextMethod())
71             .addMethod(getContextMethod())
72             .addMethod(inflatorMethod());
73 
74     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
75     Processors.addGeneratedAnnotation(builder, env, getClass());
76     Generators.copyLintAnnotations(metadata.element(), builder);
77     Generators.addSuppressAnnotation(builder, "deprecation");
78     Generators.copyConstructors(metadata.baseElement(), builder);
79 
80     metadata.baseElement().getTypeParameters().stream()
81         .map(TypeVariableName::get)
82         .forEachOrdered(builder::addTypeVariable);
83 
84     Generators.addComponentOverride(metadata, builder);
85 
86     Generators.addInjectionMethods(metadata, builder);
87 
88     if (!metadata.overridesAndroidEntryPointClass() ) {
89       builder.addMethod(getDefaultViewModelProviderFactory());
90     }
91 
92     return builder.build();
93   }
94 
95   // @CallSuper
96   // @Override
97   // public void onAttach(Activity activity) {
98   //   super.onAttach(activity);
99   //   initializeComponentContext();
100   // }
onAttachContextMethod()101   private static MethodSpec onAttachContextMethod() {
102     return MethodSpec.methodBuilder("onAttach")
103         .addAnnotation(Override.class)
104         .addAnnotation(AndroidClassNames.CALL_SUPER)
105         .addModifiers(Modifier.PUBLIC)
106         .addParameter(AndroidClassNames.CONTEXT, "context")
107         .addStatement("super.onAttach(context)")
108         .addStatement("initializeComponentContext()")
109         .build();
110   }
111 
112   // @CallSuper
113   // @Override
114   // public void onAttach(Activity activity) {
115   //   super.onAttach(activity);
116   //   Preconditions.checkState(
117   //       componentContext == null || FragmentComponentManager.findActivity(
118   //           componentContext) == activity, "...");
119   //   initializeComponentContext();
120   // }
onAttachActivityMethod()121   private static MethodSpec onAttachActivityMethod() {
122     return MethodSpec.methodBuilder("onAttach")
123         .addAnnotation(Override.class)
124         .addAnnotation(AndroidClassNames.CALL_SUPER)
125         .addAnnotation(AndroidClassNames.MAIN_THREAD)
126         .addModifiers(Modifier.PUBLIC)
127         .addParameter(AndroidClassNames.ACTIVITY, "activity")
128         .addStatement("super.onAttach(activity)")
129         .addStatement(
130             "$T.checkState($N == null || $T.findActivity($N) == activity, $S)",
131             ClassNames.PRECONDITIONS,
132             COMPONENT_CONTEXT_FIELD,
133             AndroidClassNames.FRAGMENT_COMPONENT_MANAGER,
134             COMPONENT_CONTEXT_FIELD,
135             "onAttach called multiple times with different Context! "
136         + "Hilt Fragments should not be retained.")
137         .addStatement("initializeComponentContext()")
138         .build();
139   }
140 
141   // private void initializeComponentContext() {
142   //   // Only inject on the first call to onAttach.
143   //   if (componentContext == null) {
144   //     // Note: The LayoutInflater provided by this componentContext may be different from super
145   //     // Fragment's because we are getting it from base context instead of cloning from super
146   //     // Fragment's LayoutInflater.
147   //     componentContext = FragmentComponentManager.createContextWrapper(super.getContext(), this);
148   //     inject();
149   //   }
150   // }
initializeComponentContextMethod()151   private MethodSpec initializeComponentContextMethod() {
152     return MethodSpec.methodBuilder("initializeComponentContext")
153         .addModifiers(Modifier.PRIVATE)
154         .addComment("Only inject on the first call to onAttach.")
155         .beginControlFlow("if ($N == null)", COMPONENT_CONTEXT_FIELD)
156         .addComment(
157             "Note: The LayoutInflater provided by this componentContext may be different from"
158                 + " super Fragment's because we getting it from base context instead of cloning"
159                 + " from the super Fragment's LayoutInflater.")
160         .addStatement(
161             "$N = $T.createContextWrapper(super.getContext(), this)",
162             COMPONENT_CONTEXT_FIELD,
163             metadata.componentManager())
164         .addStatement("inject()")
165         .endControlFlow()
166         .build();
167   }
168 
169   // @Override
170   // public Context getContext() {
171   //   return componentContext;
172   // }
getContextMethod()173   private static MethodSpec getContextMethod() {
174     return MethodSpec.methodBuilder("getContext")
175         .returns(AndroidClassNames.CONTEXT)
176         .addAnnotation(Override.class)
177         .addModifiers(Modifier.PUBLIC)
178         .addStatement("return $N", COMPONENT_CONTEXT_FIELD)
179         .build();
180   }
181 
182   // @Override
183   // public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
184   //   LayoutInflater inflater = super.onGetLayoutInflater(savedInstanceState);
185   //   return LayoutInflater.from(FragmentComponentManager.createContextWrapper(inflater, this));
186   // }
inflatorMethod()187   private MethodSpec inflatorMethod() {
188     return MethodSpec.methodBuilder("onGetLayoutInflater")
189         .addAnnotation(Override.class)
190         .addModifiers(Modifier.PUBLIC)
191         .addParameter(AndroidClassNames.BUNDLE, "savedInstanceState")
192         .returns(AndroidClassNames.LAYOUT_INFLATER)
193         .addStatement(
194             "$T inflater = super.onGetLayoutInflater(savedInstanceState)",
195             AndroidClassNames.LAYOUT_INFLATER)
196         .addStatement(
197             "return $T.from($T.createContextWrapper(inflater, this))",
198             AndroidClassNames.LAYOUT_INFLATER,
199             metadata.componentManager())
200         .build();
201   }
202 
203   // @Override
204   // public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
205   //   return DefaultViewModelFactories.getFragmentFactory(this);
206   // }
getDefaultViewModelProviderFactory()207   private MethodSpec getDefaultViewModelProviderFactory() {
208     return MethodSpec.methodBuilder("getDefaultViewModelProviderFactory")
209         .addAnnotation(Override.class)
210         .addModifiers(Modifier.PUBLIC)
211         .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY)
212         .addStatement(
213             "return $T.getFragmentFactory(this)",
214             AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES)
215         .build();
216   }
217 }
218