react-native-image-picker - persistent storage after rebuild - ios

I've been hitting my head on the screen for some time and just can't get it to work despite all the information I've found on git or stackoverflow.
What I'm trying to achieve:
Persistence of the images selected (from library or camera). Persistence also when I rebuild my app, which means when I run react-native run-ios for iOS, or react-native run-android for Android.
What I've implemented
Here is my function that is called when I add an image.
ImagePicker.showImagePicker({
storageOptions: {
path: 'myCustomPath',
waitUntilSaved: true,
cameraRoll: true,
skipBackup : true
},
noData: true
}, res => {
if (res.didCancel) {
console.log("User cancelled");
} else if (res.error) {
console.log("Error", res.error);
} else {
console.log("Picker Response:", res);
// Getting the fileName as for iOS the fileName is incorrect even with waitUntilSaved to true.
const path = res.uri.split("/");
const fileName = path[path.length-1];
let uri = "file://" + Platform.select({
android: RNFS.ExternalStorageDirectoryPath + "/Pictures/",
ios: RNFS.DocumentDirectoryPath+"/"
})
+ "myCustomPath/" + fileName
this.setState({
pickedImage: {
uri: uri,
}
});
this.props.onImagePicked({ uri: res.uri });
}
});
And in my home page, I display the image in <Thumbnail square style={{width:50, height:50}} source={info.item.image} />
---- EDIT START -----
---- SOLUTION ----
The solution was to reconstruct the path where I want to display the image using the fileName:
<Thumbnail square style={{width:50, height:50}} source={{uri : RNFS.DocumentDirectoryPath+"/"+info.item.image.fileName}} />
---- EDIT ENDS -----
The Result
So here are the results, you'll see works well on Android, but not on iOS.
I selected an image on the library using the react-native-image-picker.
Android
"Picker Response":
fileName: "image-d53839d1-fa89-4a61-b648-f74dace53f83.jpg"
fileSize: 235728
height: 1440
isVertical: true
originalRotation: 0
path: "/storage/emulated/0/Pictures/myCustomPath/image-d53839d1-fa89-4a61-b648-f74dace53f83.jpg"
type: "image/jpeg"
uri: "file:///storage/emulated/0/Pictures/myCustomPath/image-d53839d1-fa89-4a61-b648-f74dace53f83.jpg"
width: 1080
the save "uri" for my image is : file:///storage/emulated/0/Pictures/myCustomPath/image-d53839d1-fa89-4a61-b648-f74dace53f83.jpg
Behavior is as below :
left screenshot is after saving my image,
right screenshot is after a rebuild, opening the app, I still see my image!
--> Happy!
Now on iOS
"Picker Response" :
fileName: "IMG_0004.JPG"
fileSize: 470300
height: 1618
isVertical: true
latitude: 64.752895
longitude: -14.538611666666666
origURL: "assets-library://asset/asset.JPG?id=99D53A1F-FEEF-40E1-8BB3-7DD55A43C8B7&ext=JPG"
timestamp: "2012-08-08T21:29:49Z"
type: "image/jpeg"
uri: "file:///Users/[name]/Library/Developer/CoreSimulator/Devices/33076AD2-C989-47E9-A803-3E56CC4B09D6/data/Containers/Data/Application/4756F0A2-9CCC-4F9A-9315-D55434328FD9/Documents/myCustomPath/6A5C27E3-89F7-465F-A855-66749C92D086.jpg"
width: 1080
the save "uri" for my image is : file:///Users/[name]/Library/Developer/CoreSimulator/Devices/33076AD2-C989-47E9-A803-3E56CC4B09D6/data/Containers/Data/Application/4756F0A2-9CCC-4F9A-9315-D55434328FD9/Documents/myCustomPath/6A5C27E3-89F7-465F-A855-66749C92D086.jpg
Behavior is as below :
left screenshot is after saving my image,
right screenshot is after a rebuild, opening the app, I still see my image!
--> NOT HAPPY!
My issue
I've read all about the temporary aspect of the returned uri and see what react-native-image-picker says about it:
On iOS, don't assume that the absolute uri returned will persist.
I've found also this thread which had a similar issue, but none worked:
RNFS.DocumentDirectoryPath after a rebuild had a different UUID in the path, so the file was not found as it was saved with the previous UUID
I tried saving for iOS the uri as '~/Documents/myCustomPath/myfilename.jpg' had the same behavior. Displayed when added, blank after rebuild.
I had before the rebuild:
Document directory path being :
file:///Users/[user]/Library/Developer/CoreSimulator/Devices/33076AD2-C989-47E9-A803-3E56CC4B09D6/data/Containers/Data/Application/466CAF1A-AF8D-423C-9BF6-F0A242AF8038/Documents/
and my saved picture uri :
file:///Users/[user]/Library/Developer/CoreSimulator/Devices/33076AD2-C989-47E9-A803-3E56CC4B09D6/data/Containers/Data/Application/466CAF1A-AF8D-423C-9BF6-F0A242AF8038/Documents/myCustomPath/20969988-633B-46BD-8558-E39C3ADD6D12.jpg
but after the rebuild the Application UUID changed to BEE128C8-5FCF-483C-A829-8F7A0BB4E966 making now my document directory to be
file:///Users/[user]/Library/Developer/CoreSimulator/Devices/33076AD2-C989-47E9-A803-3E56CC4B09D6/data/Containers/Bundle/Application/BEE128C8-5FCF-483C-A829-8F7A0BB4E966/Documents
but still looking the picture in the uri :
file:///Users/[user]/Library/Developer/CoreSimulator/Devices/33076AD2-C989-47E9-A803-3E56CC4B09D6/data/Containers/Data/Application/466CAF1A-AF8D-423C-9BF6-F0A242AF8038/Documents/myCustomPath/20969988-633B-46BD-8558-E39C3ADD6D12.jpg
When the image is actually now located under:
file:///Users/[user]/Library/Developer/CoreSimulator/Devices/33076AD2-C989-47E9-A803-3E56CC4B09D6/data/Containers/Data/Application/BEE128C8-5FCF-483C-A829-8F7A0BB4E966/Documents/myCustomPath/20969988-633B-46BD-8558-E39C3ADD6D12.jpg
So how can I cope for that App UUID change after a rebuild as paths are dynamic on iOS.
Versions I use:
"react-native": "0.57.8",
"react-native-fs": "^2.13.2",
"react-native-image-picker": "^0.28.0"

As react-native-image-picker states
On iOS, don't assume that the absolute uri returned will persist.
This means that if you store the absolute uri to the file and then do react-native run-ios the application GUID will change and that absolute uri will no longer exist or work. However, and this is important the image will be in the folder that you saved it in, as it is copied across.
The only solution to fixing this is to save the directory and the filename of the file and reconstruct the uri when you want to use it.
So that means that you would save in state
this.setState({ image: '/myCustomPath/image.jpg' });
Then when you come to use it you would rebuild the path like this
let pathToFile = `${RNFS.DocumentDirectoryPath}${this.state.image}`

You could simply solve this (without rely on react-native-fs) by replacing the path before "/Documents/..." with a tilde "~".
let pathToFile = "file:///var/mobile/Containers/Data/Application/9793A9C3-C666-4A0E-B630-C94F02E32BE4/Documents/images/72706B9A-12DF-4196-A3BE-6F17C61CAD06.jpg"
if (Platform.OS === 'ios') {
pathToFile = '~' + pathToFile.substring(pathToFile.indexOf('/Documents'));
}
This is supported by React Native as you can see in their source code.

Related

Play remote audio with react-native-audio-recorder-player and RNFetchBlob not working on iOS

I'm working on a React Native app where I need to load an audio file from the backend and play it in the app.
For this I'm using the packages RNFetchBlob and react-native-audio-recorder-player.
The problem is that my implementation works perfectly on Android, but it doesn't work on iOS... Even for playing files that were recorded using the react-native-audio-recorder-player inside iOS itself.
When playing files downloaded using RNFetchBlob I get the following error:
FigFileForkOpenMainByCFURL signalled err=2 (errno) (open failed) at /Library/Caches/com.apple.xbs/Sources/EmbeddedCoreMediaFramework_Sim/EmbeddedCoreMedia-2765.6/Sources/Platform/Darwin/DarwinFile.c:576
The part of the code that matters:
import AudioRecorderPlayer from 'react-native-audio-recorder-player';
import RNFetchBlob from 'rn-fetch-blob';
// THIS IS A SAMPLE, NOT THE REAL URL AND TOKEN.
const fileRemoteUrl = 'https://my-backend.com/files/file-id';
const authToken = 'my-authtoken';
// could be 'mp3' or 'aac', etc...
const fileExtension = 'm4a';
const dir = NFetchBlob.fs.dirs.DocumentDir;
const path = `${dir}/${Base64.btoa(fileRemoteUrl)}.${fileExtension}`;
const res = await RNFetchBlob.config({
fileCache: false,
appendExt: fileExtension,
path,
}).fetch('GET', fileRemoteUrl, { Authorization: `Bearer ${authToken}` });
const internalUrl = `${Platform.OS === 'android' ? 'file://' : ''}${res.path()}`;
const audioRecorderPlayer = new AudioRecorderPlayer();
await audioRecorderPlayer.startPlayer(internalUrl);
// here the audio should start playing,
// but I get the error on the device(simulator) console (xcode output)
As I said before, the same code works like a charm on Android.
Any idea how to solve this problem? I'm stuck in this...
I appreciate any help!
In the end I found that the problem was in this line:
const internalUrl = `${Platform.OS === 'android' ? 'file://' : ''}${res.path()}`;
For both iOS and Android it is necessary to add the prefix "file://" to the file path before passing to the "startPlayer" method.
The reason I was previously only adding the prefix on Android is because I use the same code snippet to load files of other formats (not audio). And for example to use the component "<Image source={{ uri }} />" on iOS the uri cannot have the prefix.
So the solution was to create a specific treatment to add the prefix when the platform is iOS just before calling the "startPlayer" method.

How to export files in Unity iOS build

I would like to export/share a csv file in my Unity iOS build. The most satisfying functionality would be the sharing pop-up after creating a file, because I don't want to have my users search in /var/mobile/Containers/Data/Application//Documents (like with Application.persistentDataPath).
Is there any solution/plugin for this functionality?
Kind regards Chris
For this purpose, i use https://github.com/ChrisMaire/unity-native-sharing
After adding this as an asset, just call this function:
NativeShare.Share(string body, string filePath, string url, string subject, string mimeType = "text/html", bool chooser, string chooserText)
this supports Android & iOS - and iOS only uses the first 4 parameters - actually you can get it to work with only the filePath attribute - so if you save the csv file (under Application.persistentDataPath), you could simply call:
NativeShare.Share("", Application.persistentDataPath + "/myShinyCsvFile.csv", "", "", "", false, "");

How to create and save file to Files application (iOS platform)

I need to create PDF-file (done) and save it to Files application, so user can access it anytime outside of my app. I tried rn-fetch-blob and react-native-fs packages and they worked fine for Android, but for iOS they can only create files to internal app storage (so files are not created in external storage, i.e. not in iOS Files application).
What options do I have to save my created files to Files application? I know that it is possible, i.g. Slack app allows saving files to Files app.
You have to either add the UISupportsDocumentBrowser key and set to true, or both the UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace keys in your Info.plist file
When the user will open a document from your app's Documents directory, through Files app, they would be editing the document in place. The changes are saved to your app's Documents directory.
Refer this for more.
you can try with react-native-fs to save and download in local directory.
var url = path
let fileName = path.split("/").pop()
// console.log('fileName', fileName)
var ext = this.extention(url);
ext = "." + ext[0];
const localFile = ${RNFS.LibraryDirectoryPath}/${fileName};
// console.log('localFile', localFile)
const options = {
fromUrl: path,
toFile: localFile,
fileCache: true
};
if (await RNFS.exists(localFile)) {
FileViewer.open(localFile, { displayName: this.props.route.params.conversationSubject });
} else {
await RNFS.downloadFile(options).promise
.then((res) => {
// console.log('res', res)
FileViewer.open(localFile);
})
.then((res) => {
// success
// console.log("success", res);
})
.catch(error => {
// error
console.log("Attachment open error: ", error);
});
}
I've added new functionality to react-native-share module to store a file only in Files app. It's not merged yet but you can use my fork version.
https://github.com/gevorg94/react-native-share/tree/share-files-app.
Replace "react-native-share": "X.X.X" with "react-native-share": "git+https://github.com/gevorg94/react-native-share.git#share-files-app",.
You can find usage in PR https://github.com/react-native-community/react-native-share/pull/681

How to extract Dart/Flutter video metadata

I am developing a flutter demo app. I want to use metadata about a video in my phone storage. I am able to extract the path of that video, but don't know how to extract its metadata in dart/flutter.
I need the following metadata:
Duration of video
Name of video
Size of video
When video was taken
You can use the VideoPlayerController.file constructor from the official video player plugin (which is maintained by the official google team so you don't have to worry about its future and stability) to access the file and get the following meta after you install the package:
first this is your VideoPlayerController:
VideoPlayerController controller = new VideoPlayerController.file('');//Your file here
Duration:
controller.value.duration ;
Video Name, this should already be possessed with you as you can reach the file path and pass it to the player constructor.
3.Video Size:
controller.value.size ;
4.As for when the video was taken I can't help you with this. You have to find another way to figure it out.
One of the ways to get the creation time of the video in FLutter is to use flutter_ffmpeg plugin.
Add it to the pubspec.yaml:
dependencies:
flutter_ffmpeg: ^0.3.0
Get the file path of your video, for example with file_picker:
File pickedFile = await FilePicker.getFile();
Get meta data of the video by its path using ffmpeg:
final FlutterFFprobe flutterFFprobe = FlutterFFprobe();
MediaInformation mediaInformation = await flutterFFprobe.getMediaInformation(pickedFile.path);
Map<dynamic, dynamic> mp = mediaInformation.getMediaProperties();
String creationTime = mp["tags"]["creation_time"];
print("creationTime: $creationTime");
And in the console you'll get smth like this:
I/flutter (13274): creationTime: 2020-09-24T17:59:24.000000Z
Along with creation time, there are other useful details:
Note: adding this plugin to your app increases the weight of your final apk!
your code is correct Mazin Ibrahim you just need to initialize it. the future value will return all the details.
Future getVideo() async {
await MultiMediaPicker.pickVideo(source: ImageSource.gallery).then((video){
File _video = video;
VideoPlayerController fileVideocontroller = new VideoPlayerController.file(_video)
..initialize().then((_) {
debugPrint("========"+fileVideocontroller.value.duration.toString());
});
});
}

Cordova iOS application directory and file url differs (UUID issue)

I've created a Cordova application that fetches images from a server and saves them to an iPad. However, when trying to display the images in the application, the images will not load. One example of such a file path could be:
file:///var/mobile/Containers/data/Application/FC87E925-9753-4D9F-AE27-54FCF9B0451E/Documents/-media-3405-company.png
However, when inspecting the cordova.file.applicationDirectory variable, I find another path, e.g. (note that the UUID differ even though I'm inspecting both variables in the same run)
file:///var/containers/Bundle/Application/D8266D08-18A4-4293-B78A-B4597FC0C6B8/salesApp.app/
Accordingly to the documentation, the correct path "should" be: (however, that does not work either)
file:///var/mobile/Applications/UUID/Documents/-media-3405-company.png
Here is the code I use to load the images, which are correctly saved to the device
const downloadFile = (url, fileName, callback) => {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, (fs) => {
fs.root.getFile(fileName, {
create: true,
exclusive: false
}, (fileEntry) => {
const fileURL = fileEntry.toURL()
const fileTransfer = new FileTransfer()
fileTransfer.download(
url,
fileURL,
(entry) => {
const file = entry.toURL() // <--- HERE
content.pushObject('Downloaded ' + entry + ' (' + fileName + ') ' + file)
callback(file)
},
(error) => {
content.pushObject('error ' + error.code + '(' + fileName + ')')
if (error.code === FileTransferError.CONNECTION_ERR) {
downloadFile(url, fileName) // Try again
} else {
decrement(url) // Ignore this file
}
}
)
}, (error) => {
alert(2)
})
}, () => {
alert(3)
})
}
Update: Inspecting the value for cordova.file.documentsDirectory, I found that it returns a path similar to: file:///var/mobile/Containers/Data/Application/{UUID}/Documents/.
Update: The following code will return two different UUIDs:
alert(cordova.file.applicationDirectory); // file:///var/containers/Bundle/Application/54E0F914-C45B-4B8F-9067-C13AF1967760/salesApp.app/
alert(cordova.file.documentsDirectory); // file:///var/mobile/Containers/Data/Application/73806E90-90B4-488C-A68A-2715C3627489/Documents/
When inspecting the path for entry.toURL() i get the same UUIDs as the one returned in cordova.file.documentsDirectory.
When you claim that "images will not load", then you should provide the code used to load the images.
The code you provided is to download the images and it works fine.
As you didn't provide the code used to load the images I have tried two things and both of them worked
Open the file on InAppBrowser. I installed the cordova-plugin-inappbrowser and opened the file like this window.open(file,'_blank');
Display the file on a img tag. I created a img tag in my index.html <img id="downloaded" src=""/> and on my callback I assign the file obtained to the src document.getElementById("downloaded").src = file;
Both of them worked.
So you should provide your code used to load the images because the problem might be there.
The path you are getting on the download is ok.
You are getting different UUIDs because the docs are outdated. Before Xcode 6/iOS8 the app sandbox had the Bundle container and the Data container inside the same folder (the one the docs mention with a common UUID), but since Xcode 6/iOS8 the app files (Bundle container) are in a path and the App data files are in another one (Data container).
But that shouldn't be a problem for you.
Answer that talks about the file structure changes

Resources