Storing/Moving photo to a separate app folder in the phone - android

I am building a react-native app that takes a photo or choose a photo from Gallery and the photo should be moved to a separate folder called {appName} folder in the phone. I am storing all the images locally so it is critical that the image is stored in the folder.
For example: When you upload photo to Instagram or Whatsapp, the app will create a separate folder in the phone called Instagram/Whatsapp and store all of its photo in that folder.
I am using react-native to build the app, and image picker to take photo or choose photo from gallery.
I am currently trying on a simulator for Iphone, but I want it to work on both Iphone and Android.
I tried using 'react-native-fs' and 'react-native-fetch-blob'. But its not working correctly,
const dirPictures = `${RNFS.DocumentDirectoryPath}/hazelnut`;
//const dirPictures = `${RNFS.PicturesDirectoryPath}/hazelnut`;
const newImageName = `${moment().format('DDMMYY_HHmmSSS')}.jpg`;
const newFilepath = `${dirPictures}/${newImageName}`;
const imageMoved = await this.moveAttachment(this.state.image.uri, newFilepath);
moveAttachment = async (filePath, newFilepath) => {
return new Promise((resolve, reject) => {
RNFS.mkdir(dirPictures)
.then( () => {
RNFS.moveFile(filePath, newFilepath)
.then(() => {
console.log('FILE MOVED', filePath, newFilepath);
resolve(true);
})
})
.catch(err => {
console.log('mkdir error', err);
reject(err);
});
});
};
The Image is moved, but it is not the behavior I want. Using the code above, it is moved to a document directory. But I want to move it to the photo directory. If I go into Photos, I should be able to see the app folder with all the images.
If I use const dirPictures = ${RNFS.PicturesDirectoryPath}/hazelnut, then I get You don’t have permission to save the file “hazelnut” in the folder “undefined”.

I think you should use this :
const dirPictures = `${RNFS.DocumentDirectoryPath}/hazelnut`;
For android
And this :
const dirPictures = `${RNFS.LibraryDirectoryPath}/hazelnut`;
For ios

Related

How to get the thumbnail of an Android gallery picture, in NativeScript?

I understand that Android automatically creates a thumbnail, for every picture taken by the camera. I need to be able to display that thumbnail.
I'm using nativescript-imagepicker plugin to select images. The plugin returns only the size and src of the selected image(s), for instance:
'/storage/emulated/0/DCIM/DSCF2060.jpg'
How could i use this src, to retrieve the corresponding thumbnail(is it even possible?).
The Android API is very confusing for me(not to mention the Java), so any help will be greatly appreciated.
Use ThumbnailUtils
export function onGetImageButtonTap(args) {
let context = imagepicker.create({
mode: "single"
});
context
.authorize()
.then(function () {
return context.present();
})
.then(function (selection) {
selection.forEach(function (selected) {
const size = layout.toDevicePixels(96);
const bitmap = android.media.ThumbnailUtils.extractThumbnail(android.graphics.BitmapFactory.decodeFile(selected.android),
size, size);
args.object.page.getViewById("thumbnailImg").src = fromNativeSource(bitmap);
});
}).catch(function (e) {
console.log(e)
});
}
Playground Sample

React-Native <Image source={url}> - Path to Cached Image

Maybe my question will sound foolish, but here it is...
How can we get the path to a cached image (in both iOS and Android)?
Here is my use case: I present a view on my App that lists images from the web --> I get an array of urls from Google Customer Search API based on the user's provided keywords...
<FlatList
style={{ width: '100%' }}
data={this.state.suggestions}
numColumns={2}
renderItem={(img) => {
return (
<TouchableOpacity onPress={() => this.selectImageHandler(img.item)} >
<Image source={{ uri: img.item.link }} />
</TouchableOpacity>
)
}}
keyExtractor={(item, index) => index.toString()}
/>
The result looks like this:
Then the user presses on an image to select it, which then needs to store this image in the app's folder (PictureDir/myappfolder/ in Android, DocumentDir/myappfolder/ in iOS) ...
What I am doing right now is when the image is selected, I download it again:
selectImageHandler = (img) => {
// (... pre code ...)
RNFS.downloadFile({
fromUrl: img.link, // e.g. "http://www.url.to/the/picture.png
toFile: uri, // e.g. "file://"+PictureDir+ "/myAppFolder/picturename.jpg"
}).promise.then( res => {
// (... post code ...)
}
It works fine! But it takes a bit of time, as it downloads again the image,
but I feel this is doing it twice, as it was downloaded already a stored in the cache to be displayed.
So here comes my question again, is there a way to know where the image was stored in the cache, so that when the user pressed the image to save it, it will not download it again, but will rather move it from the cache folder to the app's folder?
Am I making any sense? Or is redownloading the right approach?
Thanks for your help!
One way to avoid re-downloading images a few times might be to take over the control of downloading from a remote url from the <Image> component. Basically, you can download the remote image using the RNFS.downloadFile method and then supply the local URI (toFile value) as the image source. This requires a bit more work, of course, as we need to create a wrapper component, but this approach also provides options to control the display of an image while it's loading.
For example:
import React, { useState, useLayoutEffect } from 'react';
import RNFS from 'react-native-fs';
import { URL } from 'react-native-url-polyfill';
const CACHE_DIR = RNFS.DocumentDirectoryPath; // Cross-platform directory
function ImageCard ({ imgUrl }) {
const [cachedImgUrl, setCachedImgUrl] = useState(null);
const [isImageLoading, setIsImageLoading] = useState(true);
useLayoutEffect(() => {
const getCachedImageUrl = async () => {
try {
const basename = new URL(imgUrl).pathname.split('/').pop();
const localImgUrl = `file://${CACHE_DIR}/${basename}`;
if (await RNFS.exists(localCacheUrl)) {
setCachedImgUrl(localImgUrl);
} else {
const download = RNFS.downloadFile({
fromUrl: imgUrl,
toFile: localImgUrl,
});
const downloadResult = await download.promise;
if (downloadResult.status === 200) {
setCachedImgUrl(localImgUrl);
}
}
} catch (err) {
// handle error
} finally {
setIsImageLoading(false);
}
};
getCachedImageUrl();
}, [imgUrl]);
if (isImageLoading || !cachedImgUrl) {
// A better idea would be to return some `<Loader />` component, or some placeholder, like skeleton animation, etc. This is just an example.
return null;
}
return (
<Image source={{ uri: localImgUrl }} />;
);
}
The <ImageCard /> component replaces the plain <Image /> component in the <FlatList /> and downloads from the remote image URL only once.
The code above is simplified and it assumes that you have unique image names that you can use as the identifiers on the file system, and that the image urls don't include any search parameters, etc. Please be cautious and adapt the code for your needs before using it directly.

How to get the uid of the authenticated Firebase user in a Cloud Functions storage trigger

Background: I'm using Firebase Cloud Functions, the new Firestore Database, and storage bucket with an Android client.
What I want to accomplish:
When a user uploads a picture to a storage bucket, I want to use cloud functions to get the file path/link to the image location in the storage bucket and store this string as a new document under a new collection called "pictures" under the currently logged in user's document in Firestore.
That way, I can see the images each user has uploaded directly in Firestore and it makes it easier to pull a specific user's images down to the Android client.
What I've completed so far:
1. When a user logs in for the first time, it creates a user doc in the new Firestore Database.
2. A logged in user can upload an image to a storage bucket.
3. Using Firebase Cloud Functions, I managed to get the file path/link of the storage location as follows:
/**
* When a user selects a profile picture on their device during onboarding,
* the image is sent to Firebase Storage from a function running on their device.
* The cloud function below returns the file path of the newly uploaded image.
*/
exports.getImageStorageLocationWhenUploaded = functions.storage.object().onFinalize((object) => {
const filePath = object.name; // File path in the bucket.
const contentType = object.contentType; // File content type.
// Exit if this is triggered on a file that is not an image.
if (!contentType.startsWith('image/')) {
console.log('This is not an image.');
return null;
}
console.log(filePath);
});
Question: How do I get the currently logged in user and store this user's uploaded image file path/link as a new doc under this logged in user's documents within the Firestore database using Cloud Functions?
Currently, with Cloud Storage triggers, you don't have access to authenticated user information. To work around that, you'll have to do something like embed the uid in the path of the file, or add the uid as metadata in the file upload.
I had a similar problem where I wanted to associate a user uploaded image with his/her uid. I found an elegant solution that does not necessarily requires inserting the uid in the path of the file or even adding it as metadata in the file upload. In fact, the uid is securely transmitted to the database via the standard idToken encoding. This example employs a modified version of the generate-thumbnail cloud function example (found here) which I believe the author of the question was using/alluding to. Here are the steps:
Client side:
Create a trigger function that will run once the user uploaded the image - this functions will simply call the cloud function directly (via the httpsCallable method). The cloud function will receive the user uid (idToken encoded), along with any image metadata you may wish to send. The cloud function will then return a signed URL of the image.
const generateThumbnail = firebase.functions().httpsCallable('generateThumbnail');
const getImageUrl = (file) => {
firebase.auth().currentUser.getIdToken(true)
.then((idToken) => generateThumbnail({
idToken,
imgName: file.name,
contentType: file.type
}))
.then((data) => {
// Here you can save your image url to the app store, etc.
// An example of a store action:
// setImageUrl(data.data.url);
})
.catch((error) => {
console.log(error);
})
}
Create an image upload function – this is a standard file upload handling function; you can make the uid part of storage file path location for the image (if you want to) but you can also trigger a firebase function once the image has been uploaded. This is possible using the 3rd parameter of the on method. Include the trigger function above as the 3rd argument in here.
// Function triggered on file import status change from the <input /> tag
const createThumbnail = (e) => {
e.persist();
let file = e.target.files[0];
// If you are using a non default storage bucket use this
// let storage = firebase.app().storage('gs://your_non_default_storage_bucket');
// If you are using the default storage bucket use this
let storage = firebase.storage();
// You can add the uid in the image file path store location but this is optional
let storageRef = storage.ref(`${uid}/thumbnail/${file.name}`);
storageRef.put(file).on('state_changed', (snapshot) => {}, (error) => {
console.log('Something went wrong! ', error);
}, getImageUrl(file));
}
Server side:
Create a cloud function to convert the image into a resized thumbnail and generate a signed URL – this cloud function takes the image from storage, converts it into a thumbnail (basically reduces its dimensions but keeps initial aspect ratio) using ImageMagick (this is installed by default on all cloud function instances). It then generates a signed URL of the image location and returns it to the client side.
// Import your admin object with the initialized app credentials
const mkdirp = require('mkdirp');
const spawn = require('child-process-promise').spawn;
const path = require('path');
const os = require('os');
const fs = require('fs');
// Max height and width of the thumbnail in pixels.
const THUMB_MAX_HEIGHT = 25;
const THUMB_MAX_WIDTH = 125;
// Thumbnail prefix added to file names.
const THUMB_PREFIX = 'thumb_';
async function generateThumbnail(data) {
// Get the user uid from IdToken
const { idToken, imgName, contentType } = data;
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
const uid = decodedIdToken.uid;
// File and directory paths.
const filePath = `${uid}/thumbnail/${imgName}`;
const fileDir = path.dirname(filePath);
const fileName = path.basename(filePath);
const thumbFilePath = path.normalize(path.join(fileDir, `${THUMB_PREFIX}${fileName}`));
const tempLocalFile = path.join(os.tmpdir(), filePath);
const tempLocalDir = path.dirname(tempLocalFile);
const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath);
// Exit if this is triggered on a file that is not an image.
if (!contentType.startsWith('image/')) {
return console.log('This is not an image.');
}
// Exit if the image is already a thumbnail.
if (fileName.startsWith(THUMB_PREFIX)) {
return console.log('Already a Thumbnail.');
}
// Cloud Storage files.
const bucket = initDb.storage().bucket('your_bucket_if_non_default');
const originalFile = bucket.file(filePath);
// Create the temp directory where the storage file will be downloaded.
// But first check to see if it does not already exists
if (!fs.existsSync(tempLocalDir)) await mkdirp(tempLocalDir);
// Download original image file from bucket.
await originalFile.download({ destination: tempLocalFile });
console.log('The file has been downloaded to', tempLocalFile);
// Delete the original image file as it is not needed any more
await originalFile.delete();
console.log('Delete the original file as it is not needed any more');
// Generate a thumbnail using ImageMagick.
await spawn('convert', [ tempLocalFile, '-thumbnail',
`${THUMB_MAX_WIDTH}x${THUMB_MAX_HEIGHT}>`, tempLocalThumbFile],
{ capture: ['stdout', 'stderr'] }
);
console.log('Thumbnail created at', tempLocalThumbFile);
// Uploading the Thumbnail.
const url = await uploadLocalFileToStorage(tempLocalThumbFile, thumbFilePath,
contentType);
console.log('Thumbnail uploaded to Storage at', thumbFilePath);
// Once the image has been uploaded delete the local files to free up disk space.
fs.unlinkSync(tempLocalFile);
fs.unlinkSync(tempLocalThumbFile);
// Delete the uid folder from temp/pdf folder
fs.rmdirSync(tempLocalDir);
await admin.database().ref(`users/${uid}/uploaded_images`).update({ logoUrl: url[0] });
return { url: url[0] };
}
// Upload local file to storage
exports.uploadLocalFileToStorage = async (tempFilePath, storageFilePath,
contentType, customBucket = false) => {
let bucket = initDb.storage().bucket();
if (customBucket) bucket = initDb.storage().bucket(customBucket);
const file = bucket.file(storageFilePath);
try {
// Check if file already exists; if it does delete it
const exists = await file.exists();
if (exists[0]) await file.delete();
// Upload local file to the bucket
await bucket.upload(tempFilePath, {
destination: storageFilePath,
metadata: { cacheControl: 'public, max-age=31536000', contentType }
});
const currentDate = new Date();
const timeStamp = currentDate.getTime();
const newDate = new Date(timeStamp + 600000);
const result = await file.getSignedUrl({
action: 'read',
expires: newDate
});
return result;
} catch (e) {
throw new Error("uploadLocalFileToStorage failed: " + e);
}
};
if (firebase.auth().currentUser !== null)
console.log("user id: " + firebase.auth().currentUser.uid);
Simple way to get userid of a user.

PDF thumbnail generation for Firebase

I am developing an Android app that manages PDFs. I know that Cloudinary allows users to upload PDFs and automatically generate thumbnails for the uploaded PDF (see here). Does Firebase Storage or Cloud Firestore offer a similar feature? If not, any recommended third-party tool for this task? Thanks!
Intro
ImageMagick comes pre installed on the cloud function environment, but Ghostscript does not (at least, not right now). Ghostscript is what’s needed to generate images from PDFs.
First, download a copy of the Ghostscript executable from here (linux 64 bit APGL Release), and store it in the root of your functions directory. You may want to rename the folder/executable name for shorter path references. This will be deployed when you deploy your function(s).
Second as seen in this repo, npm install —-save https://github.com/sina-masnadi/node-gs/tarball/master . This is a wrapper you’ll need to communicate with the Ghostscript executable from within your function.
Third, you may want to look over the Ghostscript / ImageMagick docs to customize even further.
Sample cloud function
Finally, here is a function that I just got working for my functions file structure. You’ll need to write better validation and tweek it for your setup, but know that the meat of this will work.
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const fs = require('fs');
const os = require('os');
const path = require('path');
const write = require('fs-writefile-promise');
const spawn = require('child-process-promise').spawn;
const mkdirp = require('mkdirp-promise');
const gs = require('gs');
const gs_exec_path = path.join(__dirname, '../../../ghostscript/./gs-923-linux-x86_64');
try { admin.initializeApp(functions.config().firebase); } catch(e) {}
/*
Callable https function that takes a base 64 string of a pdf (MAX
10mb), uploads a thumbnail of it to firebase storage, and returns its
download url.
*/
module.exports = functions.https.onCall((data, context) => {
if (!data.b64str) { throw Error('missing base 64 string of pdf!'); }
const b64pdf = data.b64str.split(';base64,').pop();
const pg = typeof data.pg === 'number' ? data.pg : 1; //1-based
const max_wd = typeof data.max_wd === 'number' ? data.max_wd : 200;
const max_ht = typeof data.max_ht === 'number' ? data.max_ht : 200;
const st_fname = typeof data.fname === 'string' ? data.fname : 'whatever.jpg';
const bucket = admin.storage().bucket();
const tmp_dir = os.tmpdir();
const tmp_pdf = path.join(tmp_dir, 'tmp.pdf');
const tmp_png = path.join(tmp_dir, 'doesntmatter.png');
const tmp_thumb = path.join(tmp_dir, st_fname.split('/').pop();
const st_thumb = st_fname;
/* create tmp directory to write tmp files to... */
return mkdirp(tmp_dir).then(() => {
/* create a temp pdf of the base 64 pdf */
return write(tmp_pdf, b64pdf, {encoding:'base64'});
}).then(() => {
/* let ghostscript make a png of a page in the pdf */
return new Promise((resolve, reject) => {
gs().batch().nopause()
.option(`-dFirstPage=${pg}`)
.option(`-dLastPage=${pg}`)
.executablePath(gs_exec_path)
.device('png16m')
.output(tmp_png)
.input(tmp_pdf)
.exec(err => err ? reject(err) : resolve());
});
}).then(() => {
/* make a thumbnail for the png generated by ghostscript via imagemagick */
var args = [ tmp_png, '-thumbnail', `${max_wd}x${max_ht}>`, tmp_thumb ];
return spawn('convert', args, {capture: ['stdout', 'stderr']});
}).then(() => {
/* upload tmp_thumb to storage. */
return bucket.upload(tmp_thumb, { destination: st_thumb });
}).then(() => {
/* get storage url for the uploaded thumbnail */
return bucket.file(st_thumb).getSignedUrl({
action:'read',
expires: '03-01-2500'
});
}).then(result => {
/* clean up temp files and respond w/ download url */
fs.unlinkSync(tmp_pdf);
fs.unlinkSync(tmp_png);
fs.unlinkSync(tmp_thumb);
return result[0];
});
});
Sample invocation from client
var fn = firebase.functions().httpsCallable('https_fn_name');
fn({
b64str: 'base64 string for pdf here....',
pg: 2, //optional, which page do u want a thumbnail of? 1 is the first.
fname: 'path/to/file/in/storage.jpg', //optional but recommended, .png if u like
max_wd: 300, // optional, max thumbnail width
max_ht: 300 // optional, max thumbnail height
}).then(res => console.log(res));
If you cant generate a signed url...
You'll need to Add the role "Cloud Functions Service Agent" to whatever service account this function is using. You can Add the role to your service account in your cloud console.

How to take data from native storage, add to an array then add back to native storage

I am building an app using ionic/cordova. I need to be able to scan a barcode, then add a timestamp (achieved this) to the devices native storage.
I tried just adding scan data to the native storage, but found that it just overwrites existing scan data. I then tried this using an array, but again noticed if I change page, when the page loads again the array is empty, so that it replaces existing data with the new empty array.
What I "think" I need to do is -
Create an empty array
Take existing data native storage and add to this array
Add new scan data to this array
Then add the array back to native storage.
What I am currently stuck on is taking the data out of native storage and adding to an array.
My code is -
export class ScanSession {
scans: any [];
constructor(private barcodeScanner: BarcodeScanner, private
nativeStorage: NativeStorage) {
this.scans = [];
}
ScanCode() : any{
this.barcodeScanner.scan().then((barcodeData) => {
this.nativeStorage.getItem('scans')
.then(
data => console.log(data),
error => console.error(error)
);
this.scans.push(data);
let inputString = "testData";
//let ts = new Date();
if( barcodeData.text == inputString){
//this.scans.push(ts);
this.nativeStorage.setItem('scans', (JSON.stringify(this.scans)))
//(JSON.stringify(this.scans)
.then(
() => console.log('Stored item!'),
error => console.error('Error storing item', error)
);
//console.log("Success");
//console.log(this.scans);
//this.nativeStorage.getItem('scans')
//.then(
// data => console.log(data),
// error => console.error(error)
// );
} else {
console.log("Doesnt Match");
}
}, (err) => {
});
};
}
Your missing JSON.parse as your using JSON.stringify, you need to unstringify, if you get what i mean.
Change
this.nativeStorage.getItem('scans')
To
JSON.parse(this.nativeStorage.getItem('scans'))

Categories

Resources