How can we store an nested JSON object in SQLite database. In Android Room we used to have Embedded and Relation to store and retrieve complex data objects. But in flutter how can we achieve the same?
I tried exploring sqflite, floor, and moor. But none seem to help except for Moor, which allows us Map the values to Object using Joins. something like below code.
Stream<List<TaskWithTag>> watchAllTasks() {
return (select(tasks)
..orderBy(
[
(t) =>
OrderingTerm(expression: t.dueDate, mode: OrderingMode.desc),
(t) => OrderingTerm(expression: t.name),
],
))
.join(
[
leftOuterJoin(tags, tags.name.equalsExp(tasks.tagName)),
],
)
.watch()
.map((rows) => rows.map(
(row) {
return TaskWithTag(
task: row.readTable(tasks),
tag: row.readTable(tags),
);
},
).toList());
}
So What exactly is the right way to do this?
Related
I've got state with a nested array that looks like the following:
{
list: [
{
id: '3546f44b-457e-4f87-95f6-c6717830294b',
title: 'First Nest',
key: '0',
children: [
{
id: '71f034ea-478b-4f33-9dad-3685dab09171',
title: 'Second Nest',
key: '0-0
children: [
{
id: '11d338c6-f222-4701-98d0-3e3572009d8f',
title: 'Q. Third Nest',
key: '0-0-0',
}
],
}
],
],
selectedItemKey: '0'
}
Where the goal of the nested array is to mimic a tree and the selectedItemKey/key is how to access the tree node quickly.
I wrote code to update the title of a nested item with the following logic:
let list = [...state.list];
let keyArr = state.selectedItemKey.split('-');
let idx = keyArr.shift();
let currItemArr = list;
while (keyArr.length > 0) {
currItemArr = currItemArr[idx].children;
idx = keyArr.shift();
}
currItemArr[idx] = {
...currItemArr[idx],
title: action.payload
};
return {
...state,
list
};
Things work properly for the first nested item, but for the second and third level nesting, I get the following Immer console errors
An immer producer returned a new value *and* modified its draft.
Either return a new value *or* modify the draft.
I feel like I'm messing up something pretty big here in regards to my nested array access/update logic, or in the way I'm trying to make a new copy of the state.list and modifying that. Please note the nested level is dynamic, and I do not know the depth of it prior to modifying it.
Thanks again in advance!
Immer allows you to modify the existing draft state OR return a new state, but not both at once.
It looks like you are trying to return a new state, which is ok so long as there is no mutation. However you make a modification when you assign currItemArr[idx] = . This is a mutation because the elements of list and currItemArr are the same elements as in state.list. It is a "shallow copy".
But you don't need to worry about shallow copies and mutations because the easier approach is to just modify the draft state and not return anything.
You just need to find the correct object and set its title property. I came up with a shorter way to do that using array.reduce().
const keyArr = state.selectedItemKey.split("-");
const target = keyArr.reduce(
(accumulator, idx) => accumulator.children[idx],
{ children: state.list }
);
target.title = action.payload;
I'm trying to dynamically generate queries.
I have a big object of things like location and price data. But I get that data from the request. How can I dynamically make use of that data if every query thing is a chained function?
Ideally I'd like to convert something like this...
const wheres = [
{ key: 'price', operator: '>=', value: '1000' },
{ key: 'price', operator: '<=', value: '2000' }
]
...to...
admin
.firestore()
.collection(`rentals`)
.where(`price`, `>=`, `1000`)
.where(`price`, `<=`, `2000`)
You don't have to chain everything directly with each other. The builder pattern used to build the query returns an instance of Query with each call to where() (and other filtering methods). The code you wrote is equivalent to this:
const collection = admin.firestore().collection('rentals')
var query = collection.where('price', '>=', '1000')
query = query.where('price', '<=', '2000')
You can keep working with query as much as you want like this. So, you should be able to keep appending more constraints to it in a loop or whatever suits your requirements.
I create a collection called "books", in it I made an object (aka. dict) called "users". That object looks like this:
users: {
5PPCTcdHOtdYjGFr7gbCEsiMaWG3: firebase.firestore.FieldValue.serverTimestamp()
}
Now I want to query all books that belong to a certain user. I tried this:
this.db
.collection('books')
.where(`users.${this.state.currentUser.uid}`, '>', 0)
.onSnapshot(querySnapshot => {
...
I never get any documents returned. I'm sure the there is something it should match.
To check my sanity, if I remove the where(...) part, I do get documents. Just that I get documents for all users.
I console logged that string in the .where() and it looks right.
You’re query is wrong because you are comparing a date object with a number, therefore two different things. You can check it by running these two lines of code below.
console.log(typeof(firebase.firestore.FieldValue.serverTimestamp()))
console.log(typeof(0))
So you have two options:
1 - You can compare date objects with data objects like below.
this.db
.collection('books')
.where(`users.${this.state.currentUser.uid}`, '>', new Date(0))
.onSnapshot(querySnapshot => {
...
2 - Or you can save your date in milliseconds and compare it with a number.
users: {
5PPCTcdHOtdYjGFr7gbCEsiMaWG3: new Date().getTime();
}
this.db
.collection('books')
.where(`users.${this.state.currentUser.uid}`, '>', 0)
.onSnapshot(querySnapshot => {
...
I can't get this to work with .where() either, but it seems to work using .orderBy(), replacing:
.where(`users.${this.state.currentUser.uid}`, '>', 0)
with
.orderBy(`users.${this.state.currentUser.uid}`)
I'm trying to populate a collection of supermarket by adding the products to each supermarket, in an asynchronous way.
The objective is to pass from having something like this:
[{
name: 'supermarket x',
products: [1, 2]
}]
To something more like this:
[{
name: 'supermarket x',
products: [{
id: 1,
name: 'cacao'
}, {
id: 2,
name: 'milk'
}]
}]
I got to make the base code for this but I cannot achieve to populate the first stream with the second one once it's completed.
Any ideas?
I leave here a JSBin with the structure to make it faster for you
https://jsbin.com/nucutox/1/edit?js,console
Thanks!
So, you have a getSupermarkets() function which returns a stream that emits multiple supermarket objects and a getProduct(id) function which returns a stream that emits a single product with the specified id and then completes. And you want to map a stream of supermarkets containing product ids to a stream of supermarkets containing "resolved" product objects. Did I get this right?
Here's my solution:
const supermarkets$ = getSupermarkets()
.concatMap(supermarket => {
const productStreams = supermarket.products
.map(productId => getProduct(productId));
return Rx.Observable.combineLatest(productStreams)
.map(products => Object.assign({}, supermarket, { products }));
});
supermarkets$.subscribe(x => console.log(x));
When a new supermarket object arrives, we first map its array of product ids to an array of product observables, i.e. Array<Id> => Array<Observable<Product>>. Then we pass that array to a combineLatest operator which waits until each of the observables produces a value and emits an array of those values, i.e. an array of product objects. Then we produce a new supermarket object with products set to the array. We want to keep the original supermarket object unchanged, that's why Object.assign.
To make it work I had to slightly update your implementation of getProduct(): use .of(product) operator instead of .from(product) as product is a plain object we want to emit.
I use Dapper with dynamics because the table to be queries is not known until runtime, so POCO classes aren't possible.
I am returning Dapper's results via WebAPI. To save bandwidth, I need to return just the values from Dapper, not the property names, e.g.:
{
7,"2013-10-01T00:00:00",0,"AC",null,"ABC","SOMESTAGE"
},...
And not:
{
TID: 7,
CHANGE_DT: "2013-10-01T00:00:00",
EFFECTIVE_APPTRANS: 0,
EFFECTIVE_APPTRANS_STATUS: "AC",
DEVICE: null,
PROCESS: "ABC",
STAGE: "SOMESTAGE"
},...
I'm having some trouble figuring out a reasonable way to do this. I've tried abusing Dapper's mapping feature:
var tableData = Database.Query<dynamic, dynamic, dynamic>(connectInfo, someResource.sql,
(x, y) =>
{
List<object> l = new List<object>();
object o = x;
foreach(var propertyName in o.GetType().GetProperties().Select(p => p.Name)) {
object value = o.GetType().GetProperty(propertyName).GetValue(o, null);
l.Add(value);
}
return l as dynamic;
},
new {implantId, pendingApptrans},
splitOn: "the_last_column");
I've also tried having Dapper return a List using the same base code.
The idea was that I'd extract property values within the map function because it allows me to play with rows before anything is returned, but I get empty results without error:
[
[ ], [ ], [ ], [ ], [ ], [ ], [ ]
]
Additionally, I don't know any column names to split on, which the mapping feature wants. However even when I enter the last column name from a test query, the results are the same.
How can I return only values from Dapper's dynamic return value?
Do I need to resort to post-processing the dynamic after the Dapper call?
For that particular set of requirements, yes you would need to post-process; for example, you could cast to DapperRow or IDictionary<string,object>. Alternatively, you could use the IDataReader API that dapper now exposes.