Modified code from https://github.com/Vydia/react-native-background-upload.
iOS only background uploader which supports network requests to continue even when the app goes to background. Supports both raw and multipart uploads (file size limits might apply). In addition, provides methods to request more background time to the OS in order to continue running for longer periods of time. Alternatively,the app can just be waken up after the upload is done. Note that if both features are combined, background time/wake ups will be reduced.
Add to packages.json: "react-native-background-upload": "github:cristianoccazinsp/react-native-background-upload"
NO LONGER NEEDED. RN 0.60 will auto link. Header import is still needed if we want to listen to events
react-native link react-native-background-upload
import Upload from 'react-native-background-upload'
const options = {
url: 'https://myservice.com/path/to/post',
path: 'file://path/to/file/on/device',
method: 'POST',
type: 'raw',
headers: {
'content-type': 'application/octet-stream', // Customize content-type
'my-custom-header': 's3headervalueorwhateveryouneed'
}
}
Upload.startUpload(options).then((uploadId) => {
console.log('Upload started')
Upload.addListener('progress', uploadId, (data) => {
console.log(`Progress: ${data.progress}%`)
})
Upload.addListener('error', uploadId, (data) => {
console.log(`Error: ${data.error}%`)
})
Upload.addListener('cancelled', uploadId, (data) => {
console.log(`Cancelled!`)
})
Upload.addListener('completed', uploadId, (data) => {
// data includes responseCode: number, responseBody: Object, responseHeaders: Lower cased http headers
console.log('Completed!')
})
}).catch((err) => {
console.log('Upload error!', err)
})
Just set the type
option to multipart
and set the field
option. Example:
import RNBackgroundUpload from 'react-native-background-upload';
const resolveUpload = function(uploadId){
return new Promise((resolve, reject) => {
let l1 = RNBackgroundUpload.addListener('error', uploadId, (data) => {
reject(data);
cleanup();
});
let l2 = RNBackgroundUpload.addListener('cancelled', uploadId, (data) => {
reject(data);
cleanup();
});
let l3 = RNBackgroundUpload.addListener('completed', uploadId, (data) => {
resolve(data);
cleanup();
});
let cleanup = () => {
l1.remove(); l2.remove(); l3.remove();
}
});
}
const headers = {
'Authorization': authString,
'Content-Type': 'multipart/form-data'
}
const uploadUrl = 'some url';
const uri = 'file://path/to/file';
const formData = {
someKey: 'someValue'
};
const options = {
url: uploadUrl,
path: uri || undefined,
method: 'POST',
field: 'fileData',
headers: headers,
type: 'multipart',
parameters: formData,
customUploadId: `u-${new Date().getTime()}`
}
let res;
let responseData = null;
try{
await RNBackgroundUpload.startUpload(options);
res = await resolveUpload(options.customUploadId);
}
catch(err){
throw {
status: -1,
_error: err,
data: {
'code':null,
'detail': null,
'message': "Network error"
}
};
}
responseData = res.responseBody;
if(res.responseCode >= 400){
throw {
_error: res,
status: res.responseCode,
data: api.processError(responseData)
};
}
else{
return {
data: responseData,
status: res.responseCode,
_response: res
}
}
Note the field
property is required for multipart uploads.
TODO
If you are not using iOS background events, you can ignore this method.
Notify the OS that your app can sleep again. Call this method when your app has done all its work or is waiting for background uploads to complete. Upon calling the method, you app is suspended if it's running in the background. Native code and JS will pause execution. Apple recommends you keep background execution time at less than 5 to 10 sec.
Here are a few common situations and how to handle them:
-
Uploads are finished (completed, error or cancelled) and your app does not need to do any more work. You should call
canSuspendIfBackground
after receiving the events. -
Uploads are finished (completed, error or cancelled) and your app needs to run some computation or make a network request. You should call
canSuspendIfBackground
after the computation or network call is done. -
Uploads are finished (completed, error or cancelled) and your app needs to upload some more. You call
startUpload
a number of times and add your listeners. You should callcanSuspendIfBackground
after the uploads start but not wait for them to finish. You also need to callcanSuspendIfBackground
after you have received the events, even if some uploads are cancelled or fail:
import { addListener, startUpload, canSuspendIfBackground } from 'react-native-background-upload';
function listenForUploadCompletion(uploadId) {
return new Promise((resolve, reject) => {
addListener('error', uploadId, reject);
addListener('cancelled', uploadId, () => reject(new Error('upload cancelled')));
addListener('completed', uploadId, ({ responseCode, responseBody }) => {
if (200 <= responseCode && responseCode <= 299) {
resolve(uploadId);
} else {
reject(new Error(`Could not upload file (${responseCode}):\n${responseBody}`));
}
});
});
}
async function uploadFilesWhileInBackground(url, files) {
const uploadIds = await Promise.all(files.map(path => startUpload({ path, url })));
const didUploadPromise = Promise.all(uploadIds.map(id => listenForUploadCompletion(id)));
// suspend after event listeners are added
canSuspendIfBackground();
try {
await didUploadPromise;
// update the app UI
} catch (e) {
// handle error (show alert, present local notification, etc)
}
canSuspendIfBackground();
}
By default, iOS does not wake up your app when uploads are done while your app is not in the foreground. To receive the upload events (error
, completed
...) while your app is in the background, add the following to your AppDelegate.m
:
#import <VydiaRNFileUploader.h>
// required for background uploads
- (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier
completionHandler:(void (^)(void))completionHandler {
[[VydiaRNFileUploader sharedInstance] setBackgroundSessionCompletionHandler:completionHandler];
}
This means you can do extra work in the background, like make network calls or uploads more files! You must call canSuspendIfBackground
when you are done processing the events to sleep again. You can safely call this method when you are not in the background.
Here is a JS example:
import RNBackgroundUpload from 'react-native-background-upload';
async function uploadFile(url, fileURI) {
const uploadId = await RNBackgroundUpload.startUpload({ url, path: fileURI, method: 'POST' });
return new Promise((resolve, reject) => {
RNBackgroundUpload.addListener('error', uploadId, reject);
RNBackgroundUpload.addListener('cancelled', uploadId, () => reject(new Error('upload cancelled')));
RNBackgroundUpload.addListener('completed', uploadId, ({ responseCode, responseBody }) => {
if (200 <= responseCode && responseCode <= 299) {
resolve(uploadId);
} else {
reject(new Error(`Could not upload file (${responseCode}):\n${responseBody}`));
}
});
});
}
async function uploadManyFilesThenPOST(files) {
try {
await Promise.all(files.map(fileURI => uploadFile('https://example.com/upload', fileURI)));
const response = await fetch('https://example.com/confirmUploads', { method: 'POST' });
if (!response.ok) throw new Error('Could not confirm uploads');
} catch (error) {
const response = await fetch('https://example.com/failedUploads', { method: 'POST' });
if (!response.ok) throw new Error('Could not report failed uploads');
}
RNBackgroundUpload.canSuspendIfBackground();
}
The function uploadManyFilesThenPOST
schedules all the file uploads at once. This is recommended because the OS can then make progress on all uploads even while your app sleeps. This may take some time as iOS decides to upload when it deems appropriate, e.g. when the device is charging and connected to WiFi. Inversely, when the device is low on battery or in energy saver mode, background uploads won't make progress.
When all uploads are finished, your app may be resumed in the background to receive the events. You should call canSuspendIfBackground
as soon as possible when you are done with other actions to conserve your app "background credit". If you don't call canSuspendIfBackground
, the library will call it for your after ~45 seconds. This makes sure that your app won't be killed by the OS right away but pretty much consumes all your background credit.
Uploads tasks started when the app is in the background are discretionary; iOS will typically upload the files later when the device is charging. Upload tasks started in the foreground are not discretionary and start right away. When your app is brought to the foreground, uploads that have been postponed by the OS will continue regardless of background credit.
If your app is dead when uploads complete (force-closed by the user via the app switcher or by the OS to reclaim memory), iOS will launch it in the background. The above example does not handle this case, i.e. there will be no POST
to https://example.com/confirmUploads
. To support this you should save the uploadId
(s) to a file (e.g. via AsyncStorage
), read it when your app starts, and add the 3 listeners back.
Use this if background events are not enough and you need even more time.
import RNBackgroundUpload from 'react-native-background-upload';
// Request background time. Do not call this on app suspend/resume since it might be already too late.
let taskId = await RNBackgroundUpload.beginBackgroundTask();
// Listen to background time is about to expire events. You can do some cleanup here. You will have about 3 to 4 seconds to run code
// before the app goes to sleep
let bgExpiredRelease = RNBackgroundUpload.addListener('bgExpired', null, (data) => {
if(this.working && (!taskId || data.id == taskId)){
// do some cleanup
}
});
// run background code, do uploads, or even regular fetch requests
// tell the OS we are done. If you don't call this, your app will still be put to sleep internally
// to avoid it from being killed
if(taskId !== null){
await RNBackgroundUpload.endBackgroundTask(taskId);
}
if(bgExpiredRelease){
bgExpiredRelease.remove();
}
Thanks to https://github.com/Vydia/react-native-background-upload to provide the main code and ideas.