-
Notifications
You must be signed in to change notification settings - Fork 5
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
feat: For Android add support for downloading files with blob or base64 URLs #50 #51
Conversation
@@ -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 |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
- 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
- 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}?") |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
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