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 static com.google.common.base.Preconditions.checkState; 20 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 21 import static kotlin.streams.jdk8.StreamsKt.asStream; 22 23 import androidx.room.compiler.processing.JavaPoetExtKt; 24 import androidx.room.compiler.processing.XAnnotated; 25 import androidx.room.compiler.processing.XExecutableParameterElement; 26 import androidx.room.compiler.processing.XFiler; 27 import androidx.room.compiler.processing.XMethodElement; 28 import androidx.room.compiler.processing.XProcessingEnv; 29 import androidx.room.compiler.processing.XTypeParameterElement; 30 import com.google.common.base.CaseFormat; 31 import com.google.common.collect.ImmutableList; 32 import com.google.common.collect.Iterables; 33 import com.squareup.javapoet.ClassName; 34 import com.squareup.javapoet.CodeBlock; 35 import com.squareup.javapoet.FieldSpec; 36 import com.squareup.javapoet.JavaFile; 37 import com.squareup.javapoet.MethodSpec; 38 import com.squareup.javapoet.ParameterSpec; 39 import com.squareup.javapoet.TypeName; 40 import com.squareup.javapoet.TypeSpec; 41 import dagger.hilt.android.processor.internal.AndroidClassNames; 42 import dagger.hilt.processor.internal.ClassNames; 43 import dagger.hilt.processor.internal.MethodSignature; 44 import dagger.hilt.processor.internal.Processors; 45 import dagger.internal.codegen.xprocessing.XElements; 46 import java.io.IOException; 47 import javax.lang.model.element.Modifier; 48 import javax.tools.Diagnostic; 49 50 /** Generates an Hilt Activity class for the @AndroidEntryPoint annotated class. */ 51 public final class ActivityGenerator { 52 private enum ActivityMethod { 53 ON_CREATE(AndroidClassNames.BUNDLE), 54 ON_STOP(), 55 ON_DESTROY(); 56 57 @SuppressWarnings("ImmutableEnumChecker") 58 private final MethodSignature signature; 59 ActivityMethod(TypeName... parameterTypes)60 ActivityMethod(TypeName... parameterTypes) { 61 String methodName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name()); 62 this.signature = MethodSignature.of(methodName, parameterTypes); 63 } 64 } 65 66 private static final FieldSpec SAVED_STATE_HANDLE_HOLDER_FIELD = 67 FieldSpec.builder(AndroidClassNames.SAVED_STATE_HANDLE_HOLDER, "savedStateHandleHolder") 68 .addModifiers(Modifier.PRIVATE) 69 .build(); 70 71 private final XProcessingEnv env; 72 private final AndroidEntryPointMetadata metadata; 73 private final ClassName generatedClassName; 74 ActivityGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata)75 public ActivityGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) { 76 this.env = env; 77 this.metadata = metadata; 78 generatedClassName = metadata.generatedClassName(); 79 } 80 81 // @Generated("ActivityGenerator") 82 // abstract class Hilt_$CLASS extends $BASE implements ComponentManager<?> { 83 // ... 84 // } generate()85 public void generate() throws IOException { 86 TypeSpec.Builder builder = 87 TypeSpec.classBuilder(generatedClassName.simpleName()) 88 .superclass(metadata.baseClassName()) 89 .addModifiers(metadata.generatedClassModifiers()); 90 91 JavaPoetExtKt.addOriginatingElement(builder, metadata.element()); 92 Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT); 93 Processors.addGeneratedAnnotation(builder, env, getClass()); 94 95 Generators.copyConstructors( 96 metadata.baseElement(), 97 CodeBlock.builder().addStatement("_initHiltInternal()").build(), 98 builder, 99 metadata.element()); 100 builder.addMethod(init()); 101 if (!metadata.overridesAndroidEntryPointClass()) { 102 builder 103 .addField(SAVED_STATE_HANDLE_HOLDER_FIELD) 104 .addMethod(initSavedStateHandleHolderMethod()) 105 .addMethod(onCreateComponentActivity()) 106 .addMethod(onDestroyComponentActivity()); 107 } 108 109 metadata.baseElement().getTypeParameters().stream() 110 .map(XTypeParameterElement::getTypeVariableName) 111 .forEachOrdered(builder::addTypeVariable); 112 113 Generators.addComponentOverride(metadata, builder); 114 Generators.copyLintAnnotations(metadata.element(), builder); 115 Generators.copySuppressAnnotations(metadata.element(), builder); 116 117 Generators.addInjectionMethods(metadata, builder); 118 119 if (Processors.isAssignableFrom(metadata.baseElement(), AndroidClassNames.COMPONENT_ACTIVITY) 120 && !metadata.overridesAndroidEntryPointClass()) { 121 builder.addMethod(getDefaultViewModelProviderFactory()); 122 } 123 124 env.getFiler() 125 .write( 126 JavaFile.builder(generatedClassName.packageName(), builder.build()).build(), 127 XFiler.Mode.Isolating); 128 } 129 130 // private void init() { 131 // addOnContextAvailableListener(new OnContextAvailableListener() { 132 // @Override 133 // public void onContextAvailable(Context context) { 134 // inject(); 135 // } 136 // }); 137 // } init()138 private MethodSpec init() { 139 return MethodSpec.methodBuilder("_initHiltInternal") 140 .addModifiers(Modifier.PRIVATE) 141 .addStatement( 142 "addOnContextAvailableListener($L)", 143 TypeSpec.anonymousClassBuilder("") 144 .addSuperinterface(AndroidClassNames.ON_CONTEXT_AVAILABLE_LISTENER) 145 .addMethod( 146 MethodSpec.methodBuilder("onContextAvailable") 147 .addAnnotation(Override.class) 148 .addModifiers(Modifier.PUBLIC) 149 .addParameter(AndroidClassNames.CONTEXT, "context") 150 .addStatement("inject()") 151 .build()) 152 .build()) 153 .build(); 154 } 155 156 // @Override 157 // public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { 158 // return DefaultViewModelFactories.getActivityFactory( 159 // this, super.getDefaultViewModelProviderFactory()); 160 // } getDefaultViewModelProviderFactory()161 private MethodSpec getDefaultViewModelProviderFactory() { 162 MethodSpec.Builder builder = MethodSpec.methodBuilder("getDefaultViewModelProviderFactory") 163 .addAnnotation(Override.class) 164 .addModifiers(Modifier.PUBLIC) 165 .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY); 166 167 if (metadata.allowsOptionalInjection()) { 168 builder 169 .beginControlFlow("if (!optionalInjectParentUsesHilt(optionalInjectGetParent()))") 170 .addStatement("return super.getDefaultViewModelProviderFactory()") 171 .endControlFlow(); 172 } 173 174 return builder 175 .addStatement( 176 "return $T.getActivityFactory(this, super.getDefaultViewModelProviderFactory())", 177 AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES) 178 .build(); 179 } 180 181 // @Override 182 // public void onCreate(Bundle bundle) { 183 // super.onCreate(savedInstanceState); 184 // initSavedStateHandleHolder(); 185 // } 186 // onCreateComponentActivity()187 private MethodSpec onCreateComponentActivity() { 188 XMethodElement nearestOverrideMethod = 189 requireNearestOverrideMethod(ActivityMethod.ON_CREATE, metadata); 190 if (nearestOverrideMethod.isFinal()) { 191 env.getMessager() 192 .printMessage( 193 Diagnostic.Kind.ERROR, 194 "Do not mark onCreate as final in base Activity class, as Hilt needs to override it" 195 + " to inject SavedStateHandle.", 196 nearestOverrideMethod); 197 } 198 ParameterSpec.Builder parameterBuilder = 199 ParameterSpec.builder(AndroidClassNames.BUNDLE, "savedInstanceState"); 200 MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("onCreate"); 201 // If the sub class is overriding onCreate with @Nullable parameter, then this generated 202 // method will also prefix the parameter with @Nullable. 203 if (isNullable(nearestOverrideMethod.getParameters().get(0))) { 204 parameterBuilder.addAnnotation(AndroidClassNames.NULLABLE); 205 } 206 if (nearestOverrideMethod.hasAnnotation(AndroidClassNames.UI_THREAD)) { 207 methodBuilder.addAnnotation(AndroidClassNames.UI_THREAD); 208 } 209 return methodBuilder 210 .addAnnotation(AndroidClassNames.CALL_SUPER) 211 .addAnnotation(Override.class) 212 .addModifiers(XElements.getModifiers(nearestOverrideMethod)) 213 .addParameter(parameterBuilder.build()) 214 .addStatement("super.onCreate(savedInstanceState)") 215 .addStatement("initSavedStateHandleHolder()") 216 .build(); 217 } 218 219 // private void initSavedStateHandleHolder() { 220 // savedStateHandleHolder = componentManager().getSavedStateHandleHolder(); 221 // if (savedStateHandleHolder.isInvalid()) { 222 // savedStateHandleHolder.setExtras(getDefaultViewModelCreationExtras()); 223 // } 224 // } initSavedStateHandleHolderMethod()225 private static MethodSpec initSavedStateHandleHolderMethod() { 226 return MethodSpec.methodBuilder("initSavedStateHandleHolder") 227 .addModifiers(Modifier.PRIVATE) 228 .beginControlFlow( 229 "if (getApplication() instanceof $T)", ClassNames.GENERATED_COMPONENT_MANAGER) 230 .addStatement( 231 "$N = componentManager().getSavedStateHandleHolder()", SAVED_STATE_HANDLE_HOLDER_FIELD) 232 .beginControlFlow("if ($N.isInvalid())", SAVED_STATE_HANDLE_HOLDER_FIELD) 233 .addStatement( 234 "$N.setExtras(getDefaultViewModelCreationExtras())", SAVED_STATE_HANDLE_HOLDER_FIELD) 235 .endControlFlow() 236 .endControlFlow() 237 .build(); 238 } 239 isNullable(XExecutableParameterElement element)240 private static boolean isNullable(XExecutableParameterElement element) { 241 return hasNullableAnnotation(element) || hasNullableAnnotation(element.getType()); 242 } 243 hasNullableAnnotation(XAnnotated element)244 private static boolean hasNullableAnnotation(XAnnotated element) { 245 return element.getAllAnnotations().stream() 246 .anyMatch(annotation -> annotation.getClassName().simpleName().equals("Nullable")); 247 } 248 249 // @Override 250 // public void onDestroy() { 251 // super.onDestroy(); 252 // if (savedStateHandleHolder != null) { 253 // savedStateHandleHolder.clear(); 254 // } 255 // } onDestroyComponentActivity()256 private MethodSpec onDestroyComponentActivity() { 257 XMethodElement nearestOverrideMethod = 258 requireNearestOverrideMethod(ActivityMethod.ON_DESTROY, metadata); 259 if (nearestOverrideMethod.isFinal()) { 260 env.getMessager() 261 .printMessage( 262 Diagnostic.Kind.ERROR, 263 "Do not mark onDestroy as final in base Activity class, as Hilt needs to override it" 264 + " to clean up SavedStateHandle.", 265 nearestOverrideMethod); 266 } 267 return MethodSpec.methodBuilder("onDestroy") 268 .addAnnotation(Override.class) 269 .addModifiers(XElements.getModifiers(nearestOverrideMethod)) 270 .addStatement("super.onDestroy()") 271 .beginControlFlow("if ($N != null)", SAVED_STATE_HANDLE_HOLDER_FIELD) 272 .addStatement("$N.clear()", SAVED_STATE_HANDLE_HOLDER_FIELD) 273 .endControlFlow() 274 .build(); 275 } 276 requireNearestOverrideMethod( ActivityMethod activityMethod, AndroidEntryPointMetadata metadata)277 private static XMethodElement requireNearestOverrideMethod( 278 ActivityMethod activityMethod, AndroidEntryPointMetadata metadata) { 279 XMethodElement methodOnAndroidEntryPointElement = 280 metadata.element().getDeclaredMethods().stream() 281 .filter(method -> MethodSignature.of(method).equals(activityMethod.signature)) 282 .findFirst() 283 .orElse(null); 284 if (methodOnAndroidEntryPointElement != null) { 285 return methodOnAndroidEntryPointElement; 286 } 287 288 ImmutableList<XMethodElement> methodOnBaseElement = 289 asStream(metadata.baseElement().getAllMethods()) 290 .filter(method -> MethodSignature.of(method).equals(activityMethod.signature)) 291 .collect(toImmutableList()); 292 checkState(methodOnBaseElement.size() >= 1); 293 return Iterables.getLast(methodOnBaseElement); 294 } 295 } 296