Skip to content

Conversation

@raju-muliyashiya
Copy link
Contributor

@raju-muliyashiya raju-muliyashiya commented Nov 27, 2025

fixes Issue #176451

Pre-Review Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] page, which explains my responsibilities.
  • I read and followed the [relevant style guides] and ran [the auto-formatter].
  • I signed the [CLA].
  • The title of the PR starts with the name of the package surrounded by square brackets, e.g. [shared_preferences]
  • I [linked to at least one issue that this PR fixes] in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the [pub versioning philosophy], or I have commented below to indicate which [version change exemption] this PR falls under[^1].
  • I updated CHANGELOG.md to add a description of the change, [following repository CHANGELOG style], or I have commented below to indicate which [CHANGELOG exemption] this PR falls under[^1].
  • I updated/added any relevant documentation (doc comments with ///).
  • I added updated tests to check the change I am making, or I have commented below to indicate which [test exemption] this PR falls under[^1].
  • All existing and new tests are passing.

Description

  • Fixes a crash on hot restart where stale Observer instances from the previous run remained attached to LiveData.
  • The fix ensures callbacks from these untracked observers are safely ignored instead of crashing app.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses a crash on hot restart caused by stale observers. The fix involves adding a check in ObserverProxyApi.java to verify that an observer instance is still tracked in the InstanceManager before its onChanged callback is invoked. This prevents calls to Dart for observers that no longer have a corresponding Dart object. A new test has been added in ObserverTest.java to confirm that callbacks for untracked observers are ignored, and an existing test was updated to be more explicit about testing the valid observer path. The package version and changelog have been updated accordingly. The changes are correct and well-tested. I have one suggestion to refactor the test code to improve maintainability.

Comment on lines 16 to 49
@Test
public void onChanged_makesExpectedCallToDartCallback() {
final ObserverProxyApi mockApi = mock(ObserverProxyApi.class);
when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar());
final TestProxyApiRegistrar registrar = new TestProxyApiRegistrar();
when(mockApi.getPigeonRegistrar()).thenReturn(registrar);

final ObserverProxyApi.ObserverImpl<String> instance =
new ObserverProxyApi.ObserverImpl<>(mockApi);

// Add the observer to the instance manager to simulate normal operation
registrar.getInstanceManager().addDartCreatedInstance(instance, 0);

final String value = "result";
instance.onChanged(value);

verify(mockApi).onChanged(eq(instance), eq(value), any());
}

@Test
public void onChanged_doesNotCallDartCallbackWhenObserverNotInInstanceManager() {
final ObserverProxyApi mockApi = mock(ObserverProxyApi.class);
final TestProxyApiRegistrar registrar = new TestProxyApiRegistrar();
when(mockApi.getPigeonRegistrar()).thenReturn(registrar);

final ObserverProxyApi.ObserverImpl<String> instance =
new ObserverProxyApi.ObserverImpl<>(mockApi);

final String value = "result";
instance.onChanged(value);

// Verify that the Dart callback is NOT invoked for stale observers
verify(mockApi, never()).onChanged(any(), any(), any());
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve maintainability and reduce code duplication, you can extract the common test setup into a method annotated with @org.junit.Before. This will make the tests cleaner and easier to read by separating setup logic from the test logic itself.

  private ObserverProxyApi mockApi;
  private TestProxyApiRegistrar registrar;
  private ObserverProxyApi.ObserverImpl<String> instance;

  @org.junit.Before
  public void setUp() {
    mockApi = mock(ObserverProxyApi.class);
    registrar = new TestProxyApiRegistrar();
    when(mockApi.getPigeonRegistrar()).thenReturn(registrar);
    instance = new ObserverProxyApi.ObserverImpl<>(mockApi);
  }

  @Test
  public void onChanged_makesExpectedCallToDartCallback() {
    // Add the observer to the instance manager to simulate normal operation
    registrar.getInstanceManager().addDartCreatedInstance(instance, 0);

    final String value = "result";
    instance.onChanged(value);

    verify(mockApi).onChanged(eq(instance), eq(value), any());
  }

  @Test
  public void onChanged_doesNotCallDartCallbackWhenObserverNotInInstanceManager() {
    final String value = "result";
    instance.onChanged(value);

    // Verify that the Dart callback is NOT invoked for stale observers
    verify(mockApi, never()).onChanged(any(), any(), any());
  }

Copy link
Contributor

@camsim99 camsim99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this fix! I think this would definitely catch the issue and I'm open to submitting it. My only concern is that I think there's also a way to better address the root of the problem.

The root of the problem seems to be that the plugin does not handle hot restart appropriately. I see that on dispose, we remove the observers, but this won't get called on hot restart. I think we need to handle this case when the camera is re-initialized, likely in one of the createCameraWithSettings or initialize methods. Did you possibly look into this?

@raju-muliyashiya
Copy link
Contributor Author

raju-muliyashiya commented Dec 2, 2025

Some thing like this inside createCameraWithSettings?

if (processCameraProvider != null) {
  await processCameraProvider!.unbindAll();
}

if (liveCameraState != null) {
  await liveCameraState!.removeObservers();
  liveCameraState = null;
}

@stuartmorgan-g stuartmorgan-g added the triage-android Should be looked at in Android triage label Dec 2, 2025
@bparrishMines
Copy link
Contributor

I think a better solution for this would be to update pigeon to do this check. It seems like a common use case where a callback is made after a hot restart. I can create a quick PR for it.

ProxyApiRegistrar.ignoreCallsToDart is called on the native side when the plugin is detached to prevent this crash, but it looks like we need something that also works for hot restarts.

Also there is still a small chance that this crash can still happen with this fix if the Observer makes a call to Dart before it is cleared from the InstanceManager. But this has to do with the fact that plugins are not notified when there is a hot restart.

auto-submit bot pushed a commit that referenced this pull request Dec 4, 2025
…the instance manager calls to Dart (#10552)

In the event of a hot restart, the native `InstanceManagers` are cleared. There is then a chance for a class to make a callback when it is no in the instance manager which leads to a null exception in the codec.

Part of fixing flutter/flutter#176451
Pigeon side fix for #10529

## Pre-Review Checklist

**Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

[^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

p: camera platform-android triage-android Should be looked at in Android triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Camera initialization fails when re-running the app without stopping it

4 participants