Skip to content

Commit c3f613f

Browse files
Chang-EricDagger Team
authored andcommitted
Add a runtime flag to control the Fragment.getContext() fix. This runtime flag is used only if the compiler flag is also on.
RELNOTES=Add a runtime flag for the Fragment.getContext() fix. PiperOrigin-RevId: 397816397
1 parent f6a8e0e commit c3f613f

File tree

11 files changed

+234
-13
lines changed

11 files changed

+234
-13
lines changed

java/dagger/hilt/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ filegroup(
135135
srcs = [
136136
"//java/dagger/hilt/android:srcs_filegroup",
137137
"//java/dagger/hilt/android/components:srcs_filegroup",
138+
"//java/dagger/hilt/android/flags:srcs_filegroup",
138139
"//java/dagger/hilt/android/internal:srcs_filegroup",
139140
"//java/dagger/hilt/android/internal/builders:srcs_filegroup",
140141
"//java/dagger/hilt/android/internal/lifecycle:srcs_filegroup",

java/dagger/hilt/android/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ android_library(
3131
exports = [
3232
"//java/dagger/hilt:install_in",
3333
"//java/dagger/hilt/android/components",
34+
"//java/dagger/hilt/android/flags:fragment_get_context_fix",
3435
"//java/dagger/hilt/android/internal",
3536
"//java/dagger/hilt/android/internal/builders",
3637
"//java/dagger/hilt/android/internal/managers",
@@ -184,6 +185,7 @@ gen_maven_artifact(
184185
"//java/dagger/hilt/android:package_info",
185186
"//java/dagger/hilt/android/components",
186187
"//java/dagger/hilt/android/components:package_info",
188+
"//java/dagger/hilt/android/flags:fragment_get_context_fix",
187189
"//java/dagger/hilt/android/internal",
188190
"//java/dagger/hilt/android/internal/builders",
189191
"//java/dagger/hilt/android/internal/earlyentrypoint",
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright (C) 2021 The Dagger Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Description:
16+
# Runtime flags to control Hilt behavior for rollout of changes. These flags are usually
17+
# meant to be temporary and so defaults may change with releases and then these flags
18+
# may eventually be removed, just like compiler options with similar purposes.
19+
20+
package(default_visibility = ["//:src"])
21+
22+
android_library(
23+
name = "fragment_get_context_fix",
24+
srcs = [
25+
"FragmentGetContextFix.java",
26+
],
27+
deps = [
28+
"//:dagger_with_compiler",
29+
"//java/dagger/hilt:entry_point",
30+
"//java/dagger/hilt:install_in",
31+
"//java/dagger/hilt/android:entry_point_accessors",
32+
"//java/dagger/hilt/android/components",
33+
"//java/dagger/hilt/internal:preconditions",
34+
],
35+
)
36+
37+
filegroup(
38+
name = "srcs_filegroup",
39+
srcs = glob(["*"]),
40+
)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright (C) 2021 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.flags;
18+
19+
import android.content.Context;
20+
import dagger.Module;
21+
import dagger.hilt.EntryPoint;
22+
import dagger.hilt.InstallIn;
23+
import dagger.hilt.android.EntryPointAccessors;
24+
import dagger.hilt.components.SingletonComponent;
25+
import dagger.hilt.internal.Preconditions;
26+
import dagger.multibindings.Multibinds;
27+
import java.lang.annotation.ElementType;
28+
import java.lang.annotation.Target;
29+
import java.util.Set;
30+
import javax.inject.Qualifier;
31+
32+
/**
33+
* Runtime flag for the Fragment.getContext() fix. See https://github.com/google/dagger/pull/2620
34+
* for this change. Controls if fragment code should use the fixed getContext() behavior where it
35+
* correctly returns null after a fragment is removed. This fixed behavior matches the behavior of a
36+
* regular, non-Hilt fragment and can help catch issues where a removed or leaked fragment is
37+
* incorrectly used.
38+
*
39+
* <p>This flag is paired with the compiler option flag
40+
* dagger.hilt.android.useFragmentGetContextFix. When that flag is false, this runtime flag has no
41+
* effect on behavior (e.g. the compiler flag being off takes precedence). When the compiler flag is
42+
* on, then the runtime flag may be used to disable the behavior at runtime.
43+
*
44+
* <p>In order to set the flag, bind a boolean value qualified with
45+
* {@link DisableFragmentGetContextFix} into a set in the {@code SingletonComponent}. A set is used
46+
* instead of an optional binding to avoid a dependency on Guava. Only one value may be bound into
47+
* the set within a given app. Example for binding the value:
48+
*
49+
* <pre><code>
50+
* {@literal @}Module
51+
* {@literal @}InstallIn(SingletonComponent.class)
52+
* public final class DisableFragmentGetContextFixModule {
53+
* {@literal @}Provides
54+
* {@literal @}IntoSet
55+
* {@literal @}FragmentGetContextFix.DisableFragmentGetContextFix
56+
* static Boolean provideDisableFragmentGetContextFix() {
57+
* return // true or false depending on some rollout logic for your app
58+
* }
59+
* }
60+
* </code></pre>
61+
*/
62+
public final class FragmentGetContextFix {
63+
64+
/** Qualifier annotation to bind disable the Fragment.getContext() fix at runtime. */
65+
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
66+
@Qualifier
67+
public @interface DisableFragmentGetContextFix {}
68+
69+
public static boolean isFragmentGetContextFixDisabled(Context context) {
70+
// Use a set here instead of an optional to avoid the Guava dependency
71+
Set<Boolean> flagSet = EntryPointAccessors.fromApplication(
72+
context, FragmentGetContextFixEntryPoint.class).getDisableFragmentGetContextFix();
73+
74+
// TODO(b/199927963): Consider adding a plugin to check this at compile time
75+
Preconditions.checkState(flagSet.size() <= 1,
76+
"Cannot bind the flag @DisableFragmentGetContextFix more than once.");
77+
78+
if (flagSet.isEmpty()) {
79+
return false;
80+
} else {
81+
return flagSet.iterator().next();
82+
}
83+
}
84+
85+
/** Entry point for getting the flag. */
86+
@EntryPoint
87+
@InstallIn(SingletonComponent.class)
88+
public interface FragmentGetContextFixEntryPoint {
89+
@DisableFragmentGetContextFix Set<Boolean> getDisableFragmentGetContextFix();
90+
}
91+
92+
/** Declare the empty flag set. */
93+
@Module
94+
@InstallIn(SingletonComponent.class)
95+
abstract static class FragmentGetContextFixModule {
96+
@Multibinds
97+
@DisableFragmentGetContextFix
98+
abstract Set<Boolean> disableFragmentGetContextFix();
99+
}
100+
101+
private FragmentGetContextFix() {
102+
}
103+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (C) 2021 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+
/**
18+
* Runtime flags to control Hilt behavior for rollout of changes. These flags are usually meant to
19+
* be temporary and so defaults may change with releases and then these flags may eventually be
20+
* removed, just like compiler options with similar purposes.
21+
*
22+
* @see <a href="https://dagger.dev/hilt">Hilt Developer Docs</a>
23+
*/
24+
@ParametersAreNonnullByDefault
25+
package dagger.hilt.android.flags;
26+
27+
import javax.annotation.ParametersAreNonnullByDefault;

java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ interface HiltExtension {
5353
/**
5454
* If set to `true`, Hilt will disable cross compilation root validation.
5555
*
56-
* See [documentation](https://dagger.dev/hilt/compiler-options#disable-cross-compilation-root-validation)
56+
* See [documentation](https://dagger.dev/hilt/flags#disable-cross-compilation-root-validation)
5757
* for more information.
5858
*/
5959
var disableCrossCompilationRootValidation: Boolean

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ public final class AndroidClassNames {
7878
public static final ClassName VIEW_MODEL_COMPONENT =
7979
get("dagger.hilt.android.components", "ViewModelComponent");
8080

81+
public static final ClassName FRAGMENT_GET_CONTEXT_FIX =
82+
get("dagger.hilt.android.flags", "FragmentGetContextFix");
83+
8184
public static final ClassName ACTIVITY_COMPONENT_MANAGER =
8285
get("dagger.hilt.android.internal.managers", "ActivityComponentManager");
8386
public static final ClassName APPLICATION_COMPONENT_MANAGER =

java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.squareup.javapoet.FieldSpec;
2424
import com.squareup.javapoet.JavaFile;
2525
import com.squareup.javapoet.MethodSpec;
26+
import com.squareup.javapoet.TypeName;
2627
import com.squareup.javapoet.TypeSpec;
2728
import com.squareup.javapoet.TypeVariableName;
2829
import dagger.hilt.android.processor.internal.AndroidClassNames;
@@ -39,6 +40,11 @@ public final class FragmentGenerator {
3940
.addModifiers(Modifier.PRIVATE)
4041
.build();
4142

43+
private static final FieldSpec DISABLE_GET_CONTEXT_FIX_FIELD =
44+
FieldSpec.builder(TypeName.BOOLEAN, "disableGetContextFix")
45+
.addModifiers(Modifier.PRIVATE)
46+
.build();
47+
4248
private final ProcessingEnvironment env;
4349
private final AndroidEntryPointMetadata metadata;
4450
private final ClassName generatedClassName;
@@ -74,6 +80,10 @@ TypeSpec createTypeSpec() {
7480
.addMethod(getContextMethod())
7581
.addMethod(inflatorMethod());
7682

83+
if (useFragmentGetContextFix(env)) {
84+
builder.addField(DISABLE_GET_CONTEXT_FIX_FIELD);
85+
}
86+
7787
Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
7888
Processors.addGeneratedAnnotation(builder, env, getClass());
7989
Generators.copyLintAnnotations(metadata.element(), builder);
@@ -158,10 +168,12 @@ private static MethodSpec onAttachActivityMethod() {
158168
// // Fragment's because we are getting it from base context instead of cloning from super
159169
// // Fragment's LayoutInflater.
160170
// componentContext = FragmentComponentManager.createContextWrapper(super.getContext(), this);
171+
// disableGetContextFix = FragmentGetContextFix.isFragmentGetContextFixDisabled(
172+
// super.getContext());
161173
// }
162174
// }
163175
private MethodSpec initializeComponentContextMethod() {
164-
return MethodSpec.methodBuilder("initializeComponentContext")
176+
MethodSpec.Builder builder = MethodSpec.methodBuilder("initializeComponentContext")
165177
.addModifiers(Modifier.PRIVATE)
166178
.beginControlFlow("if ($N == null)", COMPONENT_CONTEXT_FIELD)
167179
.addComment(
@@ -171,14 +183,22 @@ private MethodSpec initializeComponentContextMethod() {
171183
.addStatement(
172184
"$N = $T.createContextWrapper(super.getContext(), this)",
173185
COMPONENT_CONTEXT_FIELD,
174-
metadata.componentManager())
186+
metadata.componentManager());
187+
188+
if (useFragmentGetContextFix(env)) {
189+
builder.addStatement("$N = $T.isFragmentGetContextFixDisabled(super.getContext())",
190+
DISABLE_GET_CONTEXT_FIX_FIELD,
191+
AndroidClassNames.FRAGMENT_GET_CONTEXT_FIX);
192+
}
193+
194+
return builder
175195
.endControlFlow()
176196
.build();
177197
}
178198

179199
// @Override
180200
// public Context getContext() {
181-
// if (super.getContext() == null) {
201+
// if (super.getContext() == null && !disableGetContextFix) {
182202
// return null;
183203
// }
184204
// initializeComponentContext();
@@ -191,11 +211,17 @@ private MethodSpec getContextMethod() {
191211
.addModifiers(Modifier.PUBLIC);
192212

193213
if (useFragmentGetContextFix(env)) {
194-
builder.beginControlFlow("if (super.getContext() == null)");
214+
builder
215+
// Note that disableGetContext can only be true if componentContext is set, so if it is
216+
// true we don't need to check whether componentContext is set or not.
217+
.beginControlFlow(
218+
"if (super.getContext() == null && !$N)",
219+
DISABLE_GET_CONTEXT_FIX_FIELD);
195220
} else {
196-
builder.beginControlFlow(
197-
"if (super.getContext() == null && $N == null)",
198-
COMPONENT_CONTEXT_FIELD);
221+
builder
222+
.beginControlFlow(
223+
"if (super.getContext() == null && $N == null)",
224+
COMPONENT_CONTEXT_FIELD);
199225
}
200226

201227
return builder

java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ private void processModule(
134134
|| installInCheckDisabled(element),
135135
element,
136136
"%s is missing an @InstallIn annotation. If this was intentional, see"
137-
+ " https://dagger.dev/hilt/compiler-options#disable-install-in-check for how to disable this"
137+
+ " https://dagger.dev/hilt/flags#disable-install-in-check for how to disable this"
138138
+ " check.",
139139
element);
140140

javatests/dagger/hilt/android/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ android_local_test(
188188
name = "FragmentContextOnAttachTest",
189189
size = "small",
190190
srcs = ["FragmentContextOnAttachTest.java"],
191+
javacopts = [
192+
"-Adagger.hilt.android.useFragmentGetContextFix=true",
193+
],
191194
manifest_values = {
192195
"minSdkVersion": "14",
193196
},
@@ -198,6 +201,7 @@ android_local_test(
198201
"//java/dagger/hilt:install_in",
199202
"//java/dagger/hilt/android:android_entry_point",
200203
"//java/dagger/hilt/android:package_info",
204+
"//java/dagger/hilt/android/flags:fragment_get_context_fix",
201205
"//java/dagger/hilt/android/testing:bind_value",
202206
"//java/dagger/hilt/android/testing:hilt_android_test",
203207
"@google_bazel_common//third_party/java/jsr330_inject",

0 commit comments

Comments
 (0)