Skip to content

Commit f7ae0a5

Browse files
kuanyingchouDagger Team
authored andcommitted
Add AssistedInject to Hilt ViewModel.
RELNOTES=Add support for using `@AssistedInject` with `@HiltViewModel`. PiperOrigin-RevId: 563184690
1 parent 774bee1 commit f7ae0a5

File tree

16 files changed

+1172
-92
lines changed

16 files changed

+1172
-92
lines changed

java/dagger/hilt/android/internal/lifecycle/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ android_library(
3838
"@maven//:androidx_lifecycle_lifecycle_viewmodel",
3939
"@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
4040
"@maven//:androidx_savedstate_savedstate",
41+
"@maven//:org_jetbrains_kotlin_kotlin_stdlib",
4142
],
4243
)
4344

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (C) 2023 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.internal.lifecycle;
18+
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
import javax.inject.Qualifier;
24+
25+
/**
26+
* Internal qualifier for the multibinding map of assisted factories for @AssistedInject-annotated
27+
* ViewModels used by the {@link dagger.hilt.android.lifecycle.HiltViewModelFactory}.
28+
*/
29+
@Qualifier
30+
@Retention(RetentionPolicy.CLASS)
31+
@Target({ElementType.METHOD, ElementType.PARAMETER})
32+
public @interface HiltViewModelAssistedMap {}

java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616

1717
package dagger.hilt.android.internal.lifecycle;
1818

19+
import static androidx.lifecycle.SavedStateHandleSupport.createSavedStateHandle;
20+
1921
import android.app.Activity;
2022
import android.os.Bundle;
2123
import androidx.annotation.NonNull;
2224
import androidx.annotation.Nullable;
23-
import androidx.lifecycle.AbstractSavedStateViewModelFactory;
24-
import androidx.lifecycle.SavedStateHandle;
2525
import androidx.lifecycle.ViewModel;
2626
import androidx.lifecycle.ViewModelProvider;
2727
import androidx.lifecycle.viewmodel.CreationExtras;
@@ -37,6 +37,7 @@
3737
import java.util.Map;
3838
import java.util.Set;
3939
import javax.inject.Provider;
40+
import kotlin.jvm.functions.Function1;
4041

4142
/**
4243
* View Model Provider Factory for the Hilt Extension.
@@ -55,20 +56,32 @@ public final class HiltViewModelFactory implements ViewModelProvider.Factory {
5556
public interface ViewModelFactoriesEntryPoint {
5657
@HiltViewModelMap
5758
Map<String, Provider<ViewModel>> getHiltViewModelMap();
59+
60+
// From ViewModel class names to user defined @AssistedFactory-annotated implementations.
61+
@HiltViewModelAssistedMap
62+
Map<String, Object> getHiltViewModelAssistedMap();
5863
}
5964

65+
/** Creation extra key for the callbacks that create @AssistedInject-annotated ViewModels. */
66+
public static final CreationExtras.Key<Function1<Object, ViewModel>> CREATION_CALLBACK_KEY =
67+
new CreationExtras.Key<Function1<Object, ViewModel>>() {};
68+
6069
/** Hilt module for providing the empty multi-binding map of ViewModels. */
6170
@Module
6271
@InstallIn(ViewModelComponent.class)
6372
interface ViewModelModule {
6473
@Multibinds
6574
@HiltViewModelMap
6675
Map<String, ViewModel> hiltViewModelMap();
76+
77+
@Multibinds
78+
@HiltViewModelAssistedMap
79+
Map<String, Object> hiltViewModelAssistedMap();
6780
}
6881

6982
private final Set<String> hiltViewModelKeys;
7083
private final ViewModelProvider.Factory delegateFactory;
71-
private final AbstractSavedStateViewModelFactory hiltViewModelFactory;
84+
private final ViewModelProvider.Factory hiltViewModelFactory;
7285

7386
public HiltViewModelFactory(
7487
@NonNull Set<String> hiltViewModelKeys,
@@ -77,31 +90,75 @@ public HiltViewModelFactory(
7790
this.hiltViewModelKeys = hiltViewModelKeys;
7891
this.delegateFactory = delegateFactory;
7992
this.hiltViewModelFactory =
80-
new AbstractSavedStateViewModelFactory() {
93+
new ViewModelProvider.Factory() {
8194
@NonNull
8295
@Override
83-
@SuppressWarnings("unchecked")
84-
protected <T extends ViewModel> T create(
85-
@NonNull String key, @NonNull Class<T> modelClass, @NonNull SavedStateHandle handle) {
96+
public <T extends ViewModel> T create(
97+
@NonNull Class<T> modelClass, @NonNull CreationExtras extras) {
8698
RetainedLifecycleImpl lifecycle = new RetainedLifecycleImpl();
87-
ViewModelComponent component = viewModelComponentBuilder
88-
.savedStateHandle(handle)
89-
.viewModelLifecycle(lifecycle)
90-
.build();
99+
ViewModelComponent component =
100+
viewModelComponentBuilder
101+
.savedStateHandle(createSavedStateHandle(extras))
102+
.viewModelLifecycle(lifecycle)
103+
.build();
104+
T viewModel = createViewModel(component, modelClass, extras);
105+
viewModel.addCloseable(lifecycle::dispatchOnCleared);
106+
return viewModel;
107+
}
108+
109+
private <T extends ViewModel> T createViewModel(
110+
@NonNull ViewModelComponent component,
111+
@NonNull Class<T> modelClass,
112+
@NonNull CreationExtras extras) {
91113
Provider<? extends ViewModel> provider =
92114
EntryPoints.get(component, ViewModelFactoriesEntryPoint.class)
93115
.getHiltViewModelMap()
94116
.get(modelClass.getName());
95-
if (provider == null) {
96-
throw new IllegalStateException(
97-
"Expected the @HiltViewModel-annotated class '"
98-
+ modelClass.getName()
99-
+ "' to be available in the multi-binding of "
100-
+ "@HiltViewModelMap but none was found.");
117+
Function1<Object, ViewModel> creationCallback = extras.get(CREATION_CALLBACK_KEY);
118+
Object assistedFactory =
119+
EntryPoints.get(component, ViewModelFactoriesEntryPoint.class)
120+
.getHiltViewModelAssistedMap()
121+
.get(modelClass.getName());
122+
123+
if (assistedFactory == null) {
124+
if (creationCallback == null) {
125+
if (provider == null) {
126+
throw new IllegalStateException(
127+
"Expected the @HiltViewModel-annotated class "
128+
+ modelClass.getName()
129+
+ " to be available in the multi-binding of "
130+
+ "@HiltViewModelMap"
131+
+ " but none was found.");
132+
} else {
133+
return (T) provider.get();
134+
}
135+
} else {
136+
// Provider could be null or non-null.
137+
throw new IllegalStateException(
138+
"Found creation callback but class "
139+
+ modelClass.getName()
140+
+ " does not have an assisted factory specified in @HiltViewModel.");
141+
}
142+
} else {
143+
if (provider == null) {
144+
if (creationCallback == null) {
145+
throw new IllegalStateException(
146+
"Found @HiltViewModel-annotated class "
147+
+ modelClass.getName()
148+
+ " using @AssistedInject but no creation callback"
149+
+ " was provided in CreationExtras.");
150+
} else {
151+
return (T) creationCallback.invoke(assistedFactory);
152+
}
153+
} else {
154+
// Creation callback could be null or non-null.
155+
throw new AssertionError(
156+
"Found the @HiltViewModel-annotated class "
157+
+ modelClass.getName()
158+
+ " in both the multi-bindings of "
159+
+ "@HiltViewModelMap and @HiltViewModelAssistedMap.");
160+
}
101161
}
102-
ViewModel viewModel = provider.get();
103-
viewModel.addCloseable(lifecycle::dispatchOnCleared);
104-
return (T) viewModel;
105162
}
106163
};
107164
}

java/dagger/hilt/android/lifecycle/HiltViewModel.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,11 @@
6565
@Target(ElementType.TYPE)
6666
@Retention(RetentionPolicy.CLASS)
6767
@GeneratesRootInput
68-
public @interface HiltViewModel {}
68+
public @interface HiltViewModel {
69+
/**
70+
* Returns a factory class that can be used to create this ViewModel with assisted injection. The
71+
* default value `Object.class` denotes that no factory is specified and the ViewModel is not
72+
* assisted injected.
73+
*/
74+
Class<?> assistedFactory() default Object.class;
75+
}

java/dagger/hilt/android/processor/internal/AndroidClassNames.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ public final class AndroidClassNames {
114114
get("dagger.hilt.android.lifecycle", "HiltViewModel");
115115
public static final ClassName HILT_VIEW_MODEL_MAP_QUALIFIER =
116116
get("dagger.hilt.android.internal.lifecycle", "HiltViewModelMap");
117+
118+
public static final ClassName HILT_VIEW_MODEL_ASSISTED_FACTORY_MAP_QUALIFIER =
119+
get("dagger.hilt.android.internal.lifecycle", "HiltViewModelAssistedMap");
120+
117121
public static final ClassName HILT_VIEW_MODEL_KEYS_QUALIFIER =
118122
get("dagger.hilt.android.internal.lifecycle", "HiltViewModelMap", "KeySet");
119123
public static final ClassName VIEW_MODEL = get("androidx.lifecycle", "ViewModel");

java/dagger/hilt/android/processor/internal/viewmodel/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ kt_jvm_library(
3838
"//java/dagger/hilt/android/processor/internal:android_classnames",
3939
"//java/dagger/hilt/processor/internal:base_processor",
4040
"//java/dagger/hilt/processor/internal:classnames",
41+
"//java/dagger/hilt/processor/internal:compiler_options",
4142
"//java/dagger/hilt/processor/internal:processor_errors",
4243
"//java/dagger/hilt/processor/internal:processors",
4344
"//java/dagger/internal/codegen/xprocessing",

0 commit comments

Comments
 (0)