Skip to content

[Bug report] Android change notifications fail for external_primary MediaStore volume #1443

Description

@JuoCode

Version

3.10.0

Platforms

Android

Device Model

Android Emulator, Pixel 9 Pro (API 36); reproducible on any AVD

flutter info

[✓] Flutter (Channel stable, 3.44.4, on macOS 26.5.2 darwin-arm64)
    • Dart version 3.12.2
[✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0)
    • Emulator version 36.2.12.0

How to reproduce?

  1. Call PhotoManager.addChangeCallback(cb) and PhotoManager.startChangeNotify().
  2. Drag an image from the host desktop into a running Android Emulator window (or, more generally, cause any insert that MediaStore announces on the external_primary volume URI only).
  3. The image is indexed by MediaStore and appears in getAssetPathList results after a manual refresh — but cb is never invoked.

Root cause

PhotoManagerNotifyChannel.kt registers its three ContentObservers only on the legacy external volume URIs:

private val imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI  // content://media/external/images/media
private val videoUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
private val audioUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI

Since API 29, MediaStore also exposes collections under the external_primary volume (MediaStore.VOLUME_EXTERNAL_PRIMARY, i.e. content://media/external_primary/...). Some insert paths — emulator drag-and-drop being the easiest to reproduce — post their change notification only on the external_primary URI. notifyForDescendants = true does not help, because content://media/external_primary/images/media is a sibling of content://media/external/images/media, not a descendant.

Note this is distinct from #1117 / #1301 (limited-permission re-selection, fixed by #1357 via an explicit notify): here the observer registration itself never covers the volume, so no permission flow is involved.

Suggested fix

On API 29+, additionally register the same observers on the external_primary collections:

MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

Most real-device writes notify both volumes, so this will produce duplicate callbacks for a single insert; the plugin may want to debounce/coalesce internally (cf. #1219), or document that callers should. We currently work around the gap in our app with a host-side ContentObserver on external_primary bridged over an EventChannel, merged with the plugin callback behind a debounce — happy to turn the suggested fix into a PR if you're open to it.

Example code (optional)

PhotoManager.addChangeCallback((call) => debugPrint('change: $call'));
await PhotoManager.startChangeNotify();
// Drag a JPEG from the desktop into the emulator window: nothing is printed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions