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