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