tabris-js
tabris-js copied to clipboard
Issue with uploading photos taken on the camera using fetch()
Problem description
Attempting to use fetch() with FormData() containing a blob for a photo taken on the device fails on Android, but works on iOS.
Expected behavior
fetch() should succeed and upload the photo.
Environment
- Tabris.js version: 3.7.2
- Device: Samsung Galaxy A40 (and Honor 10)
- OS: Android 11
Code snippet
let captured_pod_image = await camera.captureImage({flash: 'auto'});
let image = captured_pod_image.image;
let formData = new FormData();
formData.append("image", new Blob([image.arrayBuffer()], {type: image.type}), "some_image.jpg");
console.log("About to fetch");
// Make API request to confirm completion
const response = await fetch(
'http://bla/some/url (http on tabris dev app, https on our build)',
{
method: 'POST',
headers: {/* redacted */},
body: formData
}
);
console.log("Done fetch");
if (!response.ok) {
console.log("Fetch not OK");
return;
}
console.log("Fetch OK");
Now, the strange thing is that it works perfectly on iOS, but fails on Android. With iOS, we get this output:
About to fetch
Done fetch
Fetch OK
And with Android, we get:
About to fetch
> Listener for Button event "tap" rejected: TypeError: Network request failed
at onTap (/xxxx/src/xxx.tsx:946:92)
Is this an issue with Tabris itself, or am I doing something wrong? I’d really appreciate any help. Thanks
The real flow does in fact have a step in the middle where the image is shown back to the user to be confirmed before it gets bundled into the FormData and the image shows fine there on both all devices
In case it’s of use, I have tried (I know it’s less good and not plan A) to base64 encode the image and put it in a JSON body to post to the API, with code like:
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[ i ]);
}
return btoa(binary);
}
This works perfectly on iOS too, it works perfectly on an Android Honor 10, and very strangely it works about 20% of the time on a Samsung Galaxy A40, the remainder of the time the resulting base64 string is entirely number ones - 11111111… Maybe there is an issue with the arrayBuffer on Android?
@violuke, thanks for reporting the issue. We will figure it out.
Thanks @elshadsm this is currently a bug in our production app, so anything we can do to get it working ASAP would be great.
Happy to put in a less-than-perfect workaround in and even make a different API endpoint on our servers to accept any data format required in order to get this working as soon as we can... if you can think of a temporary solution until the bug itself is fixed? Cheers
@violuke Does it help at all to persist the file to disk before uploading it?
Also, logcat output on Android may help show more information than a generic "Network request failed"
Thanks for the suggestions. I couldn't manage to get logcat output, unfortunately. I tried both Chrome Dev Tools and PyCharm (as per WebStorm) config, but it simply wouldn't connect. This might be something with our internal Wi-Fi network security, I'm not sure.
I have made some progress, however....
- If I tried to upload the image immediately after taking the photo, it seemed to work fine. But then trying to display it in a
<ImageView>would often fail. - If I instead store the image (I tried as both arrayBuffer, a Blob and even creating the entire FormData) in a variable, then show a preview of the image in a popover, with an accept/reject button (a bit like this) then the upload always works fine on iOS and occasionally works on Android. When it fails, I get the TypeError.
- If I write the image to a file, and then use the string of the file path to render the preview image, it would sometimes show something very invalid (e.g. half the image) and sometimes even show the previous image that was before in the file from a previous attempt, even if I'd deleted that file before (maybe a caching thing?).
- If I put the image in a file, before showing the popover for acceptance/rejection, then read it out of this file for upload (instead of the variable approach in 2), this seems to be a bit more reliable on Android, but still fails very often.
- If I wrap my
fs.readFile()andfetch()in atry...catchand repeatedly try the process, it always works eventually. On iOS it always succeeds on the first attempt, but with Android it's typically attempt 2 that works, but occasionally it's taken as many as attempt 4 to work. I'm thinking this might be some kind of race condition, but I'm always usingawaitto ensure promises are resolved, so I'm not sure. The TypeError occurs before anything ever is sent over HTTP to the API. - I think reducing the
captureResolutionhelped with reliability, but this was quite anecdotal.
I hope this helps your investigation? I'm going to release a production app update with this in it and hopefully won't get any more complaints of issues, but will let you know if we do, or I work anything more out. Thanks for your help.
You don't need to connect a debugger to get logcat output. If you can connect your device via USB, turn on USB debugging and then run adb logcat. If that doesn't work there are dozens of logcat apps in the Play Store
@violuke, I tried running your snippet above but realized that you don't get arrayBuffer correctly in the following line:
formData.append("image", new Blob([image.arrayBuffer()], {type: image.type}), "some_image.jpg");
The arrayBuffer() method in the Blob interface returns a Promise that should be handled correctly.
I suggest updating your code as follows:
image.arrayBuffer().then(arrayBuffer => {
const formData = new FormData();
formData.append('image', new Blob([arrayBuffer], { type: image.type }), 'some_image.jpg');
// Upload image
});
I am waiting to hear from you about the result of the proposed update.