Skip to content

feat: For Android add support for downloading files with blob or base64 URLs #50 #51

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

Merged
merged 3 commits into from
Jun 17, 2025

Conversation

Tyschenko
Copy link

For iOS PR is #50

For the first time this issue was raised in 2021 MetaMask/metamask-mobile#2195

On both platforms downloading of URLs with blob: prefix (e.g. blob:https://google.com/6c9102b5) or with data: prefix (e.g. data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAAE) is unsupported.

To test I've used two websites:

https://mdigi.tools/solid-color-image-generator/ Generates data: URL
https://eligrey.com/demos/FileSaver.js/ Generates blob: URL

When user clicks such link, nothing happens. Below you can find two videos, one for blob: URL and one for data: URL.
https://github.com/user-attachments/assets/d8dfe6f2-a47c-4275-b334-783ce229dee0
https://github.com/user-attachments/assets/0e15a9e5-ab72-4e08-8159-a686dbd5076e

In this Pull request I updated Android WebView integration to handle both URLs. Below you can find videos to prove it. There are two different implementations for Android 21-29 and 29+ because on older versions of Android we need to request permission to write file to the storage.
Unfortunately, because we have file in base64, we cannot use native Download Manager (it can download only URLs) which will show a notification. I show two Toast messages: one for downloading started and one for the outcome.

Android 8 with permission request:
https://github.com/user-attachments/assets/84d6e883-de43-4fc2-8746-f7d22f890573
https://github.com/user-attachments/assets/68bc0f23-2d36-4a5b-96cc-818a06fd8962

Android 15:
https://github.com/user-attachments/assets/c0d24444-bf2b-4377-92ce-875271bc8004
https://github.com/user-attachments/assets/f5ba24b0-5c2a-446d-874c-2a545b3cbe0e

@Tyschenko Tyschenko changed the title For Android add support for downloading files with blob or base64 URLs #50 feat: For Android add support for downloading files with blob or base64 URLs #50 May 22, 2025
@@ -25,6 +25,9 @@ import com.facebook.react.bridge.ReadableMap
import com.facebook.react.common.MapBuilder
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.uimanager.ThemedReactContext
import com.reactnativecommunity.webview.extension.file.Base64FileDownloader
Copy link

@sethkfman sethkfman May 23, 2025

Choose a reason for hiding this comment

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

Great job moving this out into a separate file.

/**
* This method is called from PermissionListener after WRITE_EXTERNAL_STORAGE permission is granted
*/
fun downloadBase64FileWithoutPermissionCheckAndDialog(

Choose a reason for hiding this comment

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

Any thoughts on adding unit testing to these public functions. If the context is not too hard to mock it may be nice to add a bit of coverage.

Copy link
Author

Choose a reason for hiding this comment

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

Two problems with unit tests:

  1. In this method there are interactions with system components (such as Context for dialog or file system). This part cannot be easily mocked, only with Robolectric library which might be inconsistent
  2. This project contains old dependencies, so setting up unit tests with Robolectric will require fighting with dependencies, resolving conflicts between old and new versions.

I would better focus on UI tests in Metamask-mobile repository (PR is here: MetaMask/metamask-mobile#15984)

private fun showAlertDialog(context: Context, extension: String, onPositiveButtonClick: () -> Unit) {
AlertDialog.Builder(context)
.apply {
setMessage("Do you want to download \nFile.${extension}?")

Choose a reason for hiding this comment

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

@Tyschenko Adjust message syntax to either use the existing filename or say ... want to download ${extension} file?

Copy link
Author

Choose a reason for hiding this comment

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

Why? I used the same text as in the existing dialog? Current text will make it easier to the user to find the file with this specific name

@RequiresApi(Build.VERSION_CODES.Q)
private fun saveFileForAndroidQAndLater(context: Context, fileBytes: ByteArray, mimeType: String, extension: String) {
val resolver = context.contentResolver
val fileName = "File.$extension"

Choose a reason for hiding this comment

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

@Tyschenko If you have to use a placeholder filename then use download.$extension

Copy link
Author

Choose a reason for hiding this comment

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

Why? How is it better than "File.extension"?

* File, File (1), File (2) and so on
*/
private fun getFileToSaveWithAvailableName(downloadsDir: File, extension: String): File {
var suffix = 1

Choose a reason for hiding this comment

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

Rename this to var iterator = 1. "Suffix" is usually the later part of a word, so "prefix" would work better but really iterator would work better here

Copy link
Author

Choose a reason for hiding this comment

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

This is a suffix to the file's name. File (5) where 5 is the suffix.
In Java/Kotlin the word iterator is a name of the system class, it can be misleading

@smilingkylan smilingkylan merged commit e56e8ff into main Jun 17, 2025
10 checks passed
@smilingkylan smilingkylan deleted the feature/support_blob_downloads_for_Android branch June 17, 2025 15:30
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.

[Sentry] Metamask mobile crashes when try to download file
3 participants