All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.hilt.android.processor.internal.viewmodel.ViewModelModuleGenerator.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2020 The Dagger Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dagger.hilt.android.processor.internal.viewmodel

import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.processing.ExperimentalProcessingApi
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.addOriginatingElement
import com.squareup.javapoet.AnnotationSpec
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
import dagger.hilt.android.processor.internal.AndroidClassNames
import dagger.hilt.processor.internal.ClassNames
import dagger.hilt.processor.internal.Processors
import javax.lang.model.element.Modifier

/**
 * Source generator to support Hilt injection of ViewModels.
 *
 * Should generate:
 * ```
 * public final class $_HiltModules {
 *   @Module
 *   @InstallIn(ViewModelComponent.class)
 *   public static abstract class BindsModule {
 *     @Binds
 *     @IntoMap
 *     @LazyClassKey(pkg.$)
 *     @HiltViewModelMap
 *     public abstract ViewModel bind($ vm)
 *   }
 *   @Module
 *   @InstallIn(ActivityRetainedComponent.class)
 *   public static final class KeyModule {
 *     private static String className = "pkg.$";
 *     @Provides
 *     @IntoMap
 *     @HiltViewModelMap.KeySet
 *     @LazyClassKey(pkg.$)
 *     public static boolean provide() {
 *       return true;
 *     }
 *   }
 * }
 * ```
 */
@OptIn(ExperimentalProcessingApi::class)
internal class ViewModelModuleGenerator(
  private val processingEnv: XProcessingEnv,
  private val viewModelMetadata: ViewModelMetadata,
) {
  fun generate() {
    val modulesTypeSpec =
      TypeSpec.classBuilder(viewModelMetadata.modulesClassName)
        .apply {
          addOriginatingElement(viewModelMetadata.viewModelElement)
          Processors.addGeneratedAnnotation(this, processingEnv, ViewModelProcessor::class.java)
          addAnnotation(
            AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT)
              .addMember(
                "topLevelClass",
                "$T.class",
                viewModelMetadata.className.topLevelClassName(),
              )
              .build()
          )
          addModifiers(Modifier.PUBLIC, Modifier.FINAL)
          addType(getBindsModuleTypeSpec())
          addType(getKeyModuleTypeSpec())
          addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
        }
        .build()

    processingEnv.filer.write(
      JavaFile.builder(viewModelMetadata.modulesClassName.packageName(), modulesTypeSpec).build()
    )
  }

  private fun getBindsModuleTypeSpec() =
    createModuleTypeSpec(
        className = "BindsModule",
        component = AndroidClassNames.VIEW_MODEL_COMPONENT,
      )
      .addModifiers(Modifier.ABSTRACT)
      .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
      .addMethod(
        if (viewModelMetadata.assistedFactory.asClassName() != XTypeName.ANY_OBJECT) {
          getAssistedViewModelBindsMethod()
        } else {
          getViewModelBindsMethod()
        }
      )
      .build()

  private fun getViewModelBindsMethod() =
    MethodSpec.methodBuilder("binds")
      .addAnnotation(ClassNames.BINDS)
      .addAnnotation(ClassNames.INTO_MAP)
      .addAnnotation(
        AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY)
          .addMember("value", "$T.class", viewModelMetadata.className)
          .build()
      )
      .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_MAP_QUALIFIER)
      .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
      .returns(AndroidClassNames.VIEW_MODEL)
      .addParameter(viewModelMetadata.className, "vm")
      .build()

  private fun getKeyModuleTypeSpec() =
    createModuleTypeSpec(
        className = "KeyModule",
        component = AndroidClassNames.ACTIVITY_RETAINED_COMPONENT,
      )
      .addModifiers(Modifier.FINAL)
      .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
      .addMethod(getViewModelKeyProvidesMethod())
      .build()

  private fun getViewModelKeyProvidesMethod() =
    MethodSpec.methodBuilder("provide")
      .addAnnotation(ClassNames.PROVIDES)
      .addAnnotation(ClassNames.INTO_MAP)
      .addAnnotation(
        AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY)
          .addMember("value", "$T.class", viewModelMetadata.className)
          .build()
      )
      .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_KEYS_QUALIFIER)
      .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
      .returns(Boolean::class.java)
      .addStatement("return true")
      .build()

  /**
   * Should generate:
   * ```
   * @Binds
   * @IntoMap
   * @LazyClassKey(pkg.FooViewModel.class)
   * @HiltViewModelAssistedMap
   * public abstract Object bind(FooViewModelAssistedFactory factory);
   * ```
   *
   * So that we have a HiltViewModelAssistedMap that maps from fully qualified ViewModel names to
   * its assisted factory instance.
   */
  private fun getAssistedViewModelBindsMethod() =
    MethodSpec.methodBuilder("bind")
      .addAnnotation(ClassNames.BINDS)
      .addAnnotation(ClassNames.INTO_MAP)
      .addAnnotation(
        AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY)
          .addMember("value", "$T.class", viewModelMetadata.className)
          .build()
      )
      .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_ASSISTED_FACTORY_MAP_QUALIFIER)
      .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
      .addParameter(viewModelMetadata.assistedFactoryClassName, "factory")
      .returns(TypeName.OBJECT)
      .build()

  private fun createModuleTypeSpec(className: String, component: ClassName) =
    TypeSpec.classBuilder(className)
      .addOriginatingElement(viewModelMetadata.viewModelElement)
      .addAnnotation(ClassNames.MODULE)
      .addAnnotation(
        AnnotationSpec.builder(ClassNames.INSTALL_IN)
          .addMember("value", "$T.class", component)
          .build()
      )
      .addModifiers(Modifier.PUBLIC, Modifier.STATIC)

  companion object {

    const val L = "\$L"
    const val T = "\$T"
    const val N = "\$N"
    const val S = "\$S"
    const val W = "\$W"
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy