diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java index 81b2b615642..ed22fdd3c67 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java @@ -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") @@ -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(); } @@ -117,6 +120,7 @@ private static MethodSpec onAttachContextMethod() { // componentContext == null || FragmentComponentManager.findActivity( // componentContext) == activity, "..."); // initializeComponentContext(); + // inject(); // } private static MethodSpec onAttachActivityMethod() { return MethodSpec.methodBuilder("onAttach") @@ -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" @@ -161,13 +164,16 @@ 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() { @@ -175,6 +181,10 @@ private static MethodSpec getContextMethod() { .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(); } diff --git a/javatests/dagger/hilt/android/BUILD b/javatests/dagger/hilt/android/BUILD index 1f9bd0809cb..73abc007994 100644 --- a/javatests/dagger/hilt/android/BUILD +++ b/javatests/dagger/hilt/android/BUILD @@ -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", diff --git a/javatests/dagger/hilt/android/FragmentContextOnAttachTest.java b/javatests/dagger/hilt/android/FragmentContextOnAttachTest.java new file mode 100644 index 00000000000..48cc6f236eb --- /dev/null +++ b/javatests/dagger/hilt/android/FragmentContextOnAttachTest.java @@ -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(); + } +}