Skip to content

Commit

Permalink
Initialize the fragment component context in getContext() as well so …
Browse files Browse the repository at this point in the history
…getContext() can be called before super.onAttach.

RELNOTES=Initialize the fragment component context in getContext() as well so getContext() can be called before super.onAttach. This also makes getContext() correctly return null after being removed.
PiperOrigin-RevId: 373867639
  • Loading branch information
Chang-Eric authored and Dagger Team committed May 14, 2021
1 parent 8235703 commit 8acc433
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@ TypeSpec createTypeSpec() {

// @CallSuper
// @Override
// public void onAttach(Activity activity) {
// super.onAttach(activity);
// public void onAttach(Context context) {
// super.onAttach(context);
// initializeComponentContext();
// inject();
// }
private static MethodSpec onAttachContextMethod() {
return MethodSpec.methodBuilder("onAttach")
Expand All @@ -106,6 +107,8 @@ private static MethodSpec onAttachContextMethod() {
.addParameter(AndroidClassNames.CONTEXT, "context")
.addStatement("super.onAttach(context)")
.addStatement("initializeComponentContext()")
// The inject method will internally check if injected already
.addStatement("inject()")
.build();
}

Expand All @@ -117,6 +120,7 @@ private static MethodSpec onAttachContextMethod() {
// componentContext == null || FragmentComponentManager.findActivity(
// componentContext) == activity, "...");
// initializeComponentContext();
// inject();
// }
private static MethodSpec onAttachActivityMethod() {
return MethodSpec.methodBuilder("onAttach")
Expand All @@ -135,23 +139,22 @@ private static MethodSpec onAttachActivityMethod() {
"onAttach called multiple times with different Context! "
+ "Hilt Fragments should not be retained.")
.addStatement("initializeComponentContext()")
// The inject method will internally check if injected already
.addStatement("inject()")
.build();
}

// private void initializeComponentContext() {
// // Only inject on the first call to onAttach.
// if (componentContext == null) {
// // Note: The LayoutInflater provided by this componentContext may be different from super
// // Fragment's because we are getting it from base context instead of cloning from super
// // Fragment's LayoutInflater.
// componentContext = FragmentComponentManager.createContextWrapper(super.getContext(), this);
// inject();
// }
// }
private MethodSpec initializeComponentContextMethod() {
return MethodSpec.methodBuilder("initializeComponentContext")
.addModifiers(Modifier.PRIVATE)
.addComment("Only inject on the first call to onAttach.")
.beginControlFlow("if ($N == null)", COMPONENT_CONTEXT_FIELD)
.addComment(
"Note: The LayoutInflater provided by this componentContext may be different from"
Expand All @@ -161,20 +164,27 @@ private MethodSpec initializeComponentContextMethod() {
"$N = $T.createContextWrapper(super.getContext(), this)",
COMPONENT_CONTEXT_FIELD,
metadata.componentManager())
.addStatement("inject()")
.endControlFlow()
.build();
}

// @Override
// public Context getContext() {
// if (super.getContext() == null) {
// return null;
// }
// initializeComponentContext();
// return componentContext;
// }
private static MethodSpec getContextMethod() {
return MethodSpec.methodBuilder("getContext")
.returns(AndroidClassNames.CONTEXT)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.beginControlFlow("if (super.getContext() == null)")
.addStatement("return null")
.endControlFlow()
.addStatement("initializeComponentContext()")
.addStatement("return $N", COMPONENT_CONTEXT_FIELD)
.build();
}
Expand Down
21 changes: 21 additions & 0 deletions javatests/dagger/hilt/android/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,27 @@ android_local_test(
],
)

android_local_test(
name = "FragmentContextOnAttachTest",
size = "small",
srcs = ["FragmentContextOnAttachTest.java"],
manifest_values = {
"minSdkVersion": "14",
},
deps = [
"//:android_local_test_exports",
"//:dagger_with_compiler",
"//java/dagger/hilt:entry_point",
"//java/dagger/hilt:install_in",
"//java/dagger/hilt/android:android_entry_point",
"//java/dagger/hilt/android:package_info",
"//java/dagger/hilt/android/testing:bind_value",
"//java/dagger/hilt/android/testing:hilt_android_test",
"@google_bazel_common//third_party/java/jsr330_inject",
"@google_bazel_common//third_party/java/truth",
],
)

android_local_test(
name = "AndroidEntryPointBaseClassTest",
size = "small",
Expand Down
77 changes: 77 additions & 0 deletions javatests/dagger/hilt/android/FragmentContextOnAttachTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2021 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;

import static com.google.common.truth.Truth.assertThat;

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import dagger.hilt.android.testing.HiltAndroidRule;
import dagger.hilt.android.testing.HiltAndroidTest;
import dagger.hilt.android.testing.HiltTestApplication;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;

@HiltAndroidTest
@RunWith(AndroidJUnit4.class)
// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
public final class FragmentContextOnAttachTest {

@Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);

/** Hilt Activity */
@AndroidEntryPoint(FragmentActivity.class)
public static final class TestActivity extends Hilt_FragmentContextOnAttachTest_TestActivity {}

/** Hilt Fragment */
@AndroidEntryPoint(Fragment.class)
public static final class TestFragment extends Hilt_FragmentContextOnAttachTest_TestFragment {
Context onAttachContextContext = null;
Context onAttachActivityContext = null;

@Override
public void onAttach(Context context) {
// Test that getContext() can be called at this point
onAttachContextContext = getContext();
super.onAttach(context);
}

@Override
public void onAttach(Activity activity) {
// Test that getContext() can be called at this point
onAttachActivityContext = getContext();
super.onAttach(activity);
}
}

@Test
public void testGetContextAvailableBeforeSuperOnAttach() throws Exception {
FragmentActivity activity = Robolectric.setupActivity(TestActivity.class);
TestFragment fragment = new TestFragment();
activity.getSupportFragmentManager().beginTransaction().add(fragment, "").commitNow();
assertThat(fragment.onAttachContextContext).isNotNull();
assertThat(fragment.onAttachActivityContext).isNotNull();
}
}

0 comments on commit 8acc433

Please sign in to comment.