Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.6.25+1

* Fixes crash on hot restart caused by stale observers.

## 0.6.25

* Adds support for `MediaSettings.fps` for camera preview, image streaming, and video recording.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ public void onChanged(T t) {
new ProxyApiRegistrar.FlutterMethodRunnable() {
@Override
public void run() {
// Check if this observer instance is still in the instance manager.
// During hot restart, old observers may remain attached to LiveData but
// are no longer tracked in the instance manager.
if (!api.getPigeonRegistrar().getInstanceManager().containsInstance(ObserverImpl.this)) {
android.util.Log.w(
"ObserverProxyApi",
"Ignoring onChanged callback for Observer not in InstanceManager (likely from previous hot restart): "
+ ObserverImpl.this);
return;
}

api.onChanged(
ObserverImpl.this,
t,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand All @@ -16,13 +17,34 @@ public class ObserverTest {
@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());
}
Comment on lines 16 to 48

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());
  }

}
2 changes: 1 addition & 1 deletion packages/camera/camera_android_camerax/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_android_camerax
description: Android implementation of the camera plugin using the CameraX library.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.6.25
version: 0.6.25+1

environment:
sdk: ^3.9.0
Expand Down