[go: nahoru, domu]

Ensure ActivityResultLauncher can be launched in onCreate

When attempting to launch an ActivityResultLauncher in onCreate() of a
Fragment, it fails since the the Fragment's Lifecycle does not move to
CREATED until after the onCreate() callback in executed. We should
register the callback as soon as the fragment is attached since that is
the point at which we have all of the needed information.

This change also adds tests to verify that ComponentActivity does not
suffer from the same issue.

Test: Added Activity and Fragment tests
Bug: 161464278
RelNote: "You can now call launch() on an ActivityResultLauncher in the
onCreate() lifecycle method of a fragment."

Change-Id: Ifd98f308fdfbd7c9ce3ae93d032b140d6f3ec35e
diff --git a/activity/activity/src/androidTest/AndroidManifest.xml b/activity/activity/src/androidTest/AndroidManifest.xml
index 0b7800a..13d2e2e 100644
--- a/activity/activity/src/androidTest/AndroidManifest.xml
+++ b/activity/activity/src/androidTest/AndroidManifest.xml
@@ -28,6 +28,7 @@
         <activity android:name="androidx.activity.ContentViewActivity"/>
         <activity android:name="androidx.activity.EmptyContentActivity" />
         <activity android:name="androidx.activity.AutoRestarterActivity"/>
+        <activity android:name="androidx.activity.ResultComponentActivity"/>
     </application>
 
 </manifest>
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityResultTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityResultTest.kt
new file mode 100644
index 0000000..fd86230
--- /dev/null
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityResultTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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 androidx.activity
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.result.ActivityResultRegistry
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.core.app.ActivityOptionsCompat
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class ComponentActivityResultTest {
+    @Test
+    fun launchInOnCreate() {
+        ActivityScenario.launch(ResultComponentActivity::class.java).use { scenario ->
+            val launchCount = scenario.withActivity { this.registryLaunchCount }
+            assertThat(launchCount).isEqualTo(1)
+        }
+    }
+}
+
+class ResultComponentActivity : ComponentActivity() {
+    var registryLaunchCount = 0
+
+    val registry = object : ActivityResultRegistry() {
+
+        override fun <I : Any?, O : Any?> onLaunch(
+            requestCode: Int,
+            contract: ActivityResultContract<I, O>,
+            input: I,
+            options: ActivityOptionsCompat?
+        ) {
+            registryLaunchCount++
+        }
+    }
+
+    val launcher = registerForActivityResult(StartActivityForResult(), registry) { }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        launcher.launch(Intent())
+    }
+}
diff --git a/fragment/fragment/api/1.3.0-alpha07.txt b/fragment/fragment/api/1.3.0-alpha07.txt
index 3cc43ad..6fd13dc 100644
--- a/fragment/fragment/api/1.3.0-alpha07.txt
+++ b/fragment/fragment/api/1.3.0-alpha07.txt
@@ -115,8 +115,8 @@
     method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
     method public void postponeEnterTransition();
     method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
     method public void registerForContextMenu(android.view.View);
     method @Deprecated public final void requestPermissions(String![], int);
     method public final androidx.fragment.app.FragmentActivity requireActivity();
diff --git a/fragment/fragment/api/current.txt b/fragment/fragment/api/current.txt
index 3cc43ad..6fd13dc 100644
--- a/fragment/fragment/api/current.txt
+++ b/fragment/fragment/api/current.txt
@@ -115,8 +115,8 @@
     method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
     method public void postponeEnterTransition();
     method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
     method public void registerForContextMenu(android.view.View);
     method @Deprecated public final void requestPermissions(String![], int);
     method public final androidx.fragment.app.FragmentActivity requireActivity();
diff --git a/fragment/fragment/api/public_plus_experimental_1.3.0-alpha07.txt b/fragment/fragment/api/public_plus_experimental_1.3.0-alpha07.txt
index 9b244bf..7512f13 100644
--- a/fragment/fragment/api/public_plus_experimental_1.3.0-alpha07.txt
+++ b/fragment/fragment/api/public_plus_experimental_1.3.0-alpha07.txt
@@ -115,8 +115,8 @@
     method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
     method public void postponeEnterTransition();
     method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
     method public void registerForContextMenu(android.view.View);
     method @Deprecated public final void requestPermissions(String![], int);
     method public final androidx.fragment.app.FragmentActivity requireActivity();
diff --git a/fragment/fragment/api/public_plus_experimental_current.txt b/fragment/fragment/api/public_plus_experimental_current.txt
index 9b244bf..7512f13 100644
--- a/fragment/fragment/api/public_plus_experimental_current.txt
+++ b/fragment/fragment/api/public_plus_experimental_current.txt
@@ -115,8 +115,8 @@
     method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
     method public void postponeEnterTransition();
     method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
     method public void registerForContextMenu(android.view.View);
     method @Deprecated public final void requestPermissions(String![], int);
     method public final androidx.fragment.app.FragmentActivity requireActivity();
diff --git a/fragment/fragment/api/restricted_1.3.0-alpha07.txt b/fragment/fragment/api/restricted_1.3.0-alpha07.txt
index 0639765..a2689d8 100644
--- a/fragment/fragment/api/restricted_1.3.0-alpha07.txt
+++ b/fragment/fragment/api/restricted_1.3.0-alpha07.txt
@@ -119,8 +119,8 @@
     method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
     method public void postponeEnterTransition();
     method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
     method public void registerForContextMenu(android.view.View);
     method @Deprecated public final void requestPermissions(String![], int);
     method public final androidx.fragment.app.FragmentActivity requireActivity();
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index 0639765..a2689d8 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -119,8 +119,8 @@
     method @CallSuper @MainThread public void onViewStateRestored(android.os.Bundle?);
     method public void postponeEnterTransition();
     method public final void postponeEnterTransition(long, java.util.concurrent.TimeUnit);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
-    method public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultCallback<O!>);
+    method @MainThread public final <I, O> androidx.activity.result.ActivityResultLauncher<I!> registerForActivityResult(androidx.activity.result.contract.ActivityResultContract<I!,O!>, androidx.activity.result.ActivityResultRegistry, androidx.activity.result.ActivityResultCallback<O!>);
     method public void registerForContextMenu(android.view.View);
     method @Deprecated public final void requestPermissions(String![], int);
     method public final androidx.fragment.app.FragmentActivity requireActivity();
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentActivityResultTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentActivityResultTest.kt
new file mode 100644
index 0000000..3970fc2
--- /dev/null
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentActivityResultTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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 androidx.fragment.app
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.ActivityResultRegistry
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.core.app.ActivityOptionsCompat
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for Fragment registerForActivityResult
+ */
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class FragmentActivityResultTest {
+
+    @Test
+    fun registerActivityResultInOnAttach() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            withActivity {
+                val fragment = RegisterInOnAttachFragment()
+
+                supportFragmentManager.beginTransaction()
+                    .add(androidx.fragment.test.R.id.content, fragment)
+                    .commitNow()
+
+                assertThat(fragment.launchedCounter).isEqualTo(1)
+            }
+        }
+    }
+
+    @Test
+    fun registerActivityResultInOnCreate() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            withActivity {
+                val fragment = RegisterInOnCreateFragment()
+
+                supportFragmentManager.beginTransaction()
+                    .add(androidx.fragment.test.R.id.content, fragment)
+                    .commitNow()
+
+                assertThat(fragment.launchedCounter).isEqualTo(1)
+            }
+        }
+    }
+
+    @Test
+    fun launchActivityResultInOnCreate() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            withActivity {
+                val fragment = ActivityResultFragment()
+
+                supportFragmentManager.beginTransaction()
+                    .add(androidx.fragment.test.R.id.content, fragment)
+                    .commitNow()
+            }
+        }
+    }
+
+    @Test
+    fun launchTwoActivityResult() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            withActivity {
+                val fragment = DoubleActivityResultFragment()
+
+                supportFragmentManager.beginTransaction()
+                    .add(androidx.fragment.test.R.id.content, fragment)
+                    .commitNow()
+
+                assertThat(fragment.launchedCounter).isEqualTo(2)
+            }
+        }
+    }
+}
+
+class ActivityResultFragment : Fragment() {
+    private val registry = object : ActivityResultRegistry() {
+        override fun <I : Any?, O : Any?> onLaunch(
+            requestCode: Int,
+            contract: ActivityResultContract<I, O>,
+            input: I,
+            options: ActivityOptionsCompat?
+        ) { }
+    }
+
+    val launcher = registerForActivityResult(
+        StartActivityForResult(),
+        registry
+    ) { }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        launcher.launch(Intent())
+    }
+}
+
+class DoubleActivityResultFragment : Fragment() {
+    private val registry = object : ActivityResultRegistry() {
+        override fun <I : Any?, O : Any?> onLaunch(
+            requestCode: Int,
+            contract: ActivityResultContract<I, O>,
+            input: I,
+            options: ActivityOptionsCompat?
+        ) { dispatchResult(requestCode, Activity.RESULT_OK, Intent()) }
+    }
+
+    var launchedCounter = 0
+
+    val launcher1 = registerForActivityResult(
+        StartActivityForResult(),
+        registry
+    ) { launchedCounter++ }
+
+    val launcher2 = registerForActivityResult(
+        StartActivityForResult(),
+        registry
+    ) { launchedCounter++ }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        launcher1.launch(Intent())
+        launcher2.launch(Intent())
+    }
+}
+
+class RegisterInOnAttachFragment : Fragment() {
+    private val registry = object : ActivityResultRegistry() {
+        override fun <I : Any?, O : Any?> onLaunch(
+            requestCode: Int,
+            contract: ActivityResultContract<I, O>,
+            input: I,
+            options: ActivityOptionsCompat?
+        ) { dispatchResult(requestCode, Activity.RESULT_OK, Intent()) }
+    }
+
+    lateinit var launcher: ActivityResultLauncher<Intent>
+
+    var launchedCounter = 0
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        launcher = registerForActivityResult(
+            StartActivityForResult(),
+            registry
+        ) { launchedCounter++ }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        launcher.launch(Intent())
+    }
+}
+
+class RegisterInOnCreateFragment : Fragment() {
+    private val registry = object : ActivityResultRegistry() {
+        override fun <I : Any?, O : Any?> onLaunch(
+            requestCode: Int,
+            contract: ActivityResultContract<I, O>,
+            input: I,
+            options: ActivityOptionsCompat?
+        ) { dispatchResult(requestCode, Activity.RESULT_OK, Intent()) }
+    }
+
+    lateinit var launcher: ActivityResultLauncher<Intent>
+
+    var launchedCounter = 0
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        launcher = registerForActivityResult(
+            StartActivityForResult(),
+            registry
+        ) { launchedCounter++ }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        launcher.launch(Intent())
+    }
+}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index e92bede..82af0f5 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -301,6 +301,12 @@
 
     private final AtomicInteger mNextLocalRequestCode = new AtomicInteger();
 
+    private final ArrayList<OnPreAttachedListener> mOnPreAttachedListeners = new ArrayList<>();
+
+    private abstract static class OnPreAttachedListener {
+        abstract void onPreAttached();
+    }
+
     /**
      * {@inheritDoc}
      * <p>
@@ -2851,6 +2857,10 @@
     }
 
     void performAttach() {
+        for (OnPreAttachedListener listener: mOnPreAttachedListeners) {
+            listener.onPreAttached();
+        }
+        mOnPreAttachedListeners.clear();
         mChildFragmentManager.attachController(mHost, createFragmentContainer(), this);
         mState = ATTACHED;
         mCalled = false;
@@ -3314,6 +3324,7 @@
      * {@link ActivityResultRegistry} of the host will be used. Otherwise, this will use the
      * registry of the Fragment's Activity.
      */
+    @MainThread
     @NonNull
     @Override
     public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@@ -3330,6 +3341,7 @@
         }, callback);
     }
 
+    @MainThread
     @NonNull
     @Override
     public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@@ -3350,20 +3362,17 @@
             @NonNull final Function<Void, ActivityResultRegistry> registryProvider,
             @NonNull final ActivityResultCallback<O> callback) {
         final AtomicReference<ActivityResultLauncher<I>> ref = new AtomicReference<>();
-
         // We can't call generateActivityResultKey during initialization of the Fragment
         // since we need to wait for the mWho to be restored from saved instance state
-        // so we'll wait until the Lifecycle is CREATED to actually generate the key
-        // and register.
-        getLifecycle().addObserver(new LifecycleEventObserver() {
+        // so we'll wait until we have all the information needed to register  to actually
+        // generate the key and register.
+
+        registerOnPreAttachListener(new OnPreAttachedListener() {
             @Override
-            public void onStateChanged(@NonNull LifecycleOwner lifecycleOwner,
-                    @NonNull Lifecycle.Event event) {
-                if (Lifecycle.Event.ON_CREATE.equals(event)) {
-                    final String key = generateActivityResultKey();
-                    ActivityResultRegistry registry = registryProvider.apply(null);
-                    ref.set(registry.register(key, Fragment.this, contract, callback));
-                }
+            void onPreAttached() {
+                final String key = generateActivityResultKey();
+                ActivityResultRegistry registry = registryProvider.apply(null);
+                ref.set(registry.register(key, Fragment.this, contract, callback));
             }
         });
 
@@ -3394,6 +3403,16 @@
         };
     }
 
+    private void registerOnPreAttachListener(@NonNull final OnPreAttachedListener callback) {
+        //If we are already attached, we can register immediately
+        if (mState >= ATTACHED) {
+            callback.onPreAttached();
+        } else {
+            // else we need to wait until we are attached
+            mOnPreAttachedListeners.add(callback);
+        }
+    }
+
     @NonNull
     String generateActivityResultKey() {
         return "fragment_" + mWho + "_rq#" + mNextLocalRequestCode.getAndIncrement();