Skip to content

[path_provider_android] Changes internal implementation to use JNI #9770

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

bparrishMines
Copy link
Contributor

Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.

List which issues are fixed by this PR. You must list at least one issue.

Pre-Review Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. 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.

Footnotes

  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. 2 3

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 refactors the Android implementation of path_provider to use jnigen and JNI instead of Pigeon for platform communication. This is a significant architectural change. While the implementation looks mostly correct, there are several potential memory leaks due to unreleased JNI objects. I've added specific comments on how to fix them.

A major concern is the removal of all existing tests without adding new ones to cover the new JNI-based implementation. The repository's style guide states that "Code should be tested"[^1]. Given the low-level nature of JNI interop, having a solid test suite is critical to ensure correctness and prevent regressions. Please add tests for the new implementation.

Comment on lines 61 to 66
final JArray<File?>? files = _applicationContext.getExternalCacheDirs();
if (files != null) {
return _toStringList(files);
}

return null;

Choose a reason for hiding this comment

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

high

The JArray<File?> object returned by getExternalCacheDirs() is a JNI object that needs to be released to avoid a memory leak. The current implementation does not release it.

    final JArray<File?>? files = _applicationContext.getExternalCacheDirs();
    if (files == null) {
      return null;
    }
    try {
      return _toStringList(files);
    } finally {
      files.release();
    }

Comment on lines 73 to 80
final JArray<File?>? files = _applicationContext.getExternalFilesDirs(
type != null ? _toNativeStorageDirectory(type) : null,
);
if (files != null) {
return _toStringList(files);
}

return null;

Choose a reason for hiding this comment

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

high

The JArray<File?> object returned by getExternalFilesDirs() is a JNI object that needs to be released to avoid a memory leak. The current implementation does not release it.

    final JArray<File?>? files = _applicationContext.getExternalFilesDirs(
      type != null ? _toNativeStorageDirectory(type) : null,
    );
    if (files == null) {
      return null;
    }
    try {
      return _toStringList(files);
    } finally {
      files.release();
    }

Comment on lines +122 to +134
final Iterator<File?> filesIterator = files.iterator;
while (filesIterator.moveNext()) {
final File? file = filesIterator.current;
if (file != null) {
final String? path =
file.getAbsolutePath()?.toDartString(releaseOriginal: true);
if (path != null) {
paths.add(path);
}

file.release();
}
}

Choose a reason for hiding this comment

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

high

There are two potential memory leaks here:

  1. The iterator returned by files.iterator is a JIterator, which is a JNI object that must be released.
  2. If file.getAbsolutePath() or toDartString() throws an exception, file.release() will not be called.

Both can be fixed by using try...finally blocks to ensure release() is always called.

  final Iterator<File?> filesIterator = files.iterator;
  try {
    while (filesIterator.moveNext()) {
      final File? file = filesIterator.current;
      if (file != null) {
        try {
          final String? path =
              file.getAbsolutePath()?.toDartString(releaseOriginal: true);
          if (path != null) {
            paths.add(path);
          }
        } finally {
          file.release();
        }
      }
    }
  } finally {
    (filesIterator as JObject).release();
  }

Comment on lines +40 to +43
final File? file = _applicationContext.getCacheDir();
final String? path = file?.getPath()?.toDartString(releaseOriginal: true);
file?.release();
return path;

Choose a reason for hiding this comment

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

medium

To ensure that the File object is always released, even if getPath() or toDartString() throws an exception, it's safer to use a try...finally block.

    final File? file = _applicationContext.getCacheDir();
    if (file == null) {
      return null;
    }
    try {
      return file.getPath()?.toDartString(releaseOriginal: true);
    } finally {
      file.release();
    }

Comment on lines +48 to +54
final File? dir = _applicationContext.getExternalFilesDir(null);
if (dir != null) {
final String? path =
dir.getAbsolutePath()?.toDartString(releaseOriginal: true);
dir.release();
return path;
}

Choose a reason for hiding this comment

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

medium

To ensure that the File object (dir) is always released, even if an exception occurs, it's best practice to use a try...finally block.

    final File? dir = _applicationContext.getExternalFilesDir(null);
    if (dir == null) {
      return null;
    }
    try {
      return dir.getAbsolutePath()?.toDartString(releaseOriginal: true);
    } finally {
      dir.release();
    }

@bparrishMines bparrishMines changed the title Pp jnigen [path_provider_android] Changes internal implementation to use JNI Aug 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant