How to do a switchmap or any better way to replace values in RxDart - dart

I have a collection with chatroom information. Something like this:
{
chatroomid: 59,
members: [2,3]
}
Now what I want to do is, get the collection stream, in the course of doing that be able to replace the members string ids with a corresponding firestore document based on member id.
End result should look something like this:
{
chatroomid: 59,
members: [{
id: 2,
username: Johndoe1
},
{
id: 3,
username: Jennydoe1
}]
}
Is this possible with Dart RxDart?
Trying something like this fails:
getChatroomStream(chatroomid)
.switchMap((i) {
return Stream.value(i.members.map((e) => i.members.add(Document(path: 'Global.userRef/$e').streamData())));
})
.listen((event) {print(event);});
[VERBOSE-2:ui_dart_state.cc(177)] Unhandled Exception: Concurrent
modification during iteration: Instance of
'MappedListIterable<dynamic, void>'.
#0 ListIterator.moveNext (dart:_internal/iterable.dart:337:7)

class MemberInfo {
final int id;
final String? username;
final bool isLoading;
}
class Room {
final int chatroomid;
final List<int> members;
}
class RoomWithMemberInfos {
final int chatroomid;
final List<MemberInfo> infos;
factory RoomWithMemberInfos.initial(Room room) {
return RoomWithMemberInfos(
room.chatroomid,
room.members
.map((id) => MemberInfo(id, null, true))
.toList(growable: false)
);
}
RoomWithMemberInfos withInfo(MemberInfo info) {
return RoomWithMemberInfos(
chatroomid,
infos
.map((e) => e.id == info.id ? MemberInfo(e.id, info.name, false) : e)
.toList(growable: false)
);
}
}
Stream<MemberInfo> getMemberInfo(int id) { ... }
getChatroomStream()
.switchMap((Room room) {
final initial = RoomWithMemberInfos.initial(room);
return Stream
.fromIterable(room.members)
.flatMap(getMemberInfo)
.scan<RoomWithMemberInfos>(
(acc, info) => acc!.withInfo(info),
initial,
)
.startWith(initial);
});

Related

type 'Null' is not a subtype of type 'Future<DataState<List<CustomerEntity>>>

I'm working on a test CRUD application using Bloc for state management and sqflite database. I was asked to implement BDD testing for the app but I have no idea about BDD testing. From what I've learned so far I tryed to implement a simple scenario for the start which is landing on the home screen, but I'm getting this error when I run the test.
Also I don't know exactly how to mock my database to test the four main Create, Read, Update, and Delete functionalities.
I'm using getIt for dependency injection, Mocktail, and bdd_widget_test.
It is the scenario that I wrote in the .feature file:
Feature: Add Feature
Scenario: Landing on the home screen
Given the app is running
Then I see enabled elevated button
And it's the logic for the test.dart file where I get the mentioned exception:
class MockGetAllCustomers extends Mock implements GetAllCustomersUsecase {}
class MockAddCustomer extends Mock implements AddCustomerUsecase {}
class MockUpdateCustomer extends Mock implements UpdateCustomerUsecase {}
class MockDeleteCustomer extends Mock implements DeleteCustomerUsecase {}
class MockDbHelper extends Mock implements DBHelper {}
void main() {
final GetIt getIt = GetIt.instance;
late CustomersBloc bloc;
late MockGetAllCustomers mockGetAllCustomers;
late MockAddCustomer mockAddCustomer;
late MockUpdateCustomer mockUpdateCustomer;
late MockDeleteCustomer mockDeleteCustomer;
late MockDbHelper dbHelper;
late Database database;
CustomerEntity customer = CustomerEntity(
firstName: 'firstName',
lastName: 'lastName',
dateOfBirth: 'dateOfBirth',
phoneNumber: 'phoneNumber',
email: 'email',
bankAccountNumber: 'bankAccountNumber');
setUpAll(() async {
// Initialize FFI
sqfliteFfiInit();
database = await databaseFactoryFfi.openDatabase(inMemoryDatabasePath);
await database.execute(
'CREATE TABLE $customersTable($colId INTEGER PRIMARY KEY AUTOINCREMENT, $colFirstName TEXT, $colLastName TEXT, $colDateOfBirth TEXT, $colPhoneNumber TEXT, $colEmail TEXT, $colAccountNum TEXT)');
dbHelper = MockDbHelper();
dbHelper.database = database;
databaseFactory = databaseFactoryFfi;
});
setUp(() {
mockGetAllCustomers = MockGetAllCustomers();
mockAddCustomer = MockAddCustomer();
mockUpdateCustomer = MockUpdateCustomer();
mockDeleteCustomer = MockDeleteCustomer();
bloc = CustomersBloc(mockGetAllCustomers, mockAddCustomer,
mockUpdateCustomer, mockDeleteCustomer);
getIt.registerFactory(() => bloc);
});
group('''Add Feature''', () {
testWidgets('''Landing on the home screen''', (tester) async {
await theAppIsRunning(tester);
await iSeeEnabledElevatedButton(tester);
});
});
}
It's a part of the exception message I'm receiving:
type 'Null' is not a subtype of type 'Future<DataState<List<CustomerEntity>>>'
package:mc_crud_test/features/customer_feature/domain/usecases/get_all_customers_usecase.dart 11:43 MockGetAllCustomers.execute
package:mc_crud_test/features/customer_feature/presentation/bloc/bloc/customers_bloc.dart 43:60 new CustomersBloc.<fn>
package:bloc/src/bloc.dart 226:26 Bloc.on.<fn>.handleEvent
package:bloc/src/bloc.dart 235:9 Bloc.on.<fn>
dart:async
And it's my database class:
String customersTable = 'customers_table';
String colId = 'id';
String colFirstName = 'firstName';
String colLastName = 'lastName';
String colDateOfBirth = 'dateOfBirth';
String colPhoneNumber = 'phoneNumber';
String colEmail = 'email';
String colAccountNum = 'bankAccountNumber';
//
class DBHelper {
//
Database database;
DBHelper({required this.database});
//Database initialization and creation
static Future<Database> initDatabase() async {
//
final dbPath = await sql.getDatabasesPath();
return await sql.openDatabase(
path.join(dbPath, 'customers.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE $customersTable($colId INTEGER PRIMARY KEY AUTOINCREMENT, $colFirstName TEXT, $colLastName TEXT, $colDateOfBirth TEXT, $colPhoneNumber TEXT, $colEmail TEXT, $colAccountNum TEXT)');
},
version: 1,
);
}
//
//find a customer by firstName, lastName, dateOfBirth, and email
Future<bool> findCustomer(CustomerEntity customer) async {
//
List<Map<String, dynamic>> map = await database.query(customersTable,
columns: [colFirstName, colLastName, colDateOfBirth, colEmail],
where:
'$colFirstName = ? OR $colLastName = ? OR $colDateOfBirth = ? OR $colEmail = ?',
whereArgs: [
customer.firstName,
customer.lastName,
customer.dateOfBirth,
customer.email
]);
if (map.isEmpty) {
return false;
} else {
return true;
}
}
// Add a customer to the Database
Future<int> insertCustomer(CustomerEntity customer) async {
//
final id = await database.insert(
customersTable,
customer.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
return id;
}
// Get the list of customers
Future<List<CustomerEntity>> getAllCustomers() async {
//
try {
final List<Map<String, Object?>> queryResult = await database.query(
customersTable,
orderBy: colId,
);
return queryResult.isEmpty
? []
: queryResult.map((e) => CustomerEntity.fromMapObject(e)).toList();
} catch (e) {
print('Error reading database : $e');
return [];
}
}
//
// Delete a Customer
Future<int> deleteCustomer(int id) async {
return await database
.delete(customersTable, where: '$colId = ?', whereArgs: [id]);
}
//
//Update a Customer
Future<bool> updateCustomer(CustomerEntity customer) async {
try {
final count = await database.update(customersTable, customer.toMap(),
where: '$colId = ?', whereArgs: [customer.id]);
if (count == 1) {
return true;
} else {
return false;
}
} catch (e) {
print('Failed to update the customer: $e');
return false;
}
}
}

How to pass null in a method?

class Foo {
final int? i;
Foo({this.i});
Foo copyWith({int? x}) {
return Foo(i: x ?? i);
}
}
void main() {
final foo = Foo(i: 0);
foo.copyWith(x: null);
print(foo.i); // prints `0` but should print `null`.
}
How can I actually pass null value to the method? In earlier Dart version copyWith() and copyWith(x: null) were two different things.
Note: I'm not looking for workarounds like making a new variable, like isNull and then deciding whether to pass null or not based on its value.
With simple copyWithwhit Dart null-safety you can't override value by null because if id is null return this.id. You need to override the value by null but not return with another value. It can solve in a few ways but I will give you the best example.
void main() {
final user = User(name: 'Dave', id: 110);
User copy = user.copyWith(id: null);
print(copy.toString()); // prints User(name: Dave, id: null).
}
class User {
User({required this.name, this.id});
final String name;
final int? id;
UserCopyWith get copyWith => _UserCopyWith(this);
#override
String toString() => 'User(name: $name, id: $id)';
}
abstract class UserCopyWith {
User call({
String name,
int? id,
});
}
class _UserCopyWith implements UserCopyWith {
_UserCopyWith(this.value);
final User value;
static const _undefined = Object();
#override
User call({
Object name = _undefined,
Object? id = _undefined,
}) {
return User(
name: name == _undefined ? value.name : name as String,
id: id == _undefined ? value.id : id as int?,
);
}
}

how to filter null values from map in Dart

Following the map, having both key-value pair as dynamic, Write a logic to filter all the null values from Map without us?
Is there any other approach than traversing through the whole map and filtering out the values (Traversing whole map and getting Entry Object and discarding those pairs) ?
I need to remove all values that are null and return map
Map<String, dynamic> toMap() {
return {
'firstName': this.firstName,
'lastName': this.lastName
};
Use removeWhere on Map to remove entries you want to filter out:
void main() {
final map = {'text': null, 'body': 5, null: 'crap', 'number': 'ten'};
map.removeWhere((key, value) => key == null || value == null);
print(map); // {body: 5, number: ten}
}
And if you want to do it as part of your toMap() method you can do something like this with the cascade operator:
void main() {
print(A(null, 'Jensen').toMap()); // {lastName: Jensen}
}
class A {
final String? firstName;
final String? lastName;
A(this.firstName, this.lastName);
Map<dynamic, dynamic> toMap() {
return <dynamic, dynamic>{
'firstName': this.firstName,
'lastName': this.lastName
}..removeWhere(
(dynamic key, dynamic value) => key == null || value == null);
}
}
You can now use a map literal with conditional entries:
Map<String, dynamic> toMap() => {
if (firstName != null) 'firstName': firstName,
if (lastName != null) 'lastName': lastName,
};
I did this to make it easy remove nulls from map and list using removeWhere:
https://dartpad.dartlang.org/52902870f633da8959a39353e96fac25
Sample:
final data =
{
"name": "Carolina Ratliff",
"company": null,
"phone": "+1 (919) 488-2302",
"tags": [
"commodo",
null,
"dolore",
],
"friends": [
{
"id": 0,
"name": null,
"favorite_fruits": [
'apple', null, null, 'pear'
]
},
{
"id": 1,
"name": "Pearl Calhoun"
},
],
};
void main() {
// From map
print('Remove nulls from map:\n' + data.removeNulls().toString());
// From list
print('\nRemove nulls from list:\n' + [data].removeNulls().toString());
}
The limitation of removeWhere is that it does not check for nested values. Use this recursive solution if you want to remove all keys in the hierarchy.
dynamic removeNull(dynamic params) {
if (params is Map) {
var _map = {};
params.forEach((key, value) {
var _value = removeNull(value);
if (_value != null) {
_map[key] = _value;
}
});
// comment this condition if you want empty dictionary
if (_map.isNotEmpty)
return _map;
} else if (params is List) {
var _list = [];
for (var val in params) {
var _value = removeNull(val);
if (_value != null) {
_list.add(_value);
}
}
// comment this condition if you want empty list
if (_list.isNotEmpty)
return _list;
} else if (params != null) {
return params;
}
return null;
}
Example:
void main() {
Map<String, dynamic> myMap = {
"a": 1,
"b": 2,
"c": [
3,
4,
null,
{"d": 7, "e": null, "f": 5}
],
"g": {"h": null, "i": null},
"j": 6,
"h": []
};
print(removeNull(myMap));
}
Output:
{a: 1, b: 2, c: [3, 4, {d: 7, f: 5}], j: 6}
Note:
If you want an empty map and list when their child has null values, comment out an empty check for map and list in the code.
I suggest you to use removeWhere function
Map<String, dynamic> map = {
'1': 'one',
'2': null,
'3': 'three'
};
map.removeWhere((key, value) => key == null || value == null);
print(map);
Maybe, just like I did, someone might come looking, for How to remove null fields from a model that will be sent to the API using retrofit and dio in flutter
Here is what I used to remove null values from the Model object
#Body(nullToAbsent: true)
For example
#POST("/shops")
Future<Shop> createShop(#Body(nullToAbsent: true) Shop shop);
The nullToAbsent param does the trick
This is the method I am using with Flutter 3:
Map<String, Object?> removeAllNulls(Map<String, Object?> map) {
final data = {...map};
data.removeWhere((key, value) => value == null);
for (final entry in data.entries.toList()) {
final value = entry.value;
if (value is Map<String, Object?>) {
data[entry.key] = removeAllNulls(value);
} else if (value is List<Object?>) {
final list = List.from(value);
for (var i = 0; i < list.length; i++) {
final obj = list[i];
if (obj is Map<String, Object?>) {
list[i] = removeAllNulls(obj);
}
}
data[entry.key] = list;
}
}
return data;
}
Usage:
var jsonData = jsonDecode(jsonEncode(example.toJson()));
jsonData = removeAllNulls(jsonData);
Remove null from JSON Using Dart Extension Method
extension JsonExtension on Map<String, dynamic> {
Map<String, dynamic> get removeNull {
removeWhere((String key, dynamic value) => value == null);
return this;
}
}
final data = {
"hello":null,
"world":"test"
};
void main() {
print(data.removeNull);
}
Output
{world: test}

After web request, navigate to other view (async /await proper usage) - Flutter , dart

I want to fetch data from the server, then parse json. When they are done, I want to navigate to another view.
void getServerData() async{
WebRequests ws = WebRequests('https://sampleurl');
Map<String, dynamic> map = await ws.getData();
mychamplist = map['mychampionships'];
mychamplist.forEach((f){
mychampionships.add(MyChampionships(
name: f['name'],
id: int.parse(f['id']),
numberOfPlayers: int.parse(f['nofplayers']),
));
});
Navigator
.of(context)
.pushReplacement(new MaterialPageRoute(builder: (BuildContext context) {
return FantasyNbi();
}));
}
It navigates to the FantasyNbi class before the previous code finished.
How could it do in proper way?
I do have a example class for you that you could use:
class API {
static Future getData(String url) {
return http.get('api link' + url);
}
static Future<List<BasicDiskInfo>> fetchAllDisks() async {
final response = await getData('disk');
if (response.statusCode == 200) {
Iterable list = json.decode(response.body);
List<BasicDiskInfo> disks =
list.map((model) => BasicDiskInfo.fromJson(model)).toList();
return disks;
} else {
throw Exception('Failed to load disks');
}
}
static Future<Disk> fetchDisk(int id) async {
final response = await getData('disk/' + id.toString());
if (response.statusCode == 200) {
return Disk.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load disk');
}
}
}
class Disk {
int id;
String name;
String volumeLable;
bool isReady;
String driveType;
String driveFormat;
int totalSize;
int totalFreeSpace;
int availableFreeSpace;
Disk(
{this.id,
this.name,
this.volumeLable,
this.isReady,
this.driveType,
this.driveFormat,
this.totalSize,
this.totalFreeSpace,
this.availableFreeSpace});
factory Disk.fromJson(Map<String, dynamic> json) {
return Disk(
id: json['id'],
name: json['name'],
volumeLable: json['volumeLable'],
isReady: json['isReady'],
driveType: json['driveType'],
driveFormat: json['driveFormat'],
totalSize: json['totalSize'],
totalFreeSpace: json['totalFreeSpace'],
availableFreeSpace: json['availableFreeSpace']);
}
}
And to get the data I can do this:
var data = await API.fetchAllDisks();
// or
API.fetchAllDisks().then((response) => {/* do something */})

TypeORM Polymorphic Relations

I am migrating a Laravel app to Node app using TypeORM. Has anyone been able to implement something similar to Laravel's Polymorphic Relations in TypeOrm?
Example schema I am trying to reproduce:
export class Notification {
id: string;
attachable_id: number;
attachable_type: string;
}
I want to be able to to have a notification.attachable relation that could be of any type. Then, ideally, I can eager load a user with their last x notifications, with the attachable on each notification.
EDIT:
So I done a refactor/rewrite and put it all in a repo https://github.com/bashleigh/typeorm-polymorphic
So, I've been thinking of trying to implement something for this for a while. I had 2 days to implement something in a hurry so I made this crud thing.
import {
FindManyOptions,
DeepPartial,
ObjectID,
FindConditions,
UpdateResult,
Repository,
SaveOptions,
} from 'typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
export interface PolymorphicInterface {
entityId: string;
entityType: string;
}
export type PolyMorphicType<K> = PolymorphicInterface & DeepPartial<K>;
export const POLYMORPHIC_RELATIONSHIP = 'POLYMORPHIC_RELATIONSHIP';
export interface PolymorphicOptions {
type: Function;
parent: Function;
property: string | Symbol;
}
export const PolyMorphic = (type: Function): PropertyDecorator => (
target: Object,
propertyKey: string | Symbol,
): void =>
Reflect.defineMetadata(
`${POLYMORPHIC_RELATIONSHIP}::${propertyKey}`,
{
type,
parent: target.constructor.name,
property: propertyKey,
},
target,
);
export class PolymorphicRepository<T extends DeepPartial<T>> extends Repository<T> {
private getMetadata(): Array<PolymorphicOptions> {
let keys = Reflect.getMetadataKeys((this.metadata.target as Function)['prototype']);
if (!Array.isArray(keys)) {
return [];
}
keys = keys.filter((key: string) => {
const parts = key.split('::');
return parts[0] === POLYMORPHIC_RELATIONSHIP;
});
if (!keys) {
return [];
}
return keys.map(
(key: string): PolymorphicOptions =>
Reflect.getMetadata(key, (this.metadata.target as Function)['prototype']),
);
}
async find(findOptions?: FindConditions<T> | FindManyOptions<T>): Promise<T[]> {
const polymorphicMetadata = this.getMetadata();
if (Object.keys(polymorphicMetadata).length === 0) {
return super.find(findOptions);
}
const entities = await super.find(findOptions);
return this.hydratePolymorphicEntities(entities);
}
public async hydratePolymorphicEntities(entities: Array<T>): Promise<Array<T>> {
const metadata = this.getMetadata();
metadata.forEach(
async (data: PolymorphicOptions): Promise<void> => {
await Promise.all(
entities.map(
async (entity: T): Promise<void> => {
const repository = this.manager.getRepository(data.type);
const property = data.property;
const parent = data.parent;
if (!repository) {
throw new Error(
`Repository not found for type [${
data.type
}] using property [${property}] on parent entity [${parent}]`,
);
}
const morphValues = await repository.find({
where: {
//#ts-ignore
entityId: entity.id, // TODO add type AbstractEntity
entityType: this.metadata.targetName,
},
});
//#ts-ignore
entity[property] = morphValues;
},
),
);
},
);
return entities;
}
public async update(
criteria:
| string
| string[]
| number
| number[]
| Date
| Date[]
| ObjectID
| ObjectID[]
| FindConditions<T>,
partialEntity: QueryDeepPartialEntity<T>,
): Promise<UpdateResult> {
const polymorphicMetadata = this.getMetadata();
if (Object.keys(polymorphicMetadata).length === 0) {
return super.update(criteria, partialEntity);
}
const result = super.update(criteria, partialEntity);
// TODO update morphs
throw new Error("CBA I'm very tired");
return result;
}
public async save<E extends DeepPartial<T>>(
entity: E | Array<E>,
options?: SaveOptions & { reload: false },
): Promise<E & T | Array<E & T>> {
const polymorphicMetadata = this.getMetadata();
if (Object.keys(polymorphicMetadata).length === 0) {
return Array.isArray(entity) ? super.save(entity, options) : super.save(entity);
}
const result = Array.isArray(entity)
? await super.save(entity, options)
: await super.save(entity);
Array.isArray(result)
? await Promise.all(result.map((res: T) => this.saveMorphs(res)))
: await this.saveMorphs(result);
return result;
}
private async saveMorphs(entity: T): Promise<void> {
const metadata = this.getMetadata();
await Promise.all(
metadata.map(
async (data: PolymorphicOptions): Promise<void> => {
const repository: Repository<PolymorphicInterface> = this.manager.getRepository(
data.type,
);
const property = data.property;
const parent = data.parent;
const value: Partial<PolymorphicInterface> | Array<Partial<PolymorphicInterface>> =
//#ts-ignore
entity[property];
if (typeof value === 'undefined' || value === undefined) {
return new Promise(resolve => resolve());
}
if (!repository) {
throw new Error(
`Repository not found for type [${
data.type
}] using property [${property}] on parent entity [${parent}]`,
);
}
let result: Array<any> | any;
if (Array.isArray(value)) {
//#ts-ignore
result = await Promise.all(
value.map(val => {
// #ts-ignore
val.entityId = entity.id;
val.entityType = this.metadata.targetName;
return repository.save(
value instanceof data.type ? value : repository.create(value),
);
}),
);
} else {
// #ts-ignore
value.entityId = entity.id; // TODO resolve AbstractEntity for T
value.entityType = this.metadata.targetName;
result = await repository.save(
value instanceof data.type ? value : repository.create(value),
);
}
// #ts-ignore
entity[property] = result;
},
),
);
}
}
It's pretty rough but that's what I implemented to tackle this. Essentially I've implemented is my own methods to handle saving of entities that are defined within the metadata by creating my own repository.
Then you can use it like so
#Entity()
export class TestEntity {
#PolyMorphic(SomeOtherEntity)
property: SomeOtherEntity[];
}
The typings are really bad but that's only because I've had 1 days to implement this feature and I did it on the plane

Resources