React Native AsyncStorage | Row too big to fit into CursorWindow - sqlite

I'm using AsyncStorage in ReactNative to store some data (large size >2MB) on device, and then read it with the following code
try {
const value = await AsyncStorage.getItem('date_stored_copy');
} catch (e) {
console.log(e);
}
I'm getting the following error:
Row too big to fit into CursorWindow requiredPos=0, totalRows=1...
Is there any way to increase CursorWindow size, or another alternative to AsyncStorage ?

An alternative solution would be to split the data into chunks and then writing it.
I write a wrapper that uses AsyncStorage that does exactly that: https://gist.github.com/bureyburey/2345dfa88a31e00a514479be37848d42
Be aware that it was originally written for using with apollo-cache-persist (a persistence lib for apollo-client).
And since graphql store the data in a very flat structure this solution works pretty well out of the box.
For your case, if your stored object looks like this:
{
data: { a lot of data here }
}
Then it wouldn't matter much and the wrapper won't work
But if your object looks like this:
{
someData: { partial data },
someMoreData: { more partial data },
....
}
Then in theory it should work.
Full disclosure: i haven't tested it thoroughly yet and only used it with apollo-cache-persist

I ran into this problem too, here is how I solved this issue :
Basic description of the algorithm :
The "key" holds the number of parts your data will be divided by. (Example : key is "MyElementToStore", its value is 7 for the number of parts your data needs to be split by to fit each part in a row of the AsyncStorage)
Each part will then be stored as an individual row in the AsyncStorage by having the name of the key followed by the index of the part. (Example : ["MyElementToStore0", "MyElementToStore1", ...]
Retrieving data works the other way around, each row is retrieved and aggregated to the result to return
Final note for clearing the store, it's important to remove each part before removing the key (use the last function "clearStore" to make sure you release memory correctly)
AsyncStorage documentation
import AsyncStorage from "#react-native-async-storage/async-storage";
const getStore = async (key) =>
{
try
{
let store = "";
let numberOfParts = await AsyncStorage.getItem(key);
if(typeof(numberOfParts) === 'undefined' || numberOfParts === null)
return null;
else
numberOfParts = parseInt(numberOfParts);
for (let i = 0; i < numberOfParts; i++) { store += await AsyncStorage.getItem(key + i); }
if(store === "")
return null;
return JSON.parse(store);
}
catch (error)
{
console.log("Could not get [" + key + "] from store.");
console.log(error);
return null;
}
};
const saveStore = async (key, data) =>
{
try
{
const store = JSON.stringify(data).match(/.{1,1000000}/g);
store.forEach((part, index) => { AsyncStorage.setItem((key + index), part); });
AsyncStorage.setItem(key, ("" + store.length));
}
catch (error)
{
console.log("Could not save store : ");
console.log(error.message);
}
};
const clearStore = async (key) =>
{
try
{
console.log("Clearing store for [" + key + "]");
let numberOfParts = await AsyncStorage.getItem(key);
if(typeof(numberOfParts) !== 'undefined' && numberOfParts !== null)
{
numberOfParts = parseInt(numberOfParts);
for (let i = 0; i < numberOfParts; i++) { AsyncStorage.removeItem(key + i); }
AsyncStorage.removeItem(key);
}
}
catch (error)
{
console.log("Could not clear store : ");
console.log(error.message);
}
};

I found another alternative mentioned here
Just install react-native-fs-store
npm i react-native-fs react-native-fs-store
react-native link react-native-fs
And use it like this:
import Store from "react-native-fs-store";
const AsyncStorage = new Store('store1');
it has has exactly same API as that of AsyncStorage, so no code changes are required
** Please notice that react-native-fs-store is slower than AsyncStorage, as each operation is synced to file. So you may notice lag (unresponsive screen) while reading/writing data

android/app/src/main/java/com/tamotam/mainApp/MainApplication.java
import android.database.CursorWindow;
import java.lang.reflect.Field;
...
#Override
public void onCreate() {
...
try {
Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
field.setAccessible(true);
field.set(null, 100 * 1024 * 1024); // the 100MB is the new size
} catch (Exception e) {
e.printStackTrace();
}
}
Fixed the issue for me, remember to include the 2 imports!
As per https://github.com/andpor/react-native-sqlite-storage/issues/364#issuecomment-665800433 there might be an addition check if (DEBUG_MODE)... in some solutions, but it caused Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0. in my case.

Related

Firestore query "onSnapshot" called at the same time does not work (

I created an app with Ionic and Firestore that features live chat and I'm having a problem with it.
The conversation is loaded with the method:
refUneConversationMyUserCol.ref.orderBy('date', 'desc').limit(20).get()
To this is added an "onSnapshot" request to retrieve the last message sent live
this.unsubscribeDataUneConversation = refUneConversationMyUserCol.ref.orderBy('date', 'desc').limit(1).onSnapshot(result => {
console.log(result.docs[0].data());
if (this.isCalledBySnapshot === false) {
this.isCalledBySnapshot = true;
} else if (result.docs[0].data().expediteur !== this.authentificationService.uidUserActif) {
const data = result.docs[0].data();
const id = result.docs[0].id;
this.dataUneConversation.push({ id, ...data } as UneConversation);
}
});
It will work perfectly however, when I send a message at the same time (with 2 different accounts talking to each other), I encounter a problem, the onSnapshot is triggered only once and I only receive one message.
I specify that the two messages are sent well in the database, they are only not displayed both during the live session
Do you have any idea why?
Thank you
(Here is the whole method)
async getDataUneConversation(idI: string) {
if (this.loadedDataUneConversation !== idI) {
/* ANCHOR Msg en direct */
this.isCalledBySnapshot = false;
if (this.unsubscribeDataUneConversation) {
await this.unsubscribeDataUneConversation();
}
const refUneConversationMyUserCol = this.afs.collection<User>('users').doc<User>(this.authentificationService.uidUserActif).collection<Conversations>('conversations');
const result = await refUneConversationMyUserCol.ref.orderBy('date', 'desc').limit(20).get();
/* ANCHOR Msg en direct */
this.unsubscribeDataUneConversation = refUneConversationMyUserCol.ref.orderBy('date', 'desc').limit(1).onSnapshot(result => {
console.log(result.docs[0].data());
if (this.isCalledBySnapshot === false) {
this.isCalledBySnapshot = true;
} else if (result.docs[0].data().expediteur !== this.authentificationService.uidUserActif) {
const data = result.docs[0].data();
const id = result.docs[0].id;
this.dataUneConversation.push({ id, ...data } as UneConversation);
}
});
/* ANCHOR Msg en brut */
if (result.docs.length < 20) {
this.infiniteLastUneConversationMax = true;
} else {
this.infiniteLastUneConversationMax = false;
}
this.infiniteLastUneConversation = result.docs[result.docs.length - 1];
this.dataUneConversation = result.docs.map(doc => {
const data = doc.data();
const id = doc.id;
return { id, ...data } as UneConversation;
});
this.dataUneConversation.reverse();
this.loadedDataUneConversation = idI;
}
}
EDIT for working :
this.unsubscribeDataUneConversation = refUneConversationMyUserCol.ref.orderBy('date', 'asc').startAfter(this.dataUneConversation[this.dataUneConversation.length
- 1].date).onSnapshot(result => {
result.docs.forEach(element => {
const data = element.data();
const id = element.id;
if (!this.dataUneConversation.some(e => e.id === element.id)) {
this.dataUneConversation.push({ id, ...data } as UneConversation);
}
});
});
You're limiting live messages to only one last message. In a chat app, you want to listen to all new messages. So the issue is probably in your .limit(1) clause.
But if you do that, I understand that you'll get the whole conversation, with all messages, since the conversation started.
My approach would be like this:
Get the date of the last message from your refUneConversationMyUserCol... conversation loader.
When you do the onSnapshot() to get the last message, do not limit to 1 message, instead, start at a date after the date of the last loaded message.
Since you're ordering by date anyway, this will be an easy fix. Look into "Adding a cursor to your query".
Basically, you'll be saying to Firestore: give me LIVE new messages but start at NOW - and even if there are many messages posted at the same time, you'll get them all, since you're not limiting to 1.
Feel free to ask if this is not clear enough.

Question about storing data in arrays in firestore

So right now I am currently storing data with a number after it like EmailSubject1, EmailSubject2, etc which obviously is a stupid way of doing things.
I would instead like to store these in arrays like EmailSubject[1], EmailSubject[2] etc.
I spent some time looking over the firebase documentation and searching online but was struggling to find a solution and I am unfamilar in storing things in firebase this way personally.
let z = 0;
for (z = 0; z < 20; z++) {
db.collection("Users")
.doc("6IzsLbD4r4R5RXdGB5BQy6xq8Dc2")
.set(
{
["EmailSubject" + z]: mail.subject,
["EmailBody" + z]: mail.text,
["EmailFrom" + z]: mail.from,
["EmailDate" + z]: newDate,
},
{ merge: true }
)
.then(() => {
console.log("Doc successful");
return null;
})
.catch((error) => {
console.error("Error writing doc", error);
});
}
Here is my code, any help would be much appreciated =]
If I correctly understand your question, you can use arrayUnion() to add elements to the different arrays, but you should note that this method will only add elements that are not already present.
So if you are sure that you only add new data, you could do as follows:
var promises = [];
var docRef = db
.collection('Users')
.doc('6IzsLbD4r4R5RXdGB5BQy6xq8Dc2');
for (z = 0; z < 20; z++) {
promises.push(
docRef.set(
{
EmailSubject: firebase.firestore.FieldValue.arrayUnion(
mail.subject + z
),
EmailBody: firebase.firestore.FieldValue.arrayUnion('text' + z),
//....
},
{ merge: true }
)
);
}
Promise.all(promises);
If, on the opposite, there are values that are similar in one of the Arrays, you would need to read the Array in your client, push the new value and write back the entire Array.
Ex.: If your EmailFrom array is ['john#gmail.com', 'jack#gmail.com', 'john#gmail.com'], arrayUnion() will not work.

correct way to fire out a fail result in page reponse event listener in codeceptJS

I wrote a helper methods to add a network response listener over Puppeteer page instance. the code looks like this
let Helper = codecept_helper;
class CheckHelper extends Helper {
async listenRequest(listener)
{
const helper = this.helpers['Puppeteer'];
await helper.page.setRequestInterception(true);
helper.page.on("request",listener);
return helper._waitForAction();
}
async listenResponse(listener)
{
const helper = this.helpers['Puppeteer'];
helper.page.on("response",listener);
return helper._waitForAction();
}
}
module.exports = CheckHelper;
then in the test script
let self=this;
I.listenResponse((response)=>{
if(response.url().match(/github.*\.js/) && response.headers()['content-length']>1000) {
//codeceptjs.event.emit(codeceptjs.event.test.failed, self, 'js file is too big!');
//codeceptjs.recorder.throw('js file is too big!')
//codeceptjs.recorder.stop();
//throw new Error('js file is too big!')
}
})
I.amOnPage("https://www.github.com");
i first add response listener, then i goto "github", when some js file size is too big,i will throw out an error,in order too check content size is correctly.
however, even i throw error out (like the comments codes did), the main test flow just not stop, how do i do is the right way?
well,i found a solution later
i recorded all the page response into a custom object in the page instance.
later i wrote a help methods to check whole records.
//in helper.js
startRecordResponse() {
const helper = this.helpers['Puppeteer'];
helper.page.on("response", (res) => {
//record all response instance into savedResponse object inside page, we'll use it later
helper.page.savedResponse = helper.page.savedResponse || {};
helper.page.savedResponse[res.url()] = res;
});
return helper._waitForAction();
}
checkFileIsTooBig(filter, sizeLimit) {
const helper = this.helpers['Puppeteer'];
//use the data recorded in savedResponse object
Object.keys(helper.page.savedResponse).forEach((url) => {
var res = helper.page.savedResponse[url];
if (((filter instanceof RegExp && filter.test(url)) || (typeof filter == "string" && url.indexOf(filter) != -1)) && res.headers()['content-length'] > sizeLimit) {
throw new Error(`file ${url} is too big,${res.headers()['content-length']} > ${sizeLimit}`)
}
})
return helper._waitForAction();
}
then in test file
Before((I) => {
I.startRecordResponse();
I.amOnPage("https://www.github.com");
});
Scenario('github_test', (I) => {
//check a js file contain github is less than 100 bytes
I.checkFileIsTooBig(/github.*\.js/,100);
}
);

DART async/await not waiting

So I've been working on experimenting with DART (whereby my core languages are C++, and embedded C derivates). Hence my code is probably not pretty as I'm more of a procedural programmer, but I'm getting by and learning... I've been struggling around Futures pertaining to await sync, and basically, I simply can't get DART to WAIT. The following code establishes a socket connection to a small embedded device and extracts info. That all works, but notice the order of operations SHOULD be main() gets some info from the console, then should call the method cardStatus to run off and get the info from the embedded device via the socket connection. This is where the await should occur. When the Future is returned, it should go off to the printstuff() method. I've added print statements that should go in order and read:
This should print 1st
This should print 2nd
This should print 3rd
Instead since the wait is not occurring on the cardstatus call (which is time consuming), I get:
This should print 1st
This should print 3rd
This should print 2nd
I've followed another thread on the use of async, and seem to be at least following one solid way of using this Other thread (I tried a .then with a completer with a similar result, so there is something core I feel I'm missing).. but I've been stuck on this for a week.
Code below, along with the console output.
import 'dart:io';
import 'dart:async' show Future;
const String STATUS = "#111111;";
String defaultIP = "10.1.14.202";
int defaultConfigPort = 5111;
int defaultControlPort = 6722;
var card = new Map();
getInput(String defaults) {
String takenin = stdin.readLineSync();
if (takenin == '') takenin = defaults;
return takenin;
}
Future main() async {
stdout.write('What is the IP address of the card ($defaultIP): ');
String ipaddress = getInput(defaultIP);
defaultIP = ipaddress;
print ("This should print 1st");
stdout.writeln("Connecting to $defaultIP");
await cardStatus(defaultIP, defaultConfigPort, STATUS, card);
printstuff();
}
printstuff() {
stdout.writeln(card['subnet']);
print ("This should print 3rd");
}
Future cardStatus(String ip, int port, String message, Map card) {
return new Future.delayed(Duration.ZERO, () {
Socket.connect(ip, port).then((socket) {
print('Connected to: '
'${socket.remoteAddress.address}:${socket.remotePort}');
socket.listen((data) {
print(new String.fromCharCodes(data).trim());
List str1 = (new String.fromCharCodes(data).trim().split(','));
print(str1);
print ("This should print 2nd");
//var card = new Map();
card['ip'] = str1[0];
card['subnet'] = str1[1];
card['gateway'] = str1[2];
card['unknown'] = str1[3];
card['persist'] = str1[4] == 'true';
card['build'] = str1[5];
card['serial'] = str1[6].substring(0, 14);
card['cloudpassword'] = str1[6].substring(14, 20);
card['DNS'] = str1[7];
card['cloudhost'] = str1[8];
card['cloudenabled'] = str1[9] == 'true';
print(card['ip']);
},
onDone: () {
print("Done");
socket.destroy();
});
//Send the request
socket.write(message);
});
});
}
and this is the current console output. notice the null shouldn't be a null if the cardStatus would have completed it would be printed str1.
What is the IP address of the card (10.1.14.202):
This should print 1st
Connecting to 10.1.14.202
null
This should print 3rd
Connected to: 10.1.14.202:5111
>10.1.14.202,255.255.255.0,10.1.14.1,,0,435,F44900A60040F8000000,192.168.1.1,connect.tutuuu.com,0;
[>10.1.14.202, 255.255.255.0, 10.1.14.1, , 0, 435, F44900A60040F8000000, 192.168.1.1, connect.tutuuu.com, 0;]
This should print 2nd
10.1.14.202
Done
Process finished with exit code 0
Thanks for all the help!
You are missing return before Socket.connect. As it stands now your code just starts connecting but never awaits it through future. I would highly recommend using as much as possible the new await / async syntax.
Here is a running example that does get google homepage:
import 'dart:io';
import 'dart:async' show Future;
Future main() async {
print("This should print 1st");
await cardStatus('www.google.com', 80, 'GET /\nHTTP 1.1\n\n');
printstuff();
}
printstuff() {
print("This should print 3rd");
}
Future cardStatus(String ip, int port, String message) {
return new Future.delayed(Duration.ZERO, () {
return Socket.connect(ip, port).then((socket) {
print('Connected to: '
'${socket.remoteAddress.address}:${socket.remotePort}');
socket.listen((data) {
List str1 = (new String.fromCharCodes(data).trim().split(','));
print(str1.first);
print("This should print 2nd");
}, onDone: () {
print("Done");
socket.destroy();
}, onError: (e) {
print("Error while listening: $e");
});
socket.write(message);
});
});
}
Below slightly redacted version that uses awaits, and try / catch to handle errors:
import 'dart:io';
import 'dart:async' show Future;
Future main() async {
print("This should print 1st");
await cardStatus('www.google.com', 80, 'GET /\nHTTP 1.1\n\n');
print("This should print 3rd");
}
Future<String> cardStatus(String ip, int port, String message) async {
var socket = await Socket.connect(ip, port);
print('Connected to: '
'${socket.remoteAddress.address}:${socket.remotePort}');
socket.write(message);
print("Sent request");
try {
var response = await socket.fold(
'',
(String acc, List<int> data) =>
acc + new String.fromCharCodes(data).trim());
print("Received response: ${response.substring(0, 10)}");
return response;
} finally {
socket.close();
}
}
I know it was answered but the question is great and I struggled myself with the concept so here is another element that got me to understand. in the dartpad (https://dartpad.dartlang.org/) try this (comments are in the code):
import 'dart:async';
//Just creating a duration to use later
Duration duration = new Duration(milliseconds: 500);
void main() {
//This is what tricked me, printStuff is async so running in parallel processing by default
//There is no need to call the function in a certain way (like a go xxx for a goroutine)
//there is an await in the function so it will wait inside the function only
//i.e. printStuff('a') starts then printStuff('b') starts straight away...already in prallel processing
//Run it and check the output
printStuff('a');
printStuff('b');
//Basically the await is in the function so printStuff is still returning a Future
//i.e. printStuff('a') starts but doesn't wait to complete to start printStuff('b')
}
Future<void> printStuff(String id) async {
for(int i = 0; i <= 5; ++i) {
//this await is waiting for the command to complete to move to the next iteration...
//the i iterations are done one after the other
await new Future.delayed(duration, () {
print(id + i.toString());
});
}
}
Then try this:
import 'dart:async';
Duration duration = new Duration(milliseconds: 500);
//becuase I use await in main now, I must make it return a future and be async
Future main() async {
//to make it happen one after the other, you need await at a call function level
await printStuff('a');
await printStuff('b');
//Basically this says complete printStuff('a'), then start printStuff('b')...
//and yes technically one doesn't need the second await becuase there is nothing after
}
Future<void> printStuff(String id) async {
for(int i = 0; i <= 5; ++i) {
await new Future.delayed(duration, () {
print(id + i.toString());
});
}
}
So my personal misunderstanding was that an async function is called in parallel straight away and an await in a function waits for real but in the function itself, with no impact on other parallel processing happening.

How to load multiple data via service and wait for it in Angular2

I use Ionic 2 with Angular 2 in my project. In the root component you can click a "Add" button to add a new Report via a complex form and a lot of preprovided data (there are some selects that are feeded with data fetched from sqlite database)
Now in my "CreateReportComponent" i have the following constructor to load the data and assign it to local array variable:
selectEmployeeOptions: Employee[];
constructor(private dbService: DatabaseService) {
dbService.getAllEmployees().then(employees => {
this.selectEmployeeOptions = employees;
});
// load more data like tasks etc.
});
But when I want to modify this data in my component, the array is empty. I tried to do it in ngOnInit() but this seems to be to early as well.
I want to to something like this, before the component gets displayed:
dbService.getAllEmployees().then(employees => {
this.selectEmployeeOptions = employees;
// modify data
this.selectEmployeeTitleOptions = employees.map((item) => {
return item.title;
});
console.log(JSON.stringify(this.selectEmployeeTitleOptions)) // --> empty
});
But selectEmployeeTitleOptions is empty...
The function in the databaseService looks like this:
getAllEmployees(): Promise<Emplyoee[]> {
let query = "SELECT * FROM employees";
let employeeList = [];
this.database.executeSql(query, []).then((data) => {
if(data.rows.length > 0) {
let e = new Employee();
e.id = data.rows.item(i).id;
e.firstname = data.rows.item(i).firstname;
e.lastname = data.rows.item(i).lastname;
employeeList.push(e);
}
}, (error) => {
// handle error
});
return Promise.resolve(employeeList);
}
I read that there is the Resolve pattern (https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html) But I need to make multiple calls and not only for contacts as in the example.
So the question: How to wait for multiple calls to database?
i think something go wrong here
getAllEmployees(): Promise<Emplyoee[]> {
let query = "SELECT * FROM employees";
let employeeList = [];
this.database.executeSql(query, []).then((data) => {
if(data.rows.length > 0) {
let e = new Employee();
e.id = data.rows.item(i).id;
e.firstname = data.rows.item(i).firstname;
e.lastname = data.rows.item(i).lastname;
employeeList.push(e);
}
}, (error) => {
// handle error
});
return Promise.resolve(employeeList);
}
first return Promise.resolve(employeeList); will return empty array, because it is async process.
you need loop through data.rows, then format return data like this.
getAllEmployees(): Promise<Employee[]> {
let query = "SELECT * FROM employees";
return this.database.executeSql(query, []).then((data) => {
let arr = [];
for(let i = ; i < data.rows.length; ++i) {
let emp = data.rows.item(i);
let e = new Employee();
e.id = emp.id;
e.firstname = emp.firstname;
e.lastname = emp.lastname;
arr.push(e);
}
return arr;
});
}
note that .then() return a promise object.
What you are looking for is forkJoin method that returns Observable that you should switch to instead of using Promises, for reference about why you should do this check here.
Short information about fork join from its GitHub page:
Runs all observable sequences in parallel and collect their last elements.
This way you can safely make parallel requests to your API.
For more information regarding forkJoin go here.
Additionally you should call services using ngOnInit as you mentioned before. For more information about Angular 2 lifecycle hooks see the docs.
You can use Promise.all
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You push all promises to an array, and then go
let foo : [Promise<Emplyoee[]>,Promise<void>] = [getAllEmployees(), method2()];
Promise.all(foo).then((results:any[]) => {
let employeearray: any = results[0];
/* and on an on */
});

Resources