docs/ui/android/activity_result_tracker.md
ActivityResultTracker
is a utility within Chromium for Android designed to more robustly handle
results from activities started for a result. It ensures that results are not
lost even if the base Activity is destroyed and recreated by the Android system
while the launched activity is still active. This is useful for flows like
account addition starting an activity from a different app, where Chrome can be
killed by the OS in the background to free memory during the flow.
Implement ActivityResultTracker.ResultListener: Your component that
needs to receive an activity result should implement the
ActivityResultTracker.ResultListener interface. This interface has two
methods:
onActivityResult(ActivityResult result, @Nullable Bundle savedInstanceData):
This is where you handle the result. The savedInstanceData parameter
contains the optional data that was saved before starting the activity.getRestorationKey(): This should return a String that uniquely
identifies the purpose of the activity launch across activity
recreations. If multiple indistinguishable instances of your component
must exist in parallel, they can return the same restoration key - an
arbitrary instance will be chosen to handle the activity result in case
the base activity is recreated before the new activity finishes.class MyComponent implements ActivityResultTracker.ResultListener {
private final String mRestorationKey;
MyComponent(String restorationKey) {
mRestorationKey = restorationKey;
}
@Override
public void onActivityResult(ActivityResult result, @Nullable Bundle savedInstanceData) {
// Handle the result...
}
@Override
public String getRestorationKey() {
return mRestorationKey;
}
}
Get an ActivityResultTracker Instance: Get the tracker instance inside
the base Activity and pass it to your component.
ActivityResultTracker tracker = getActivityResultTracker();
Register the Listener: Before you start the activity, register your activity result listener with the tracker.
MyComponent myComponent = new MyComponent("my_unique_restoration_key");
tracker.register(myComponent);
Start the Activity Using the Tracker:
tracker.startActivity(myComponent, intent, savedInstanceData);
The savedInstanceData is optional and can be used to save state that needs
to be restored after the base activity's recreation.
Re-registration is Key: Components MUST call tracker.register() with
the same restoration key in case the base activity is recreated and ensures
it happens automatically. (e.g. it should not be triggered by an user
action) If a pending result for your listener's restoration key already
exists (e.g., from before the activity was recreated), onActivityResult
will be called immediately.
If multiple instances of a component were registered using the same restoration key and a result matching the restoration key is received:
Unregister the listener when closing the UI owning it:
tracker.unregister(myComponent);
Chrome mainly relies on
IntentRequestTracker
owned by
WindowAndroid
for activity result handling. This system is straightforward to use, but has one
limitation: if the base Activity hosting the requesting component is killed by
the system (e.g., due to memory pressure) and then later recreated when the user
navigated back, the callback to handle the activity result is lost. This means
the result from the external activity is effectively discarded, breaking the
user flow. This was observed to affect a noticeable percentage (~1% in 2024) of
sign-in attempts, for example.
The standard Android
ActivityResultRegistry
API does support handling results across Activity recreation. However, its
limitation is that callbacks must be registered unconditionally during the
Activity/Fragment initialization or onCreate to catch the in-flight activity
results after recreation. This model doesn't fit well with Chrome's
architecture, where many features are encapsulated in components (like
Coordinators) that might be created on demand, much later than the Activity's
onCreate. These components, often deeply nested, are the ones that actually
need to initiate an activity and handle its result.
ActivityResultTracker offers to bridge this gap. It's hosted by the base
Activity and uses the ActivityResultRegistry from the Android SDK to ensure
result reception across Activity death and recreation. The key features are:
onSaveInstanceState bundle.ActivityResultRegistry during onCreate. This ensures the tracker catches
any result sent back.ActivityResultTracker holds the
ActivityResult and the corresponding restoration key in a temporary cache.ActivityResultTracker.register() with its
restoration key, the tracker checks the cache. If a result is pending for
that key, it's delivered immediately to the newly provided callback
instance.This design allows components to be created at any time and still reliably receive activity results, even if the main Activity is destroyed in the background.
Note that the cached results are not persisted with the recreation, so the components needs to ensure early registration of the result callback upon recreation, to prevent the result from being lost during a second activity recreation.
The implementation of ActivityResultTracker (e.g.,
ActivityResultTrackerImpl) requires hooks into the Activity lifecycle:
onSaveInstanceState, onRestoreInstanceState, and onDestroy. These are
handled by the base Activity class (currently ChromeBaseAppCompatActivity).