I have a question, I started learning dart/flutter, and when passing data from one screen to another I access the data like this
final orderData = ModalRoute.of(context).settings.arguments;
OrderData class looks like this
class OrderItem {
final String id;
final String name;
final String date;
final String address;
final String recordNumber;
OrderItem({
#required this.id,
#required this.name,
#required this.date,
#required this.address,
#required this.recordNumber,
});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'date': date,
'address': address,
'recordNumber': recordNumber,
};
}
factory OrderItem.fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return OrderItem(
id: map['id'],
name: map['name'],
date: map['date'],
address: map['address'],
recordNumber: map['recordNumber'],
);
}
String toJson() => json.encode(toMap());
factory OrderItem.fromJson(String source) => OrderItem.fromMap(json.decode(source));
#override
String toString() {
return 'OrderItem(id: $id, name: $name, date: $date, address: $address, recordNumber: $recordNumber)';
}
#override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is OrderItem &&
o.id == id &&
o.name == name &&
o.date == date &&
o.address == address &&
o.recordNumber == recordNumber;
}
#override
int get hashCode {
return id.hashCode ^
name.hashCode ^
date.hashCode ^
address.hashCode ^
recordNumber.hashCode;
}
}
My question is two fold(this is what vs code plugin generates for class). First is how can I access the data in the instance of the class(do I need for each specific getter), and second can somebody explain what toMap(), fromMap(), toJson(), fromJson(), bool operator ==(Object o), and hashCode getter do.
Looks like you're trying to use Flutter navigation methods.
Have a look at this blog post that explains how it works.
In summary, to "push" state to be used in the next screen, do something like this:
final arguments = OrderItem(
id = 'id',
name = 'name',
date = 'date',
address = 'address',
recordNumber = '10',
);
Navigator.pushNamed(context, NamedPagePassed.route, arguments: arguments);
You can later access it like this:
final OrderItem args = ModalRoute.of(context).settings.arguments;
print('The id of the order is ${args.id}');
You seem to have a whole lot of generated code in your class. The fromJson, toJson, fromMap and toMap methods are used for serialization (i.e. turn a Dart object into something that can be "transferred" to/from another language/network/etc).
The == operator and hashCode are used to check if an object instance is equal to another (which is very common in Flutter as Flutter wants to know if your UI state has been modified). hashCode allows a fast way to check that two objects are definitely not equal (you can know for sure that two objects are not equal if their hash-codes are different... if the hash-codes are equal, the objects may or may not be equal, but the probability they are NOT equal will be low because hash functions try to avoid "collisions", which is when two different objects have the same hash-code).
Hash-code and == are normally implemented together to give your class "identity". Just google around and you'll see how this all works.
I would recommend you don't use code generation from your IDE like this. Instead, get familiar with how Dart builders work, then use a codegen library that will automatically create these methods for you every time you compile (so changes to the data model are immediately reflected in the implementation of all these generated methods).
My recommendation is to use freezed for that.
Your code will be much more maintainable this way.
Related
I am using the faunadb_http package and I want the value to be returned null from Fauna DB if the field does not exist in the collection. I am just not able to figure out what should I put in the default parameter of this package so that I get that back as the default value.
I tried the following two variations of default parameter and I get "Value not found at path" error for first and just an empty Object {} for second.
'itemPrice': Select(["data", "itemPrice"], Var("postDoc"), default_: null),
'itemLocation': Select(["data", "itemLocation"], Var("postDoc"), default_: Obj({})),
Can somebody help me understand what should I be passing to default_ so that I get a String or Int as a response back.
This is the code for the Select class from the package
#JsonSerializable()
class Select extends Expr {
#JsonKey(name: 'select')
final Object path;
final Expr from;
#JsonKey(name: 'default', disallowNullValue: true, includeIfNull: false)
final Expr? default_;
Select(this.path, this.from, {this.default_});
factory Select.fromJson(Map<String, dynamic> json) => _$SelectFromJson(json);
#override
Map<String, dynamic> toJson() => _$SelectToJson(this);
}
And this is for the Expr class
class Expr {
static Object? wrap_value(dynamic value) {
if (value is List) {
return wrap_values(value);
} else if (value is Map<String, dynamic>) {
return Obj(value);
} else if (value is DateTime) {
return Time(value.toUtc().toIso8601String());
} else {
return value;
}
}
static Object? wrap_values(Object? data) {
if (data == null) return null;
if (data is List) {
return List.generate(
data.length,
(e) => wrap_value(data[e]),
growable: false,
);
} else if (data is Map<String, dynamic>) {
return data.map(
(key, value) => MapEntry(key, wrap_value(value)),
);
}
return data;
}
Expr();
factory Expr.fromJson(Map<String, dynamic> json) => _$ExprFromJson(json);
Map<String, dynamic> toJson() => _$ExprToJson(this);
#override
String toString() {
return json.encode(this).toString();
}
}
I'm going to set aside the language-specific aspects, as I'm not familiar with Dart.
That said, as I read through your post it seems like Select() is working as defined. The third argument is what is returned if your data is not found, e.g., null.
In the first case, you are returning null explicitly, and Fauna removes keys with null values, so that value would indeed not be found.
In the second case, you are returning an empty Object, and you receive an empty Object, so that seems to be working as defined as well.
Can somebody help me understand what should I be passing to default_ so that I get a String or Int as a response back.
In this case you need to explicitly set an Expr that will evaluate to a string or Int. If the empty string "" and zero 0 are reasonable defaults, then you would want:
'itemPrice': Select(["data", "itemPrice"], Var("postDoc"), default_: 0),
and
'itemLocation': Select(["data", "itemLocation"], Var("postDoc"), default_: ""),
I got in touch with the author of the package and they were kind enough to fix the issue within a day of reporting it. Now it works as expected.
Using Flutter and Dart, lets say I have this class:
#JsonSerializable()
class User {
#JsonKey(nullable: true, required: false)
final String name;
#JsonKey(nullable: true, required: false)
final int age;
User({
this.name,
this.age,
});
factory User.fromJson(Map<String, dynamic> json) => _$AddressFromJson(json);
Map<String, dynamic> toJson() => _$AddressToJson(this);
#override
String toString() {
return 'User ${toJson().toString()}';
}
}
In my code I'm trying to create a new instance of this class to be sent to /update-user endpoint on the server. My goal is to send an object that contains just the properties I would like the server to update. let's say only update age.
Using
final dto = new UpdateUserRequest(
age: 34
);
results in this json representation: {name: null, age: 34}, which will override the already existing name on the server.
I also tried 'json merging' 2 objects, the one I already have with the name, and the new dto that updates age:
final combined = UpdateUserRequest.fromJson({
...(dtoWithAge.toJson()),
...(existingUserWithName.toJson()),
});
but no matter how I play around with these, they end up overriding each other.
So, is there anyway to get a json/DTO instance of the class, that only contains the properties and values I want the server to update? (trying to achieve something very similar to javascript)
I don't think there's a pre-implemented solution for that. Assuming I understood what you're trying to accomplish, how about adding a copyWith method to User?
User copyWith({
String name,
int age,
}) => User(
name: name ?? this.name,
age: age ?? this.age,
);
You'd use it like this:
final existingUser = User(name: 'John');
final updatedUser = existingUser.copyWith(age: 25);
sendUpdateRequestWith(updatedUser);
For you convenience, there's a plugin to generate it.
vscode: https://marketplace.visualstudio.com/items?itemName=BendixMa.dart-data-class-generator
IDEA/Android Studio: https://plugins.jetbrains.com/plugin/12429-dart-data-class
How i can control witch rules i want to apply to my code.
I added lint package but argument type rules are happening to much.
I have a lot dynamic data from API calls and i tried disable theme but it didn't work.
Can i decide which rules i want to apply?
There are rules that make the code more efficient like the const rule, adding lint to active project can be headache so i think if it is worth it?
The current rules i try to disable:
argument_type_not_assignable: false
invalid_assignment: false
Article.fromMap throw The argument type 'dynamic' can't be assigned to the parameter type 'String'.dartargument_type_not_assignable
class Article {
String id;
String image;
String title;
Map contentEditor;
List<Map<String, dynamic>> teams;
List<Map<String, dynamic>> leagues;
String content;
String updateDate;
Article({
this.id,
this.image,
this.title,
this.contentEditor,
this.teams,
this.leagues,
this.content,
this.updateDate,
});
String getDateString() {
DateFormat formatter = DateFormat("MM/dd/yyyy");
return formatter.format(DateTime.parse(this.updateDate));
}
String getTimeString() {
final dateTime = DateTime.parse(this.updateDate).toLocal();
return DateFormat.Hm().format(dateTime);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'image': image,
'title': title,
'contentEditorId': contentEditor,
'teams': teams,
'leagues': leagues,
'content': content,
'updateDate': updateDate
};
}
factory Article.fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return Article(
id: map['id'],
image: map['image'],
title: map['title'],
contentEditor: map['contentEditor'],
teams: List<Map<String, dynamic>>.from(map['teams']?.map((x) => x)),
leagues: List<Map<String, dynamic>>.from(map['leagues']?.map((x) => x)),
content: map['content'],
updateDate: map['updateDate'],
);
}
String toJson() => json.encode(toMap());
factory Article.fromJson(Map json) => Article.fromMap(json);
}
I like more this approach
dynamic getPoints(dynamic property) {
if (property == null) {
return 0;
}
return property.won * 3 + property.draw;
}
than this:
int getPoints(Map<String, int> property) {
if (property == null) {
return 0;
}
return property["won"] * 3 + property["draw"];
}
You can disable the strict type casting by following:
analyzer:
language:
strict-casts: false
strict-casts is a language mode of stricter type checks.
There are 3 modes.You can find more on this at Enabling stricter type checks
argument_type_not_assignable and invalid_assignment are errors, not lints.
You can make the Dart analyzer ignore them by modifying your analysis_options.yaml file to have:
analyzer:
errors:
argument_type_not_assignable: ignore
invalid_assignment: ignore
However, it does not make any sense to disable those rules. Even though the analyzer would no longer complain about violations, violations would still be illegal and ultimately would generate compilation errors. What would you expect int x = 'string'; to do?
In the following model class, what purpose does overriding the toString() method achieve?
class User {
final int id;
String name, email, token;
User(this.id, this.name, this.email, this.token);
User.fromJson(dynamic json) : this.id = json['id'] {
this.name = json['name'];
this.email = json['email'];
this.token = json['token'];
}
dynamic toJson() => {'id': id, 'name': name, 'email': email, 'token': token};
#override
String toString() {
return toJson().toString();
}
}
The purpose of the toString() method is to provide a literal representation of whatever object it is called on, or to convert an object to a string (example converting primitive types to strings). For a user defined class, it can vary depending on your use case. You might want to print out the object to a console. A simple print command on the object won't give you any useful information. However, you can use the toString() method and convert it to a more understandable version. Example
#override
String toString() {
return "($someProperty,$someOtherProperty)";
}
This will return whatever the properties of that object were set at that time. Another (although not good) purpose could be to compare two objects of the same class. Similar to the above example, you can say that two objects of the class would be equal if the output of the toString() method of both objects is the same. Again, it totally depends on your use case.
This is a Flutter project using Floor (package equivalent of Jetpack's Room).
I have an entity with an auto increment id (note that this is pre null-safety code, in the answer I start now with post null safety snippet):
const String ACTIVITIES_TABLE_NAME = 'activities';
#Entity(
tableName: ACTIVITIES_TABLE_NAME,
indices: [
Index(value: ['start'])
],
)
class Activity {
#PrimaryKey(autoGenerate: true)
int id;
#ColumnInfo(name: 'device_name')
final String deviceName;
#ColumnInfo(name: 'device_id')
final String deviceId;
final int start;
Activity({
this.deviceName,
this.deviceId,
this.start,
});
}
I have this DAO:
#dao
abstract class ActivityDao {
#Query('SELECT * FROM $ACTIVITIES_TABLE_NAME ORDER BY start DESC')
Future<List<Activity>> findAllActivities();
#insert
Future<int> insertActivity(Activity activity);
}
In my app I recorded five activities so far, hunky-dory.
_database =
await $FloorAppDatabase.databaseBuilder('app_database.db').build();
...
_activity =
Activity(deviceName: device.name, deviceId: device.id.id, start: _lastRecord.millisecondsSinceEpoch);
final id = await _database.activityDao.insertActivity(_activity);
_activity.id = id;
Then in another view I list them, but there is a lurking problem.
final data = await _database.activityDao.findAllActivities();
When I query the entities with this dead simple method, all the five or so activities in my DB are returned with an id field filled with null. That makes the entity completely useless because any other operation I would like to perform on it fails due to lack of actual id. Am I doing something wrong?
I mostly have experience with MySQL, Postgres and non SQLite RDBMS. As I understand in SQLite every row has a unique rowid, and by declaring my auto increment id primary key field basically I alias that rowid? Whatever it is, I need the id. It cannot be null.
I'm debugging the guts of Floor.
Future<List<T>> queryList<T>(
final String sql, {
final List<dynamic> arguments,
#required final T Function(Map<String, dynamic>) mapper,
}) async {
final rows = await _database.rawQuery(sql, arguments);
return rows.map((row) => mapper(row)).toList();
}
At this point the rows still have the ids filled in properly, although the entities in the rows are just a list of dynamic values. The rows.map supposed to map them to the entity objects, and that cannot carry the id over for some reason? Can this be a Floor bug?
Ok, now I see that in the generated database.g.dart the mapper does not have the id:
static final _activitiesMapper = (Map<String, dynamic> row) => Activity(
deviceName: row['device_name'] as String,
deviceId: row['device_id'] as String,
start: row['start'] as int);
That explains it why id is null then. But this is generated code, how can I tell Floor I need the id? Or why should I tell it, it has to be there by default, who wouldn't want to know the primary key of an object?
Ok, so this is because I didn't have the the id as a constructor parameter. I didn't have that because it's an auto increment field and I'm not the one who determines it. However without having it as a constructor argument, the generated code cannot pass it along with the mapper as a parameter, so it leaves it out from the mapper. So I added the id as a constructor argument.
Post null-safety version:
class Activity {
#PrimaryKey(autoGenerate: true)
int? id;
#ColumnInfo(name: 'device_name')
final String deviceName;
#ColumnInfo(name: 'device_id')
final String deviceId;
final int start;
final int end;
Activity({
this.id,
required this.deviceName,
required this.deviceId,
required this.start,
this.end: 0,
});
}
I also add this Floor GitHub issue here, it looks like in the future there might be an annotation: https://github.com/vitusortner/floor/issues/527
Pre null-safety version:
import 'package:meta/meta.dart';
class Activity {
#PrimaryKey(autoGenerate: true)
int id;
#ColumnInfo(name: 'device_name')
#required
final String deviceName;
#ColumnInfo(name: 'device_id')
#required
final String deviceId;
final int start;
Activity({
this.id: null,
this.deviceName,
this.deviceId,
this.start,
});
}
The compiler is a little iffy about the null, but after the flutter packages pub run build_runner build the database.g.dart's mappers have the id as well.