Convert Stream<List<T>> to Stream<Map<K,T>> - dart

I have a requirement of converting Stream<List<T>> to Stream<Map<K,T>>
I have a class
class Order
{
int id;
DateTime date;
}
I want to convert Stream<List<Order>> to Stream<Map<DateTime, List<Order>>
I want to display orders as below
12-Dec-2020
Order 1
Order 2
13-Dec-2020
Order 3
Order 4
14-Dec-2020
Order 5
Order 6
Suggestions for a better DS are welcome.
How do I do this?
Please suggest.
Thanks in advance.

Using groupListsBy from collection: ^1.15.0
K keySelector(T t) { ... }
final Stream<List<T>> source$ = ...;
final Stream<Map<K, List<T>>> result$ = source$.map((list) => list.groupListsBy(keySelector));

You can do this without any package.
First, you have to create an empty Map, then map a List<Order> to a map with a key as a date from the list item (single order) then add it to That Empty map.
Check if the date is available as a key in Map. If the date exists as a key in that map then simply add a new order to that value or else simply add a new key to the Map.
Code:
Stream<List<Order>> orders = ...;
Stream<Map<DateTime, List<Order>>> ordersByDate = {};
orders.forEach((Order order){
ordersByDate.keys.contains(order.date)
? ordersByDate.update(order.date, (oldValue) => oldValue + <Order>[order])
: ordersByDate[order.date] = [order];
});
print(ordersByDate);
//At here your ordersByDate
Let me know if it works for you.

Fold method is what you're looking for.
Example:
void main() async {
final today = DateTime.now();
final tomorrow = today.add(const Duration(days: 1));
final order1 = Order(1, today);
final order2 = Order(2, today);
final order3 = Order(3, tomorrow);
final x = await Stream
.fromIterable([order1, order2, order3])
.fold<Map<DateTime, List<Order>>>({}, (val, element) {
(val[element.date] ??= []).add(element);
// Same as:
// if (val[element.date] == null) {
// val[element.date] = [];
// }
// val[element.date]!.add(element);
return val;
});
print(x);
}
class Order
{
int id;
DateTime date;
Order(this.id, this.date);
#override
String toString() {
return 'Order(id: $id, date: $date)';
}
}

Related

How to get the number of rows in a database table with Flutter SQFlite

How do I get the row count of a database table in Flutter. I am using the SQFlite plugin.
I assume that it is similar to Android, but Android has DatabaseUtils.queryNumEntries(db, TABLE_NAME). Does SQFlite have something similar?
I am answering the question to the best of my knowledge below, but I would be glad for a better answer if there is one.
You can use
int count = Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM table_name'));
where db is an SQFlite Database.
Source: I found this here and in the source code.
Try this function:
Future<int> getCount() async {
//database connection
Database db = await this.database;
var x = await db.rawQuery('SELECT COUNT (*) from
$Table');
int count = Sqflite.firstIntValue(x);
return count;
}
No more hard function invocations needed. just try with .length
If you are using a data model you can use its' name at MODEL_NAME.
Future<int> getCount() async {
Database db = await this.database;
var result = await db.query(MODEL_NAME.TABLE_NAME);
int count = result.length;
return count;
}
I believe a better way to do this is inside the DatabaseHelper/Service class:
class DbService {
static Database _db; //static = only a single copy of _db is shared among all the instances of this class (Database)
static int _count;
static int get count => _count;
...
this way every time a query gets executed the count updates, this also makes it possible to update the count on queries with filters without having to write a new function for it 😎:
List<Map> result = await db.query('myDatabase');
_count = result.length;
return result;
}
and then simply use DbService.count to get the count
You can use the magic column COUNT(*) as shown in the docs
Hence, the count function would look something like:
#override
Future<int> countUsersWithFirstName(String firstName) async {
return Sqflite.firstIntValue(await db.query(
'users',
columns: ['COUNT(*)'],
where: 'firstName = ?',
whereArgs: [firstName],
)) ??
0;
}
You can also use
List<Map> list = await db.rawQuery('SELECT * FROM table_name');
int count = list.length;
Try this code:
class DatabaseHelper() {
Future<List<Map<String, dynamic>>> getDataInMap() async {
Database database = await this.database;
return database.query("ahkam_fiqhia");
}
Future<List<BooksModel>> getModelsFromMapList() async {
List<Map<String, dynamic>> mapList = await getDataInMap();
List<DataModel> dataModel = List();
for (int i = 0; i < mapList.length; i++) {
dataModel(DataModel(mapList[i]));
}
print(mapList.length);
return dataModel;
}
}
Add initState() function in your view then call getModelsFromMapList function:
class _MainView extends State<MainView> {
DatabaseHelper dbHelper = databaseHelper();
#override
void initState() {
super.initState();
databaseHelper.getModelsFromMapList();
}
}
This is my code using for custom column condition count
Future<int?> getCustomTaskCount(String t1) async {
Database? _database = await database;
if (_database != null) {
var result = Sqflite.firstIntValue(await _database.rawQuery(
'SELECT COUNT (*) FROM tableName WHERE columnName=?',
[t1]));
print(result);
return result;
}}

How to select an item from a List in flutter

I have list from a model like this
amount:"12000"
dateTime:"19/07/2018"
detail:"Soto"
hashCode:853818549
id:1
name:"Theodorus"
I want to just select amount and add it to another list of string, but I'm always getting this error A value of type 'String' can't be assigned to a variable of type 'List<String>'. , I thinks its because im not doing it right, here is my code below
void setupList() async {
DebtDatabase db = DebtDatabase();
listCache = await db.getMyDebt();
setState(() {
filtered = listCache;
});
List<String> amount = new List<String>();
listCache.map((value) {
amount = value.amount; } );
//print(amount);
}
can anyone help me, so I can get list of ammount from this model list and then sum all the ammount?
The map function returns an iterable and you can then transform it into a list.
You should try something like this:
void setupList() async {
DebtDatabase db = DebtDatabase();
listCache = await db.getMyDebt();
setState(() {
filtered = listCache;
});
List<String> amount = listCache.map((value) => value.amount).toList();
//print(amount);
}

Using a stream for PropertyChanges

Trying to understand how streams work so i wrote this
class ViewModelBase{
final List<PropertyChangedRecord> _changeRecords = new List<PropertyChangedRecord>();
Stream<PropertyChangedRecord> _changes;
Stream<PropertyChangedRecord> get changes{
//lazy initialization
if(_changes==null)
_changes = new Stream<PropertyChangedRecord>.fromIterable(_changeRecords);
return _changes;
}
_raisePropertyChanged(oldValue,newValue,propertySymbol){
if(oldValue!=newValue){
_changeRecords.add(new PropertyChangedRecord(this, propertySymbol,oldValue,newValue));
}
return newValue;
}
}
class PropertyChangedRecord{
final ViewModelBase viewModel;
final Symbol propertySymbol;
final Object newValue;
final Object oldValue;
PropertyChangedRecord(this.viewModel,this.propertySymbol,this.oldValue,this.newValue);
}
and used it as
void main() {
var p = new Person('waa',13);
p.age = 33334;
p.name = 'dfa';
p.changes.listen((p)=>print(p));
p.age = 333834;
p.name = 'dfia';
}
class Person extends ViewModelBase{
String _name;
String get name => _name;
set name(String value) => _name = _raisePropertyChanged(_name,value,#name);
int _age;
int get age => _age;
set age(int value) => _age = _raisePropertyChanged(_age,value,#age);
Person(this._name,this._age);
}
and got the following exception
Uncaught Error: Concurrent modification during iteration: Instance(length:4) of '_GrowableList'
which i think is because the stream is removing items from the list while new PropertyChangedRecords are being added, how do i go around that?
The error could be caused by adding an item while the stream iterates the list.
You could use a StreamController to create the stream instead (see How to pass a callback function to a StreamController for an example).
class ViewModelBase{
//final List<PropertyChangedRecord> _changeRecords = new List<PropertyChangedRecord>();
//Stream<PropertyChangedRecord> _changes;
final StreamController _changeRecords = new StreamController<PropertyChangedRecord>();
Stream<PropertyChangedRecord> get changes => _changeRecords.stream;
_raisePropertyChanged(oldValue,newValue,propertySymbol){
if(oldValue!=newValue){
_changeRecords.add(new PropertyChangedRecord(this, propertySymbol,oldValue,newValue));
}
return newValue;
}
}

Observing an object containing a list of observables?

I am using the observe package.
Consider this example:
class Product extends Object with ChangeNotifier {
double _price = 0.0;
#reflectable double get price => _price;
#reflectable void set price(double value) {
if (value == null) throw new ArgumentError();
_price = notifyPropertyChange(#price, price, value);
}
}
class Order extends Object with ChangeNotifier {
final ObservableList<Product> products = new ObservableList<Product>();
double get total {
double sum = 0.0;
for (var item in products) {
sum += item.price;
}
return sum;
}
}
// Synchronizes the view total with the order total.
// Or rather, I'd like it to do that.
var order = new Order();
order.changes.listen((records) {
view.total = order.total;
});
How would I rewrite this example to make it work?
I would like to be notified of any changes to the object's state, even if they happen to the list or the items of the list.
Do I have to manage change subscriptions to all items and the list itself? Inside or outside of the Order class? Through which property would I notify the change? It seems messy either way.
The elements in the ObservableList do not propagate the notification to the list that contains them. They can't because they have no reference to the list.
Also the list does not forward the notifications to the class it is referenced by.
Not really satisfying but the best I could come up with.
import 'dart:async' as async;
import 'package:observe/observe.dart';
class Product extends Object with ChangeNotifier {
double _price = 0.0;
#reflectable double get price => _price;
#reflectable void set price(double value) {
if (value == null) throw new ArgumentError();
_price = notifyPropertyChange(#price, price, value);
}
#override
String toString() => 'Product - price: $price';
}
class Order extends Object with ChangeNotifier {
final ObservableList<Product> products = new ObservableList<Product>();
// keep listeners to be able to cancel them
final List<async.StreamSubscription> subscriptions = [];
Order() {
products.changes.listen((cr) {
// only react to length changes (isEmpty, isNotempty changes are redundant)
var lengthChanges = cr.where((c) => c.name == #length);
if(lengthChanges.isNotEmpty) {
lengthChanges.forEach((lc) =>
notifyChange(lc));
// we can't know if only additions/removals were done therefore we
// cancel all existing listeners and set up new ones for all items
// after each length change
_updateProductsListeners();
}
});
// initial setup
_updateProductsListeners();
}
// cancel all product change listeners and create new ones
void _updateProductsListeners() {
print('updateListeners');
subscriptions.forEach((s) => s.cancel());
subscriptions.clear();
products.forEach((p)
=> subscriptions.add(p.changes.listen((crs) =>
crs.forEach((cr) =>
notifyPropertyChange(cr.name, cr.oldValue, cr.newValue)))));
}
double get total {
double sum = 0.0;
for (var item in products) {
sum += item.price;
}
return sum;
}
}
void main() {
// Synchronizes the view total with the order total.
// Or rather, I'd like it to do that.
var order = new Order();
order.changes.listen((records) {
//view.total = order.total;
records.forEach(print);
});
// a PathObserver example but it doesn't seem to be more convenient
var op = new PathObserver(order, 'products[3].price')..open((c) =>
print(c));
var prods = [new Product()..price = 1.0, new Product()..price = 2.0, new Product()..price = 3.0, new Product()..price= 4.0];
var prods2 = [new Product()..price = 5.0, new Product()..price = 6.0];
order.products.addAll(prods);
// use Future to allow change notification propagate between changes
new async.Future(() =>
order.products..addAll(prods2)..removeWhere((p) => p.price < 3.0))
.then((_) => new async.Future(() => order.products[3].price = 7.0));
new async.Future.delayed(new Duration(seconds: 1), () => print('done'));
}
I suggest to use something like an event bus for this where the objects that want/should to notify about something just send and event and objects that are interested in something listen for that without any knowledge of where the other object exists.
For example https://pub.dartlang.org/packages/event_bus
Another solution is to use the ListPathObserver. The class is deprecated but you can copy his code and reuse it. With that class you can listen for specific changes in the contained items. The field to watch is specified by path.

Extract parameter / value pairs from an Uri as a Map

How do I get the parameter / value pairs of an URL / URI using Dart? Unfortunately currently there is no built-in functionality for this problem neither in the Uri library or the Location interface.
You can use Uri.splitQueryString to split the query into a map.
There is now a queryParameters member of Uri that returns a Map
Uri u = Uri.parse("http://app.org/main?foo=bar&baz=bat");
Map<String,String> qp = u.queryParameters;
print(qp);
// {foo: bar, baz: bat}
// url=http://127.0.0.1:3030/path/Sandbox.html?paramA=1&parmB=2#myhash
void main() {
String querystring = window.location.search.replaceFirst("?", "");
List<String> list = querystring.split("&").forEach((e) => e.split("="));
print(list); // [[paramA, 1], [parmB, 2]]
}
Map<String, String> getUriParams(String uriSearch) {
if (uriSearch != '') {
final List<String> paramValuePairs = uriSearch.substring(1).split('&');
var paramMapping = new HashMap<String, String>();
paramValuePairs.forEach((e) {
if (e.contains('=')) {
final paramValue = e.split('=');
paramMapping[paramValue[0]] = paramValue[1];
} else {
paramMapping[e] = '';
}
});
return paramMapping;
}
}
// Uri: http://localhost:8080/incubator/main.html?param=value&param1&param2=value2&param3
final uriSearch = window.location.search;
final paramMapping = getUriParams(uriSearch);

Resources