How can I use local copy of MongoDB Synced Realm database while completely offline? - react-native

I am using MongoDB Realm Sync on my React Native app. When I start my app online and later disconnect internet, my realm works fine. I can see my data and also I can write data which syncs with server when I go back online. But when I start my app completely offline, my app does not show any data. From what I understand, realm is suppose to read local database and return data even when the app starts from complete offline. Isn't it ? How can I access my data when I start my app offline ? Below is my code I've used to sync with server.
const config = {
schema: [sessionSchema],
sync: {
user,
partitionValue: 'Test',
},
};
try {
Realm.open(config)
.then((openedRealm) => {
if (canceled) {
openedRealm.close();
return;
}
realmRef.current = openedRealm;
const syncSessions = openedRealm.objects('session');
openedRealm.addListener('change', () => {
setSessions([...syncSessions]);
});
setSessions([...syncSessions]);
}
} catch (e) {
console.log('ERROR', e);
}

const OpenRealmBehaviorConfiguration = {
type: "openImmediately",
}
const configuration = {
schema: [UserSchema],
sync: {
user: app.currentUser,
partitionValue: "user_1",
// Add this two lines below
newRealmFileBehavior: OpenRealmBehaviorConfiguration,
existingRealmFileBehavior: OpenRealmBehaviorConfiguration,
}
}

I found an answer for similar question here: https://developer.mongodb.com/community/forums/t/open-synced-local-database-when-completely-offline/11169/2
You can do something like:
async function getRealm() {
const app = new Realm.App("your-app-id");
if (app.currentUser) {
// A user had already logged in - open the Realm synchronously
return new Realm(getConfig(app.currentUser));
}
// We don't have a user - login a user and open the realm async
const user = await app.logIn(Realm.Credentials.anonymous());
return await Realm.open(getConfig(user));
}
function getConfig(user) {
return {
sync: {
user,
partitionValue: "my-partition"
}
};
}

Related

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
}
}

Expo in app purchases implementation issues

I am struggling a little with getting in app purchases working due to lack of examples that show how all the functionality links together
In my app, I have one in app purchase which basically allows the user to unlock some restricted functionality for life.
So at the start I want to check if the user have purchased the item before. (via get history)
If they have I unlock the functionality.
On my signed APK file (android) made in android studio, I have the following issues:
-the purchase never acknowledges (although it does when run via react-native run-android)
-if you press the purchase button twice the error "already connected to app store appears"
-I don't think its getting the purchase history when running from the signed file (although I can print out the result in the console in debug mode)
I am not entirely sure when to call await InAppPurchases.connectAsync(); so this could be one potential source of issues
So this is my code in my "Inner App" . My App component is just the InnerApp wrapped in the provider component from redux. The inner app contains all the navigation stacks so the purchase listener should be global.
e.g.
export default function App (){
...more code
return(
< Provider store={store} >
< InnerApp />
</ Provider >
}
Inner app code
import * as InAppPurchases from 'expo-in-app-purchases';
export default function InnerApp (){
.....some more code
//gets purchase history
const getHistory = async ()=>{
await InAppPurchases.connectAsync();
let found=false
const { responseCode, results } = await InAppPurchases.getPurchaseHistoryAsync();
if (responseCode === InAppPurchases.IAPResponseCode.OK) {
results.forEach(result => {
if (result.acknowledged) {
found =true
// this is just saving to local storage (in case they are using the app offline)
savePurchaseHistory(true)
}else{
savePurchaseHistory(false)
}
});
}
if( found){
//updates a state in the redux store
dispatch(purchaseIAP() )
}else if(responseCode === IAPResponseCode.USER_CANCELED ){
dispatch(removeIAP() )
savePurchaseHistory(false)
}
await InAppPurchases.disconnectAsync();
}
//listens for purchases
const setUpIAP = async() => {
// Set purchase listener
await InAppPurchases.connectAsync();
await InAppPurchases.setPurchaseListener(({ responseCode, results, errorCode }) => {
// Purchase was successful
if (responseCode === InAppPurchases.IAPResponseCode.OK) {
results.forEach(purchase => {
if (!purchase.acknowledged) {
// Process transaction here and unlock content...
dispatch(purchaseIAP() )
// Then when you're done
InAppPurchases.finishTransactionAsync(purchase, false);
}
});
}
// Else find out what went wrong
if (responseCode === InAppPurchases.IAPResponseCode.USER_CANCELED) {
} else if (responseCode === InAppPurchases.IAPResponseCode.DEFERRED) {
console.log('User does not have permissions to buy but requested parental approval (iOS only)');
} else {
console.warn(`Something went wrong with the purchase. Received errorCode ${errorCode}`);
}
});
}
//The in app stuff is called when the component is mounted
useEffect(() => {
setUpIAP()
getHistory()
}, [ ] })
Further in my app I have a button that calls the following function when pressed
const unlockModes = async () => {
try {
const items = Platform.select({
ios: [
'dev.products.all_modes'
],
android: ['all_modes'],
});
await connectAsync();
const products = await InAppPurchases.getProductsAsync(items);
if (products.results.length > 0) {
await InAppPurchases.purchaseItemAsync("all_modes");
}
} catch (err) {
alert("error occured while trying to purchase: " + err);
}
};
In the end I used the React Native IAP library and I couldn't get the expo one to work.
I think the Expo Version currently might just be bust.
Setting useGooglePlayCache will resolve your problem

React Native Firebase push notification

I have a requirement to automatically send push notifications to my application when new data is inserted into firebase.
Is there any way to do so ?
Thanks !
You can use Firebase Functions as a middleware function for sending push notifications via FCM to the device If the database value is changed.
Adding an example from my FirebaseDBtoFCMFunction repo.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendPushNotification = functions.database
.ref('/users/{user_id}') // Put your path here with the params.
.onWrite(async (change, context) => {
try {
const { after } = change;
const { _data } = after;
const { deviceToken } = _data.receiver; // Always send the device token within the data entry.
if(!deviceToken) return;
const payload = {
notification: {
title: 'Notification',
body: `FCM notification triggered!`
},
data: context.params // Passing the path params along with the notification to the device. [optional]
};
return await admin.messaging().sendToDevice(deviceToken, payload);
} catch (ex) {
return console.error('Error:', ex.toString());
}
});
Inside your application add child_change (valueChanged) or child_add event for specific database location than when it changes, it will fired.
From doc.
FirebaseDatabase.DefaultInstance
.GetReference("Leaders").OrderByChild("score")
.ValueChanged += HandleValueChanged;
}
void HandleValueChanged(object sender, ValueChangedEventArgs args) {
if (args.DatabaseError != null) {
Debug.LogError(args.DatabaseError.Message);
return;
}
// Do something with the data in args.Snapshot
}
For nodejs value listener

Redux + Rest API + Realm JS in 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?

react-native-background-task Expected to run on UI thread

I am trying to sync data capture offline with an online api, I periodically run an background task using react-native-background-task to retrieve offline data and sync the data with an online api.
react-native-background-task error
// This component below triggers the background task on load
import { sync, clean } from "../../services/market/forms/tasks";
import MediaWorker from "../../services/market/forms/MediaWorker";
let worker = new MediaWorker();
BackgroundTask.define(async () => {
console.log("Life's good");
// loads data from db and sync them with the online service
await sync(worker);
// delete synced data from the db and end task
await clean();
});
export default class Onboard extends Component {
constructor(props) {
super(props);
}
async checkStatus() {
const status = await BackgroundTask.statusAsync();
if (status.available) {
// schedule the background task
BackgroundTask.schedule();
return;
}
const reason = status.unavailableReason;
if (reason === BackgroundTask.UNAVAILABLE_DENIED) {
Alert.alert(
"Denied",
'Please enable background "Background App Refresh" for this app'
);
} else if (reason === BackgroundTask.UNAVAILABLE_RESTRICTED) {
Alert.alert(
"Restricted",
"Background tasks are restricted on your device"
);
}
}
componentDidMount() {
this.checkStatus();
}
render() {
// Not important for the question
}
}
// snippet for sync function
export const sync = async worker => {
const formInstances = await loadFormInstance();
if (formInstances.length) {
// Send Textual data
const formInstancesText = filterFormInstances(formInstances, "text");
postFormTextInstance(formInstancesText);
// Get form image data and post
const formInstancesImage = filterFormInstances(formInstances, "image");
formInstancesImage.forEach(worker.send);
// Get form audio data and post
const formInstancesAudio = filterFormInstances(formInstances, "audio");
formInstancesAudio.forEach(worker.send);
// Get form video data and post
const formInstancesVideo = filterFormInstances(formInstances, "video");
formInstancesVideo.forEach(worker.send);
} else {
console.log("Nothing to sync");
BackgroundTask.finish();
}
};
// snippet for clean function
export const clean = async () => {
const formInstances = await loadFormInstance();
if (formInstances.length) {
const toBeDeleted = new Set();
formInstances.forEach(formInstance => {
const fields = formInstance.fields;
let allSynced = true;
for (let index in fields) {
const field = fields[index];
if (field.synced === false) {
allSynced = false;
break;
}
}
if (allSynced) {
toBeDeleted.add(formInstance.instanceID);
}
});
toBeDeleted.forEach(deleteFormInstance);
} else {
console.log("All tasks finished");
BackgroundTask.finish();
}
};
Adb log(Used for monitoring background activity)
Note: Background task runs successfully a lot of time, but fails occasionally with the red screen shown when the app is build in debug mode.
In release mode, the app completely crashes.
Stack trace generated by Crashlytics in production
I fixed it, it turned out react-native-background-task version wasn't compatible with my react-native version, i upgraded from 0.48.1 to 0.51.0 which requires react 16.0.0