Skip to content

fix(android): stop mangling the file name for extensions Android doesn't recognize in saveFile - #2201

Merged
vicajilau merged 2 commits into
mainfrom
fix/android-savefile-unknown-extension-mimetype
Sep 10, 2026
Merged

vicajilau merged 2 commits into
mainfrom
fix/android-savefile-unknown-extension-mimetype

Conversation

@vicajilau

@vicajilau vicajilau commented Sep 9, 2026 •

Copy link
Copy Markdown
Owner

Fixes #2199.

The bug

Saving a file with an extension unregistered in the device's MimeTypeMap (e.g. .gpx, a niche XML based format) produces a mangled name. The report's example: saving trace.gpx produced trace.gpx.xml.gpx.

Root cause, traced through the actual save flow

  1. getMimeTypeForBytes (FileUtils.kt) doesn't find gpx in the device's MimeTypeMap, so it falls back to sniffing the file's byte content. A GPX file is valid XML, so the sniff correctly detects text/xml, a real but wrong answer for what the caller asked for.
  2. That mime type is set on the ACTION_CREATE_DOCUMENT intent alongside the suggested title trace.gpx. The system's document picker honors the declared type and "corrects" the suggested name by appending its own default extension for text/xml, .xml, producing the actual created document trace.gpx.xml.
  3. maybeRenameGenericMimeDuplicate runs afterward to append the extension when the picker didn't add one (the case it was written for, generic mime types like application/octet-stream). It only checks whether the current name already ends with the extension we asked for, gpx, never whether it ends with a different one. trace.gpx.xml doesn't end in .gpx, so the whole string is treated as the base name and .gpx gets appended on top, producing trace.gpx.xml.gpx.

Not specific to .gpx, any extension unknown to the device whose content happens to sniff as a recognizable generic format (XML, in this case, likely others too) can hit the same chain.

The fix

  • getMimeTypeForBytes: when the extension is present but unknown to MimeTypeMap, use a wildcard mime type (*/*) instead of falling through to content sniffing. A wildcard has no default extension of its own, so the picker has nothing to "correct" the name with.
  • extractBaseNameAndSuffix (used by maybeRenameGenericMimeDuplicate): added a case that recognizes our extension immediately followed by exactly one more extension, e.g. trace.gpx.xml, and strips both, recovering the true base name (trace) instead of only handling the case where no extension is present at all.

Verified the new regex's matching behavior directly (a Kotlin Regex and Python's re share the same backtracking semantics for this pattern) against the exact reported name plus a few edge cases (a base name containing a literal dot, the existing Android collision suffix style, a name that already has the right extension), all resolve to the expected base name.

What I could not verify myself

There's no existing Kotlin unit test setup in this module (only Dart tests), and reproducing this needs an actual Android device/emulator plus a real "create document" system picker interaction, which isn't something I can drive from here. @nicolaspernoud if you're able to test this branch against your actual repro (saving with fileName: 'trace.gpx', allowedExtensions: ["gpx"]), that would be the most reliable confirmation before merging. You can point at it with:

dependency_overrides:
  android_file_picker:
    git:
      url: https://github.com/vicajilau/flutter_file_picker.git
      ref: fix/android-savefile-unknown-extension-mimetype
      path: packages/file_picker_android

Test plan

  • flutter analyze clean
  • Bumped android_file_picker to 1.1.1 with a changelog entry
  • Confirmation against the real repro (see above)

…n't recognize in saveFile

Saving with an extension unregistered in the device's MimeTypeMap, e.g.
trace.gpx, produced trace.gpx.xml.gpx.

getMimeTypeForBytes fell back to sniffing the file's byte content when
the extension was unknown, and a GPX file is valid XML, so it detected
text/xml. The system's create document picker honored that declared
type and appended its own default extension for it to the suggested
name, turning trace.gpx into trace.gpx.xml. A later cleanup step meant
to append a missing extension then appended the original one on top of
that instead of replacing the mismatched one, since it only checked
whether the current name already ended with the extension we asked
for, never that it ended with a different one.

Extensions unknown to the device now get a wildcard mime type instead
of a sniffed one, which has no default extension of its own for the
picker to enforce. The cleanup step now also recognizes our extension
followed by exactly one more as a system appended mismatch and strips
both, recovering the true base name, instead of only handling the
case where no extension is present at all.

Fixes #2199

@navaronbracke navaronbracke left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM with nit

Comment thread packages/file_picker_android/CHANGELOG.md Outdated
@nicolaspernoud

Copy link
Copy Markdown

Hello,
Thanks for the quick fix.
I confirm that it solves the problem.
Best regards.

Co-authored-by: Navaron Bracke <brackenavaron@gmail.com>
@vicajilau
vicajilau enabled auto-merge September 10, 2026 04:21
@vicajilau
vicajilau merged commit 5ef6d5b into main Sep 10, 2026
17 of 18 checks passed
@vicajilau
vicajilau deleted the fix/android-savefile-unknown-extension-mimetype branch September 10, 2026 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Saving file as gpx give .gpx.xml.gpx.xml extension

3 participants