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