I am developing a hybrid app targeting iOS & Android. I had to implement a photo-uploader so I implemented it through ImagePicker & File Cordova Plugins. I tested it on Android and everything was okay.
The problem is when I test it for iOS I get the image URIs through ImagePicker but File throws the following error:
[error] - ERROR Error: Uncaught (in promise):
FileError: {"code":5,"message":"ENCODING_ERR"}
I have seen plenty of issues related to this error but all of them are related to Android while I have a problem with iOS. I understand that is a really complex issue so if anyone could give me any piece of advice, I would be grateful.
More details about the issue:
this.imagePicker
.getPictures({
maximumImagesCount: 1,
})
.then(async (result : string[]) => {
let file = 'file://' + result[0];
let path = file.substring(0, file.lastIndexOf("/") + 1);
this.image = await this.file.readAsDataURL(path, file): // Line which throws the exception
});
PS: I've tried adding 'file://' in all the ways I could imagine =)
Thanks in advance
Thanks both of you who helped, I fixed my problem through changing the outputType to 1 (base64 dataURL) instead of 0 (URI).
This is the code that fixed my bug.
this.imagePicker
.getPictures({
outputType: 1,
})
.catch((e) => alert(e))
.then(async (results: string[]) => {
this.image = "data:image/jpg;base64," + results[0]
}
});
A posible explanation for this is that ImagePicker is copying from the ios gallery directory to a ImagePicker's temporary directory, then File is trying to read those copied files but they must have been deleted by iOS File System.
Related
In case you guys don't know, there was a problem previously with this library not rendering local images on Android as well, but apparently it was solved. Now, I'm facing the exact same issue on iOS, with a difference that I can use static images like assets/src/assets/images/logo.png. But when the images start with something like file:///, storage://, ph:// it simply does not get rendered.
What I'm doing is trying to generate a PDF report file, which must be generated independently the user has an internet connection or not. That is the reason why I have to use local images.
The static image is the logo of the company, and the local image which is not getting rendered is an image saved to the phone's storage through Image Picker or Camera Roll. The React Native Image component displays the image perfectly, so I don't think I'm using a wrong path.
What I have tried so far:
Removing the file:/// or storage:// or ph:// from the beginning of the path string;
In some cases, when I save an image to the phone's library with Camera Roll, it will return a path that starts with ph:// but without an extension, such as .jpg or .png. I tried to put the extension manually, and still does not make any difference;
I tried to convert the image to base64 using rn-fetch-blob (with RNFetchBlob.fs.base64.encode(path)), but still got no success.
Devices:
iPhone SE with iOS 14 (also simulator iPhone 11 with iOS 15)
MacBook Air 2017 Core i5 1.8GHz and 8gb RAM (macOS Big Sur 11.5.2)
Environment
node: 12.22.7
npm: 6.14.15
react: 16.9.0
react-native: 0.61.5
react-native-html-to-pdf: ^0.11.0 (updating it to 0.12.0 also got me the same result)
Code:
sharePDF = async () => {
try {
this.changeVisibilityOptions(false);
this.changeVisibilityLoading('Gerando PDF...');
let htmlTemplate = '';
htmlTemplate = await getPDFDespesa(this.state);
const pdfOptions = {
html: htmlTemplate,
fileName: 'RelatorioDespesas',
directory: 'Relatorios'
};
let pdfFile = await RNHTMLtoPDF.convert(pdfOptions);
this.changeVisibilityLoading(false);
const shareOptions = {
title: 'Compartilhar com:',
url: `file://${pdfFile.filePath}`,
type: 'application/pdf',
failOnCancel: false
};
const ShareResponse = await Share.open(shareOptions);
} catch (error) {
this.setState({ visibilityLoadingScreen: false });
console.log('Error =>', error);
}
}
Final thoughts:
Well, since the code is stored at a private repository, I can't show the whole thing here for ethical reasons. But I'm doing my best to give you as much details as possible.
The output the code produces an almost complete PDF, with the only point that I see broken image icons where the images were supposed to be. For Android it works perfectly now.
I think this might be an issue related to WebView, since react-native-html-to-pdf uses WebView to generate the PDF from HTML code. I reached this conclusion after another developer at my job was trying to create a screen with a preview of the PDF before it could be shared got the very same problem for both Android and iOS. The library he used was react-native-webview.
Update with solution
Alright guys, after a long time of research, me and a colleague got to a solution which may not be the best but does what we expected.
First of all, one thing that was discovered is that we have to divide the problem in two, because we actually had two problems.
Images from react-native-image-picker: After a long time trying to find the problem which was preventing the local images from getting rendered, I tried updating the library to version 4.7.3 (latest version at that day) and did a number of required changes to the code, as the version we were using was considerably aged. Well, it happened to work out for my surprise, even with the response uri's format not being changed;
Images from #react-native-community/cameraroll: This one was a bit more complicated. It took me some time to realize that the iOS' PHAsset was not supported in the WebView or react-native-html-to-pdf (which uses WebView in background). So, after some research, me and my colleague found a workaround that lead us to a relatively easy solution. Basically we used react-native-fs to copy the PHAsset media file to a temporary directory, which would return a uri that started with file:// and could be rendered by WebView. That's the code we used to do this:
export default function getImageNameFromUrl(imageUrl = "") {
if (imageUrl) {
const splittedImageUrl = imageUrl.split('/');
return splittedImageUrl.pop();
}
return null;
};
export default async function copyAssetsFileIOSAndReturnURI(remoteURL = '', localURI = '') {
try {
if (remoteURL && localURI) {
const imageName = getImageNameFromUrl(remoteURL);
const imgPath = await RNFS.copyAssetsFileIOS(localURI, RNFS.TemporaryDirectoryPath+imageName, 0, 0);
return imgPath;
}
return null;
} catch (err) {
console.log(err);
return null;
}
}
I get the error below when downloading an audio file from Amplify Storage on an iOS device, both Simulator and a device. It all works perfectly fine when doing it on an Android emulator and device.
When downloading a JSON-file from the Storage it's working on both platforms without errors.
The JSON I'm downloading in the app as a guest, while the audio file is downloaded as private, but I don't see why this should make a difference when it's working on Android.
StorageException(message: Unexpected error occurred with message: Domain: [NSCocoaErrorDomain
Code: [4
LocalizedDescription: [The file doesn’t exist.
LocalizedFailureReason: [The file doesn’t exist.
LocalizedRecoverySuggestion: [, recoverySuggestion: This should not happen. There is a possibility that there is a bug if this error persists. Please take a look at https://github.com/aws-amplify/amplify-ios/issues to see if there are any existing issues that match your scenario, and file an issue with the details of the bug if there isn't.
Issue encountered at: file: /Users/x/Documents/Github/TransscriberAI-App/Flutter/transscriber/ios/Pods/Amplify/Amplify/Categories/Storage/Error/StorageError.swift
function: recoverySuggestion line: 63, underlyingException: null)
Here is the code I use to download the file:
Future<String> getAudioPath(String key) async {
final docDir = await getTemporaryDirectory();
final filePath = docDir.path + '/audio/$key';
File file = File(filePath);
final S3DownloadFileOptions options = S3DownloadFileOptions(
accessLevel: StorageAccessLevel.private,
);
if (await file.exists()) {
return filePath;
} else {
try {
await Amplify.Storage.downloadFile(
key: key,
local: file,
options: options,
);
return filePath;
} on StorageException catch (e) {
print('Error downloading file: $e');
return 'null';
}
}
}
Update:
I have now created an issue on the Amplify flutter Github
https://github.com/aws-amplify/amplify-flutter/issues/1276
The problem was caused by the /audio/ folder not existing on iOS. This answer tells how you can create the path, if it doesn't exist.
In the Unity Editor, the app works well with the Firebase storage. Images are downloaded and used. When we build the Unity app for iOS, it gave errors when it tries to download the images from the Firebase Storage. I think, this is an issue to writing permission, and I have no clue on how to solve it from Unity code
What was done
Clean Build Folder
Update to Latest Firebase packages
Refer to these SO questions:
Firebase Storage download to local file error
Images not downloading from Firebase Storage
Error when downloading from Firebase storage
These threads aren't using Unity, and I don't know how these can be applied to the issue at hand
Code
string savePath = Application.persistentDataPath + "/" + data.banner;
StorageReference storageRef = storage.GetReference(data.banner);
Task task = storageRef.GetFileAsync(savePath, new StorageProgress<DownloadState>((DownloadState state) => {
GetComponent<DatabaseManager>().UpdateProgress(state.BytesTransferred, state);
}), CancellationToken.None);
task.ContinueWithOnMainThread(resultTask => {
if (!resultTask.IsFaulted && !resultTask.IsCanceled && resultTask.IsCompleted) {
floorCounter++;
if (floorCounter == floors.Count) {
isFloorComplete = true;
}
} else {
Debug.Log(resultTask.Exception.ToString());
errorCaught = true;
return;
}
});
Errors
System.AggregateException: One or more errors occurred. ---> Firebase.Storage.StorageException: An unknown error occurred
--- End of inner exception stack trace ---
---> (Inner Exception #0) Firebase.Storage.StorageException: An unknown error occurred<---
<>c__DisplayClass3_1:<DownloadImages>b__3(Task)
System.Action`1:Invoke(T)
Firebase.Extensions.<ContinueWithOnMainThread>c__AnonStorey1:<>m__0()
System.Func`1:Invoke()
Firebase.<RunAsync>c__AnonStorey1`1:<>m__0()
System.Action:Invoke()
Firebase.ExceptionAggregator:Wrap(Action)
Firebase.Dispatcher:PollJobs()
Firebase.Platform.FirebaseHandler:Update()
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
The errors is not helpful and to make it worse, I don't know how to find the debug log mentioned at Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35). I've opened the Xcode project, and see no Runtime folder (even after showing hidden items)
Any idea how to solve this issue?
The solution was certainly not obvious.
Apparently, all I need to do was to prepend file:/// to the savePath variable, like so:
string savePath = "file:///" + Application.persistentDataPath + "/" + data.banner
I am trying to download a file and save that file in iOS. I am using ionic-native-file plugin to achieve this. The file has been download but i could not find that file in the device.
filewrite = (): void => {
let transfer = this.fileTransfer.create();
let path = "";
let dir_name = 'Download';
let file_name = "Sample.pdf";
if (this.platform.is('ios')) {
this.platform.ready().then(() => {
path = this.file.documentsDirectory;
this.file.writeFile(path,file_name,this.pdfSrc).then((entry) => {
this.showAlert("download completed");
}, (error) => {
this.showAlert("Download Failed.");
}).catch(err=>{
this.showAlert("Download Failed catch.");
this.showAlert(err.message);
});
}
)
}
};
Download completed alert shows and the downloaded path is:
file:///var/mobile/Containers/Data/Application/6DE22F30-5806-4393-830A-14C8A1F320BE/Library/Cloud/Sample.pdf
But i could not find that location in the device. Then, i google and saw here.
cordova.file.documentsDirectory - Files private to the app, but that
are meaningful to other application (e.g. Office files). Note that for
OSX this is the user's ~/Documents directory. (iOS, OSX)
So, i can't see the file actually as it is private to the application. Then i saw in the same link:
So, i tried with the both preference in my config.xml but nothing happened.
Is there any approach to download the file iOS or to dropbox or anywhere else?
I've struggled to find the right directory to save to on different platforms with this cordova plugin too. I landed on using file.externalRootDirectory on Android, and file.cacheDirectory on iOS.
The right location likely depends on what you intend to do with the file. In my case I just needed it stored short-term so I can open it using the user's native PDF reader.
I'm developing a App with Ionic 2 and I'm have problems with #ionic-native/Camera. I've this code on Upload.ts
let loader = this.loading.create({
content: 'Carregando video....'
});
loader.present().then(() => {
const options: CameraOptions = {
quality: 100,
destinationType: this.camera.DestinationType.FILE_URI,
sourceType: this.camera.PictureSourceType.PHOTOLIBRARY,
mediaType: this.camera.MediaType.VIDEO,
}
this.camera.getPicture(options).then((videoData) => {
this.uploadForm.controls['file'].setValue(videoData)
loader.dismiss();
}, (err) => {
console.log(err);
});
});
This code works fine in Android, but when I run ionic cordova run ios -lc, the promise this.camera.getPicture(options) is never resolved, so the loader keep running forever.
Thanks in advance!
So, I found the problem. First thing is that native components bugs with -l (--livereload). I don't know how to explain why but I got this information from the Ionic Worldwide slack. A member from Ionic Team said:
"live-reload on a device can cause issues with plugins and file system".
So I used this video to understand how to debbug the APP using the iOS emulator and Safari.
https://www.youtube.com/watch?v=Aob749bkoLY
A little brief of the video: when using iOS emulator, you can access the menu Developer > Emulator > <App Name>. A new window with inspector tools will open and logs from the emulator will appear on this window.
I found out that's the video url was incorrect. Before, to be compatible with Android, I've this code responsible to find the video pointer in system and send to the server:
filePath = 'file:///' + this.uploadForm.controls['file'].value;
But, iOS File Picker already has a "file:///" prefix. So prefixing it again made it wrong. So I updated the code to be like this:
if (this.platform.is('android')) {
filePath = 'file:///' + this.uploadForm.controls['file'].value;
} else {
filePath = this.uploadForm.controls['file'].value;
}
This resolved the problem.