Redux + Rest API + Realm JS in React Native - react-native

I am developing an app that controls IOT devices via REST API.
I am using Realm database inside the app to keep historical data captured by IOT sensors. I also decided to use it to persist information about user and devices. redux-persist didn't seem like the best choice since the app will eventually deal with big tables of data of historical data.
I am now building my Redux actions with Redux-Thunk and I am in doubt about what would be the ideal workflow/data flow. I am currently calling realm inside redux actions like this:
function addNew(deviceIp) {
const request = () => { return { type: c.ADD_REQUEST } };
const success = (payload) => { return { type: c.ADD_SUCCESS, payload} };
const failure = (payload) => { return { type: c.ADD_FAILURE, payload } };
return async (dispatch,getState) => {
dispatch(request());
try {
const res = await apiService.getIt(deviceIp + urls.general);
// Convert responses to date before Realm Insertion
const newDevice = {
...res.deviceInfo[0],
host: deviceIp,
manufacturingDate: new Date(res.deviceInfo[0].manufacturingDate),
lastFwUpdateDate: new Date(res.deviceInfo[0].lastFwUpdateDate),
firstLaunchDate: new Date(res.deviceInfo[0].firstLaunchDate),
lastResetDate: new Date(res.deviceInfo[0].lastResetDate)
};
const addedDevice = await realmActions.createNew('Device',newDevice);
dispatch(success(addedDevice));
}
catch (error)
{
dispatch(failure(error));
}
};
}
realmActions.createNew(collectionName, newEntry) is a method I have created to add new entries to the specified collection in Realm DB. I have one main concern about this methodology.:
It seems to me a bit of an overkill to write objects to both Realm and Redux. But Realm will be useful to persist this data in case the user closes and re-opens the app. What do you think about the approach I am taking. Would you suggest anything cleaner or smarter?

Related

Issue rendering data from firestore document in react native

I created a map for the array of exercises in my database, and then for each exercise, which is a document reference, I'm getting the data from that document reference and setting it to a state. This is resulting in an infinite loop right now.
If I remove the setExerciseData line, the console logs the exercise object's data that I'm expecting to see. I'm really not sure what the correct way to render the name field from this data is.
{workout.exercises.map((exercise) => {
async function getData(exercise) {
getDoc(exercise).then((doc) => {
console.log(doc.data());
setExerciseData(doc.data());
});
}
getData(exercise);
return (
<Text>{exerciseData.name}</Text>
)
})}
You need to use useEffect() and setState() to be able to render your data. Also, Firebase Firestore is Asynchronous in nature, as a general note, no one should be trying to convert an Async operation into a sync operation as this will cause problems. You need to use an Asynchronous function to fetch data from Firestore. See sample code below:
const getExerciseData = async () => {
const docRef = doc(db, "<collection-name>", '<document-id>')
const docSnap = await getDoc(docRef)
if (docSnap.exists()) {
// console.log("Document data:", docSnap.data())
setExerciseData(docSnap.data())
} else {
// doc.data() will be undefined in this case
console.log("No such document!")
}
}
useEffect(() => {
getExerciseData()
}, [])
return (
<Text>{exerciseData.name}</Text>
)
You could also check my answer on this thread for more use-cases.

react-native (Expo) upload file on background

In my Expo (react-native) application, I want to do the upload task even if the application is in the background or killed.
the upload should be done to firebase storage, so we don't have a REST API.
checked out the Expo task manager library, but I could not figure out how it should be done. is it even possible to achieve this goal with Expo? is the TaskManager the correct package for this task?
there are only some Expo packages that could be registered as a task (e.g. backgroundFetch), and it is not possible to register a custom function (in this case uploadFile method).
I even got more confused as we should enable add UIBackgroundModes key for iOS but it only has audio,location,voip,external-accessory,bluetooth-central,bluetooth-peripheral,fetch,remote-notification,processing as possible values.
I would appreciate it if you can at least guide me on where to start or what to search for, to be able to upload the file even if the app is in the background is killed/terminated.
import { getStorage, ref, uploadBytes } from "firebase/storage";
const storage = getStorage();
const storageRef = ref(storage, 'videos');
const uploadFile = async (file)=>{
// the file is Blob object
await uploadBytes(storageRef, file);
}
I have already reviewed react-native-background-fetch, react-native-background-upload, react-native-background-job . upload should eject Expo, job does not support iOS, and fetch is a fetching task designed for doing task in intervals.
if there is a way to use mentioned libraries for my purpose, please guide me :)
to my understanding, the Firebase Cloud JSON API does not accept files, does it ? if so please give me an example. If I can make storage json API work with file upload, then I can use Expo asyncUpload probably without ejecting.
I have done something similar like you want, you can use expo-task-manager and expo-background-fetch. Here is the code as I used it. I Hope this would be useful for you.
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';
const BACKGROUND_FETCH_TASK = 'background-fetch';
const [isRegistered, setIsRegistered] = useState(false);
const [status, setStatus] = useState(null);
//Valor para que se ejecute en IOS
BackgroundFetch.setMinimumIntervalAsync(60 * 15);
// Define the task to execute
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
const now = Date.now();
console.log(`Got background fetch call at date: ${new Date(now).toISOString()}`);
// Your function or instructions you want
return BackgroundFetch.Result.NewData;
});
// Register the task in BACKGROUND_FETCH_TASK
async function registerBackgroundFetchAsync() {
return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
minimumInterval: 60 * 15, // 1 minutes
stopOnTerminate: false, // android only,
startOnBoot: true, // android only
});
}
// Task Status
const checkStatusAsync = async () => {
const status = await BackgroundFetch.getStatusAsync();
const isRegistered = await TaskManager.isTaskRegisteredAsync(
BACKGROUND_FETCH_TASK
);
setStatus(status);
setIsRegistered(isRegistered);
};
// Check if the task is already register
const toggleFetchTask = async () => {
if (isRegistered) {
console.log('Task ready');
} else {
await registerBackgroundFetchAsync();
console.log('Task registered');
}
checkStatusAsync();
};
useEffect(() => {
toggleFetchTask();
}, []);
Hope this isn't too late to be helpful.
I've been dealing with a variety of expo <-> firebase storage integrations recently, and here's some info that might be helpful.
First, I'd recommend not using the uploadBytes / uploadBytesResumable methods from Firebase. This Thread has a long ongoing discussion about it, but basically it's broken in v9. Maybe in the future the Firebase team will solve the issues, but it's pretty broken with Expo right now.
Instead, I'd recommend either going down the route of writing a small Firebase function that either gives a signed-upload-url or handles the upload itself.
Basically, if you can get storage uploads to work via an http endpoint, you can get any kind of upload mechanism working. (e.g. the FileSystem.uploadAsync() method you're probably looking for here, like #brentvatne pointed out, or fetch, or axios. I'll show a basic wiring at the end).
Server Side
Option 1: Signed URL Upload.
Basically, have a small firebase function that returns a signed url. Your app calls a cloud function like /get-signed-upload-url , which returns the url, which you then use. Check out: https://cloud.google.com/storage/docs/access-control/signed-urls for how you'd go about this.
This might work well for your use case. It can be configured just like any httpsCallable function, so it's not much work to set up, compared to option 2.
However, this doesn't work for the firebase storage / functions emulator! For this reason, I don't use this method, because I like to intensively use the emulators, and they only offer a subset of all the functionalities.
Option 2: Upload the file entirely through a function
This is a little hairier, but gives you a lot more fidelity over your uploads, and will work on an emulator! I like this too because it allows doing upload process within the endpoint execution, instead of as a side effect.
For example, you can have a photo-upload endpoint generate thumbnails, and if the endpoint 201's, then you're good! Rather than the traditional Firebase approach of having a listener to cloud storage which would generate thumbnails as a side effect, which then has all kinds of bad race conditions (checking for processing completion via exponentiational backoff? Gross!)
Here are three resources I'd recommend to go about this approach:
https://cloud.google.com/functions/docs/writing/http#multipart_data
https://github.com/firebase/firebase-js-sdk/issues/5848
https://github.com/mscdex/busboy
Basically, if you can make a Firebase cloud endpoint that accepts a File within formdata, you can have busboy parse it, and then you can do anything you want with it... like upload it to Cloud Storage!
an outline of this:
import * as functions from "firebase-functions";
import * as busboy from "busboy";
import * as os from "os";
import * as path from "path";
import * as fs from "fs";
type FieldMap = {
[fieldKey: string]: string;
};
type Upload = {
filepath: string;
mimeType: string;
};
type UploadMap = {
[fileName: string]: Upload;
};
const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
export const uploadPhoto = functions.https.onRequest(async (req, res) => {
verifyRequest(req); // Verify parameters, auth, etc. Better yet, use a middleware system for this like express.
// This object will accumulate all the fields, keyed by their name
const fields: FieldMap = {};
// This object will accumulate all the uploaded files, keyed by their name.
const uploads: UploadMap = {};
// This will accumulator errors during the busboy process, allowing us to end early.
const errors: string[] = [];
const tmpdir = os.tmpdir();
const fileWrites: Promise<unknown>[] = [];
function cleanup() {
Object.entries(uploads).forEach(([filename, { filepath }]) => {
console.log(`unlinking: ${filename} from ${path}`);
fs.unlinkSync(filepath);
});
}
const bb = busboy({
headers: req.headers,
limits: {
files: 1,
fields: 1,
fileSize: MAX_FILE_SIZE,
},
});
bb.on("file", (name, file, info) => {
verifyFile(name, file, info); // Verify your mimeType / filename, etc.
file.on("limit", () => {
console.log("too big of file!");
});
const { filename, mimeType } = info;
// Note: os.tmpdir() points to an in-memory file system on GCF
// Thus, any files in it must fit in the instance's memory.
console.log(`Processed file ${filename}`);
const filepath = path.join(tmpdir, filename);
uploads[filename] = {
filepath,
mimeType,
};
const writeStream = fs.createWriteStream(filepath);
file.pipe(writeStream);
// File was processed by Busboy; wait for it to be written.
// Note: GCF may not persist saved files across invocations.
// Persistent files must be kept in other locations
// (such as Cloud Storage buckets).
const promise = new Promise((resolve, reject) => {
file.on("end", () => {
writeStream.end();
});
writeStream.on("finish", resolve);
writeStream.on("error", reject);
});
fileWrites.push(promise);
});
bb.on("close", async () => {
await Promise.all(fileWrites);
// Fail if errors:
if (errors.length > 0) {
functions.logger.error("Upload failed", errors);
res.status(400).send(errors.join());
} else {
try {
const upload = Object.values(uploads)[0];
if (!upload) {
functions.logger.debug("No upload found");
res.status(400).send("No file uploaded");
return;
}
const { uploadId } = await processUpload(upload, userId);
cleanup();
res.status(201).send({
uploadId,
});
} catch (error) {
cleanup();
functions.logger.error("Error processing file", error);
res.status(500).send("Error processing file");
}
}
});
bb.end(req.rawBody);
});
Then, that processUpload function can do anything you want with the file, like upload it to cloud storage:
async function processUpload({ filepath, mimeType }: Upload, userId: string) {
const fileId = uuidv4();
const bucket = admin.storage().bucket();
await bucket.upload(filepath, {
destination: `users/${userId}/${fileId}`,
{
contentType: mimeType,
},
});
return { fileId };
}
Mobile Side
Then, on the mobile side, you can interact with it like this:
async function uploadFile(uri: string) {
function getFunctionsUrl(): string {
if (USE_EMULATOR) {
const origin =
Constants?.manifest?.debuggerHost?.split(":").shift() || "localhost";
const functionsPort = 5001;
const functionsHost = `http://${origin}:${functionsPort}/{PROJECT_NAME}/${PROJECT_LOCATION}`;
return functionsHost;
} else {
return `https://{PROJECT_LOCATION}-{PROJECT_NAME}.cloudfunctions.net`;
}
}
// The url of your endpoint. Make this as smart as you want.
const url = `${getFunctionsUrl()}/uploadPhoto`;
await FileSystem.uploadAsync(uploadUrl, uri, {
httpMethod: "POST",
uploadType: FileSystem.FileSystemUploadType.MULTIPART,
fieldName: "file", // Important! make sure this matches however you want bussboy to validate the "name" field on file.
mimeType,
headers: {
"content-type": "multipart/form-data",
Authorization: `${idToken}`,
},
});
});
TLDR
Wrap Cloud Storage in your own endpoint, treat it like a normal http upload, everything plays nice.

store locally either in asyncStorage or in a state

I use redux for state management. Now I have a json object of 1000 objects that I need to store locally either in asyncStorage or in a state to use it in different part of my application. What is the best way to handle this json data
const initialState = {
loading: false,
dataAppDB:{}
}
try {
await fetch(url)
.then(response => response.json())
.then(data => {
thunkAPI.dispatch(saveDataApp(data));
let data1 = JSON.stringify(data);
AsyncStorage.setItem('dataAppDB', data1);
});
} catch (e) {
}
}
}
Is it normal to store these 1000 json objects in for example in state : dataAppDB = {}
and to be able to use it in my application
That will depend on one simple question. Do you need the data to persist after the app was closed?
If yes, then using AsyncStorage is a valid approach. Of course, you can also have a proper backend with a database, but AsyncStorage will do the job.
If not, then what you want is to use a context, for this you have different options. A common one is the hook useContext, but redux is also a very good alternative.

Which storage option is best for my react native application?

I'm building a React Native application that require to persist some values. But I don't want to trouble setting up backend and cloud databases for simple and small data. The dataset I want to persist is like 81 array items that has boolean values in each.
[true, true, false .... ]
OR
[{id: int, isCompleted: bool}, {id: int, isCompleted: bool},...]
Since I'm a React web developer and if it is a web application, I would be using local storage. But in React Native, what kind of storage that I can use that's local to the device only?
Maybe the best solution in your case is to use AsyncStorage library. Is simple to use and can be stored directly on the device.
import { AsyncStorage } from "react-native";
_store = async () => {
try {
await AsyncStorage.setItem('array', yourArray);
} catch (error) {
// Error
}
}
_rertrive = async () => {
try {
const value = await AsyncStorage.getItem('array');
if (value !== null) {
console.log(value);
}
} catch (error) {
// Error
}
}

using mock data while on development with vue + vuex

I'm working on a Vue app which also uses vuex.
Everything is setup ad working correctly as expected but i'd like to improve it so that I can work on it without actually calling the API endpoints (mainly to avoid rate limit).
I created a mock folder and placed some file in there.
How do I manage to use those mock in development, and the real api endpoint on the build on production withouth making a mess in my code ?
I created a repo with as less as possible.
It includes vue + vuex, a single smart component in charge of reading from the store, and a dumb component do display it.
In poor words, I'm looking for a way to do change this:
const actions = {
async fetchTodos({ commit }) {
let response;
if (process.env.NODE_ENV === "development") {
response = { data: todos };
} else {
response = await axios.get("https://jsonplaceholder.typicode.com/todos");
}
commit("setTodos", response.data);
}
};
with something which would be easier to maintain and wouldn't increase the bundle size.
I thought about mocking the whole action object, which seemed to be ok, but how do i avoid to bundle my mock files at that point?
How do you manage your front end environment to avoid this kind of problem?
What I did is encapsulate the whole API in another class/object. That single point of entry then switches between the mock and real api:
// store.js
const api = require('./api');
const actions = {
async fetchTodos({ commit }) {
// you can use api.getTodos() instead or another naming convention
const response = await api.get('todos');
commit("setTodos", response.data);
},
};
// api.js
const realapi = require('./realapi');
const mockapi = require('./mockapi');
module.exports = process.env.NODE_ENV === 'production' ? realapi : mockapi;
// mockapi/index.js
const todos = loadTodos();
module.exports = {
async get(path) {
switch (path) {
case 'todos':
return { data: todos };
// etc.
}
}
};
// realapi/index.js
const root = 'https://jsonplaceholder.typicode.com/';
module.exports = {
get(path) {
return axios.get(root + path);
}
};
Builders like Webpack can optimize the build and remove the whole mock api part in production builds based on the environment.