Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2024 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.internal.work;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;

/**
* Internal qualifier for the multibinding map of @HiltWorker workers.
*/
@Qualifier
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface HiltWorkerMap {

/** Internal qualifier for the multibinding set of class names annotated with @HiltWorker. */
@Qualifier
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.PARAMETER})
@interface KeySet {}
}
61 changes: 61 additions & 0 deletions hilt-android/main/java/dagger/hilt/android/work/HiltWorker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2024 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.work;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.CLASS;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
* Annotates a class that is a WorkManager {@link
* androidx.work.ListenableWorker} to enable injection with Hilt.
*
* <p>Hilt currently supports the following types of workers:
*
* <ul>
* <li>{@link androidx.work.Worker}
* <li>{@link androidx.work.CoroutineWorker}
* </ul>
*
* <p>The annotated class must have a single constructor annotated with
* {@code @Inject} or {@code @AssistedInject}.
*
* <p>Example usage with a Worker:
* <pre>
* &#64;HiltWorker
* public class MyWorker extends Worker {
* &#64;Inject
* public MyWorker(
* &#64;Assisted Context context,
* &#64;Assisted WorkerParameters workerParams,
* MyDependency myDependency
* ) {
* super(context, workerParams);
* }
*
* &#64;Override
* public Result doWork() { ... }
* }
* </pre>
*/
@Documented
@Retention(CLASS)
@Target(TYPE)
public @interface HiltWorker {}
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,14 @@ public final class AndroidClassNames {
public static final ClassName INJECT_VIA_ON_CONTEXT_AVAILABLE_LISTENER =
get("dagger.hilt.android", "InjectViaOnContextAvailableListener");

// WorkManager-related class names
public static final ClassName LISTENABLE_WORKER = get("androidx.work", "ListenableWorker");
public static final ClassName WORKER_PARAMETERS = get("androidx.work", "WorkerParameters");
public static final ClassName HILT_WORKER = get("dagger.hilt.android.work", "HiltWorker");
public static final ClassName HILT_WORKER_MAP_QUALIFIER =
get("dagger.hilt.android.internal.work", "HiltWorkerMap");
public static final ClassName HILT_WORKER_KEYS_QUALIFIER =
get("dagger.hilt.android.internal.work", "HiltWorkerMap", "KeySet");

private AndroidClassNames() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright (C) 2024 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.worker

import androidx.room3.compiler.codegen.toJavaPoet
import androidx.room3.compiler.processing.ExperimentalProcessingApi
import androidx.room3.compiler.processing.XProcessingEnv
import androidx.room3.compiler.processing.XTypeElement
import com.squareup.javapoet.ClassName
import dagger.hilt.android.processor.internal.AndroidClassNames
import dagger.hilt.processor.internal.ClassNames
import dagger.hilt.processor.internal.LazyString
import dagger.hilt.processor.internal.ProcessorErrors
import dagger.hilt.processor.internal.Processors
import dagger.internal.codegen.xprocessing.XAnnotations
import dagger.internal.codegen.xprocessing.XElements
import dagger.internal.codegen.xprocessing.XTypes

@OptIn(
ExperimentalProcessingApi::class,
com.squareup.kotlinpoet.javapoet.KotlinPoetJavaPoetPreview::class
)
internal class HiltWorkerMetadata
private constructor(
val workerElement: XTypeElement,
val isAssistedInject: Boolean,
) {
val className = workerElement.asClassName().toJavaPoet()

val assistedFactoryClassName: ClassName =
ClassName.get(workerElement.packageName, "${className.simpleNames().joinToString("_")}_AssistedFactory")

val modulesClassName =
ClassName.get(
workerElement.packageName,
"${className.simpleNames().joinToString("_")}_HiltModules",
)

companion object {
internal fun create(
processingEnv: XProcessingEnv,
workerElement: XTypeElement,
): HiltWorkerMetadata? {
ProcessorErrors.checkState(
XTypes.isSubtype(
workerElement.type,
processingEnv.requireType(AndroidClassNames.LISTENABLE_WORKER),
),
workerElement,
"@HiltWorker is only supported on types that subclass %s.",
AndroidClassNames.LISTENABLE_WORKER,
)

val injectConstructors =
workerElement.getConstructors().filter { constructor ->
Processors.isAnnotatedWithInject(constructor) ||
constructor.hasAnnotation(ClassNames.ASSISTED_INJECT)
}

ProcessorErrors.checkState(
injectConstructors.size == 1,
workerElement,
"@HiltWorker annotated class should contain exactly one @Inject or @AssistedInject annotated constructor.",
)

val injectConstructor = injectConstructors.single()

ProcessorErrors.checkState(
!injectConstructor.isPrivate(),
injectConstructor,
"%s annotated constructors must not be private.",
if (injectConstructor.hasAnnotation(ClassNames.ASSISTED_INJECT)) {
"@Inject or @AssistedInject"
} else {
"@Inject"
},
)

ProcessorErrors.checkState(
!workerElement.isNested() || workerElement.isStatic(),
workerElement,
"@HiltWorker may only be used on inner classes if they are static.",
)

Processors.getScopeAnnotations(workerElement).let { scopeAnnotations ->
ProcessorErrors.checkState(
scopeAnnotations.isEmpty(),
workerElement,
"@HiltWorker classes should not be scoped. Found: %s",
LazyString.of { scopeAnnotations.joinToString { XAnnotations.toStableString(it) } },
)
}

val isAssistedInject = injectConstructor.hasAnnotation(ClassNames.ASSISTED_INJECT)

return HiltWorkerMetadata(workerElement, isAssistedInject)
}
}
}
Loading