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 builder.addMethod(init()); 100 if (!metadata.overridesAndroidEntryPointClass()) { 101 builder 102 .addField(SAVED_STATE_HANDLE_HOLDER_FIELD) 103 .addMethod(initSavedStateHandleHolderMethod()) 104 .addMethod(onCreateComponentActivity()) 105 .addMethod(onDestroyComponentActivity()); 106 } 107 108 metadata.baseElement().getTypeParameters().stream() 109 .map(XTypeParameterElement::getTypeVariableName) 110 .forEachOrdered(builder::addTypeVariable); 111 112 Generators.addComponentOverride(metadata, builder); 113 Generators.copyLintAnnotations(metadata.element(), builder); 114 Generators.copySuppressAnnotations(metadata.element(), builder); 115 116 Generators.addInjectionMethods(metadata, builder); 117 118 if (Processors.isAssignableFrom(metadata.baseElement(), AndroidClassNames.COMPONENT_ACTIVITY) 119 && !metadata.overridesAndroidEntryPointClass()) { 120 builder.addMethod(getDefaultViewModelProviderFactory()); 121 } 122 123 env.getFiler() 124 .write( 125 JavaFile.builder(generatedClassName.packageName(), builder.build()).build(), 126 XFiler.Mode.Isolating); 127 } 128 129 // private void init() { 130 // addOnContextAvailableListener(new OnContextAvailableListener() { 131 // @Override 132 // public void onContextAvailable(Context context) { 133 // inject(); 134 // } 135 // }); 136 // } init()137 private MethodSpec init() { 138 return MethodSpec.methodBuilder("_initHiltInternal") 139 .addModifiers(Modifier.PRIVATE) 140 .addStatement( 141 "addOnContextAvailableListener($L)", 142 TypeSpec.anonymousClassBuilder("") 143 .addSuperinterface(AndroidClassNames.ON_CONTEXT_AVAILABLE_LISTENER) 144 .addMethod( 145 MethodSpec.methodBuilder("onContextAvailable") 146 .addAnnotation(Override.class) 147 .addModifiers(Modifier.PUBLIC) 148 .addParameter(AndroidClassNames.CONTEXT, "context") 149 .addStatement("inject()") 150 .build()) 151 .build()) 152 .build(); 153 } 154 155 // @Override 156 // public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { 157 // return DefaultViewModelFactories.getActivityFactory( 158 // this, super.getDefaultViewModelProviderFactory()); 159 // } getDefaultViewModelProviderFactory()160 private MethodSpec getDefaultViewModelProviderFactory() { 161 MethodSpec.Builder builder = MethodSpec.methodBuilder("getDefaultViewModelProviderFactory") 162 .addAnnotation(Override.class) 163 .addModifiers(Modifier.PUBLIC) 164 .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY); 165 166 if (metadata.allowsOptionalInjection()) { 167 builder 168 .beginControlFlow("if (!optionalInjectParentUsesHilt(optionalInjectGetParent()))") 169 .addStatement("return super.getDefaultViewModelProviderFactory()") 170 .endControlFlow(); 171 } 172 173 return builder 174 .addStatement( 175 "return $T.getActivityFactory(this, super.getDefaultViewModelProviderFactory())", 176 AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES) 177 .build(); 178 } 179 180 // @Override 181 // public void onCreate(Bundle bundle) { 182 // super.onCreate(savedInstanceState); 183 // initSavedStateHandleHolder(); 184 // } 185 // onCreateComponentActivity()186 private MethodSpec onCreateComponentActivity() { 187 XMethodElement nearestOverrideMethod = 188 requireNearestOverrideMethod(ActivityMethod.ON_CREATE, metadata); 189 if (nearestOverrideMethod.isFinal()) { 190 env.getMessager() 191 .printMessage( 192 Diagnostic.Kind.ERROR, 193 "Do not mark onCreate as final in base Activity class, as Hilt needs to override it" 194 + " to inject SavedStateHandle.", 195 nearestOverrideMethod); 196 } 197 ParameterSpec.Builder parameterBuilder = 198 ParameterSpec.builder(AndroidClassNames.BUNDLE, "savedInstanceState"); 199 MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("onCreate"); 200 // If the sub class is overriding onCreate with @Nullable parameter, then this generated 201 // method will also prefix the parameter with @Nullable. 202 if (isNullable(nearestOverrideMethod.getParameters().get(0))) { 203 parameterBuilder.addAnnotation(AndroidClassNames.NULLABLE); 204 } 205 if (nearestOverrideMethod.hasAnnotation(AndroidClassNames.UI_THREAD)) { 206 methodBuilder.addAnnotation(AndroidClassNames.UI_THREAD); 207 } 208 return methodBuilder 209 .addAnnotation(AndroidClassNames.CALL_SUPER) 210 .addAnnotation(Override.class) 211 .addModifiers(XElements.getModifiers(nearestOverrideMethod)) 212 .addParameter(parameterBuilder.build()) 213 .addStatement("super.onCreate(savedInstanceState)") 214 .addStatement("initSavedStateHandleHolder()") 215 .build(); 216 } 217 218 // private void initSavedStateHandleHolder() { 219 // savedStateHandleHolder = componentManager().getSavedStateHandleHolder(); 220 // if (savedStateHandleHolder.isInvalid()) { 221 // savedStateHandleHolder.setExtras(getDefaultViewModelCreationExtras()); 222 // } 223 // } initSavedStateHandleHolderMethod()224 private static MethodSpec initSavedStateHandleHolderMethod() { 225 return MethodSpec.methodBuilder("initSavedStateHandleHolder") 226 .addModifiers(Modifier.PRIVATE) 227 .beginControlFlow( 228 "if (getApplication() instanceof $T)", ClassNames.GENERATED_COMPONENT_MANAGER) 229 .addStatement( 230 "$N = componentManager().getSavedStateHandleHolder()", SAVED_STATE_HANDLE_HOLDER_FIELD) 231 .beginControlFlow("if ($N.isInvalid())", SAVED_STATE_HANDLE_HOLDER_FIELD) 232 .addStatement( 233 "$N.setExtras(getDefaultViewModelCreationExtras())", SAVED_STATE_HANDLE_HOLDER_FIELD) 234 .endControlFlow() 235 .endControlFlow() 236 .build(); 237 } 238 isNullable(XExecutableParameterElement element)239 private static boolean isNullable(XExecutableParameterElement element) { 240 return hasNullableAnnotation(element) || hasNullableAnnotation(element.getType()); 241 } 242 hasNullableAnnotation(XAnnotated element)243 private static boolean hasNullableAnnotation(XAnnotated element) { 244 return element.getAllAnnotations().stream() 245 .anyMatch(annotation -> annotation.getClassName().simpleName().equals("Nullable")); 246 } 247 248 // @Override 249 // public void onDestroy() { 250 // super.onDestroy(); 251 // if (savedStateHandleHolder != null) { 252 // savedStateHandleHolder.clear(); 253 // } 254 // } onDestroyComponentActivity()255 private MethodSpec onDestroyComponentActivity() { 256 XMethodElement nearestOverrideMethod = 257 requireNearestOverrideMethod(ActivityMethod.ON_DESTROY, metadata); 258 if (nearestOverrideMethod.isFinal()) { 259 env.getMessager() 260 .printMessage( 261 Diagnostic.Kind.ERROR, 262 "Do not mark onDestroy as final in base Activity class, as Hilt needs to override it" 263 + " to clean up SavedStateHandle.", 264 nearestOverrideMethod); 265 } 266 return MethodSpec.methodBuilder("onDestroy") 267 .addAnnotation(Override.class) 268 .addModifiers(XElements.getModifiers(nearestOverrideMethod)) 269 .addStatement("super.onDestroy()") 270 .beginControlFlow("if ($N != null)", SAVED_STATE_HANDLE_HOLDER_FIELD) 271 .addStatement("$N.clear()", SAVED_STATE_HANDLE_HOLDER_FIELD) 272 .endControlFlow() 273 .build(); 274 } 275 requireNearestOverrideMethod( ActivityMethod activityMethod, AndroidEntryPointMetadata metadata)276 private static XMethodElement requireNearestOverrideMethod( 277 ActivityMethod activityMethod, AndroidEntryPointMetadata metadata) { 278 XMethodElement methodOnAndroidEntryPointElement = 279 metadata.element().getDeclaredMethods().stream() 280 .filter(method -> MethodSignature.of(method).equals(activityMethod.signature)) 281 .findFirst() 282 .orElse(null); 283 if (methodOnAndroidEntryPointElement != null) { 284 return methodOnAndroidEntryPointElement; 285 } 286 287 ImmutableList<XMethodElement> methodOnBaseElement = 288 asStream(metadata.baseElement().getAllMethods()) 289 .filter(method -> MethodSignature.of(method).equals(activityMethod.signature)) 290 .collect(toImmutableList()); 291 checkState(methodOnBaseElement.size() >= 1); 292 return Iterables.getLast(methodOnBaseElement); 293 } 294 } 295