Dart what is the best way to create model with nullable field - flutter

let's say I have the user model, I know all fields, id, name, createdAt, and updatedAt from database are not null, but when I want to add new user, id, cratedAt, and updatedAt are null because they will be inserted on database.
Then, my question is what is the best approach to handle this kind of typical case?
1. creating the user model with nullable fields.
class User {
final String? id;
final String name;
final DateTime? createdAt;
final DateTime? updatedAt;
...
}
This maybe the most typical approach, but I have to use ! or check null all the time and feel not good.
2. creating new user model for adding data other than existing one which has no nullable fields.
class User {
final String id;
final String name;
final DateTime createdAt;
final DateTime updatedAt;
...
}
class NewUser {
final String? id;
final String name;
final DateTime? createdAt;
final DateTime? updatedAt;
...
}
This could work on very small size but error prone.
3. generate fields, id, createdAt, and updatedAt on the client side, and use non nullable value on all fields
class User {
final String id;
final String name;
final DateTime createdAt;
final DateTime updatedAt;
...
}
This could work too, but generating id or timestamp on the client side is not good approach.
4. do you have any other recommendation?
I understand there is no silver bullet, and it depends on the situation or preference. I just want to learn the typical approach.
I would appreciate any advice even on the different langs, like java, swift, or kotlin.

My suggestion would be to use a private nullable field for the fields that can be null, and then a getter for that field that asserts that the private field is not null. So:
class User {
final String? _id;
final String name;
...
String get id {
assert(_id != null);
return _id;
}
void set id(String value) => _id = value;
}
That way, you can use user.id just like you would any other field (like name), but you will get thrown an exception if you use it before it has been set, so you'll have to make sure not to read the field before the database has set the field. You will therefore want to set the field immediately after the database has generated the id. Same logic of course for the createdAt and updatedAt fields.
For convenience, you can add a getter on User that checks if all database-generated fields have been set, e.g.:
bool get databaseFieldsSet => _id != null && _createdAt != null && _updatedAt != null

Related

DART/FLUTTER - Check if a class value has already been instantiated

I have a class with the following attributes in Dart
class StartValue {
final int id;
final String firstName;
final String lastName;
StartValue({this.id, this.firstName, this.lastName})
}
and Ill initiate that classe with the values:
StartValue(
id: 1,
firstName: 'First',
lastName: 'LastName'
)
The question is what kind of validation i need to do to never instance a class StartValue with the NAME = 'First' again? Assuming I can only instantiate the class once with firstName = 'First'.
How do I do an instance validation to verify that each instance does not contain the firstName = "First" ?
I have to do something like:
StartValues.contains("First")
Keep in mind that I have almost 1000 classes instantiated, so I will have to check one by one if the value "First" contains in each class, this is my question
You have to iterate through every class to check if the firstName is taken, but I recommend using the == operator instead of .contains(). Why would you have 1000 instances? Can you put us in context?
Use a class-static Set of all ids seen so far. This will be quick to identify whether an item has already been generated.
Something like:
class Person {
final int id;
final String name;
static var seenIds = <int>{};
Person({
required this.id,
required this.name,
}) {
if (!seenIds.add(id)) throw ArgumentError('id $id already seen');
}
}
Keeping thousands of instances / names in memory is bad design as they are way too many instances / names you don't need at that moment. You go for local sql database like sqflite or you go for cloud database like Cloud Firestore to fetch the user you need and validate it.
If you still want to do it in-memory you can use a factory constructor, a private constructor and a static HashSet to check user instances.
If you need explanation then comment below. Full code example:
import 'dart:collection';
class Person {
final int id;
final String firstName;
final String lastName;
static HashSet<String> allNames = HashSet<String>();
factory Person({
required int id,
required String firstName,
required String lastName,
}) {
if (!allNames.add(firstName)) {
throw ArgumentError("Person with firstname $firstName already exists");
}
return Person._(id: id, firstName: firstName, lastName: lastName);
}
Person._({
required this.id,
required this.firstName,
required this.lastName
});
}

Flutter json_serializable different reference key name switch request

I'm asking a question and not found an similar post for this.
I explain, I use json_serializable, but webservice provide some information of the same type of object by with different name keys, exemple :
#JsonSerializable(explicitToJson: true)
class Group {
Group(
this.id,
this.name,
this.owner,
this.description);
int id;
String name;
User owner; //Here owner are only "id" and "name"
String? description;
...
}
#JsonSerializable()
class User {
User(this.uid, this.nom, this.mail, this.prenom);
String? uid;
late String mail;
String nom;
String? prenom;
...
So "id" and "uid" are different key but for same value, and same things for "name" and "nom".
There is any way for indicate to generator this can be different name of key for same value ..?
My solution is to set two different variable witch one will can be NULL

Flutter: json_serializable ignore nullable fields instead of throwing an error

Suppose there are two models User and City
#JsonSerializable()
class User {
int id;
String name;
City? city;
List<Map<String, City>>? listMapCity;
}
#JsonSerializable()
class City {
int id;
String name;
}
Now suppose during API call, we've got a user model but in the city object model, we only get id not name. Something like this
{
"id": 5,
"name": "Matthew",
"city": {
"id": 12
}
}
But due to the default nature of json_serializable and json_annotation.
This JSON is not mapped to the User model, during mapping, it throws the exception.
type Null is not a subtype of type String. (because here name key is missing in city object)
But as we already declared in the User object that City is optional, I wanted that it should parse the User JSON with city and listMapCity to be null.
Any help or solution would be really appreciated, Thank you
You need to set the includeIfNull flag to false to have the autogenerated code handle nulls correctly.
#JsonSerializable(includeIfNull: false)
The property should be declared with a ? as per your example.
You need to have a default constructor on your JsonSerializable User class. Then, if name should be nullable, declare it with a nullable String? name;
Here's the updated User class.
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
#JsonSerializable()
class User {
int id;
String name;
City? city;
List<Map<String, City>>? listMapCity;
User({required this.id, required this.name, this.city, this.listMapCity});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
#JsonSerializable()
class City {
int id;
String name;
City({required this.id, required this.name});
}

Flutter & firestore: query all posts from multiple users

the structure of my database looks like this:
each user has a document in the following collection, that document also contains a collection which holds the ids of the users that he is following.
the "posts" collection has the id of the user who posted it.
now i want to query it in such a way that i get all posts from the users that my user is following (ordered by timestamp)
this is what my user & post model look like:
class PostModel {
final String id;
final String creator;
final String text;
final Timestamp timestamp;
PostModel({this.id, this.creator, this.text, this.timestamp});
}
class UserModel {
final String id;
final String email;
final int following;
final int followers;
final String profileImgUrl;
final String name;
final String bio;
final bool isVerified;
final int postAmount;
UserModel(
{this.id,
this.email,
this.following,
this.followers,
this.profileImgUrl,
this.name,
this.bio,
this.isVerified,
this.postAmount});
}
can someone help me come up with an easy way to do this in flutter?
Instead of a subcollection users you could have an array field to store the users otherwise you need to fetch the users subquery everytime which is expensive

Flutter json_serializable Build failing

I am using json_serializable, json_annotation, and build to generate serialization/deserialization functionality for my models. When I run the build though, I get this error.
Error running JsonSerializableGenerator
Cannot populate the required constructor argument: created.
package:explorer/models/Account/account.dart:46:3
The line it is referring to is my model constructor which is this.
Account(String id, String firstName, String lastName, String email,
DateTime dob, DateTime created, DateTime updated,
{String accessTkn, String refreshTkn}) {}
Why am I getting this error?
As requested, here is my model class.
import "package:json_annotation/json_annotation.dart";
part "account.g.dart";
#JsonSerializable(nullable: true)
class Account {
#JsonKey(name: "id")
String _id;
#JsonKey(name: "first_name")
String _firstName;
#JsonKey(name: "last_name")
String _lastName;
#JsonKey(name: "email")
String _email;
#JsonKey(
name: "dob", fromJson: _isoStringToDateTime, toJson: _dateTimeToIsoString)
DateTime _dob;
#JsonKey(
name: "created",
fromJson: _isoStringToDateTime,
toJson: _dateTimeToIsoString)
DateTime _created;
#JsonKey(
name: "updated",
fromJson: _isoStringToDateTime,
toJson: _dateTimeToIsoString)
DateTime _updated;
#JsonKey(name: "access_token")
String _accessToken;
#JsonKey(name: "refresh_token")
String _refreshToken;
Account(String id, String firstName, String lastName, String email,
DateTime dob, DateTime created, DateTime updated,
{String accessTkn, String refreshTkn}) {
this._id = id;
this._firstName = firstName;
this._lastName = lastName;
this._email = email;
this._dob = dob;
this._created = created;
this._updated = updated;
this._accessToken = accessToken;
this._refreshToken = refreshTkn;
}
factory Account.fromJson(Map<String, dynamic> json) {
_$AccountFromJson(json);
}
// converts a DateTime to a ISO string
static String _dateTimeToIsoString(DateTime date) {
return date.toIso8601String();
}
// convert back to date time
static DateTime _isoStringToDateTime(String iso) {
return DateTime.parse(iso);
}
/// get the account id
String get id {
return this._id;
}
/// get the account first name
String get firstName {
return this._firstName;
}
/// get the account last name
String get lastName {
return this._lastName;
}
/// get the account email.
String get email {
return this._email;
}
/// get the account owner's date of birth
DateTime get dob {
return this._dob;
}
/// Get the date the account was created.
DateTime get createdAt {
return this._created;
}
/// get teh date the account was last updated.
DateTime get updatedAt {
return this._updated;
}
// get the account access token.
String get accessToken {
return this._accessToken;
}
// get the account refresh token.
String get refreshToken {
return this._refreshToken;
}
/// clones the account instance
Account clone() {
return Account(this.id, this.firstName, this.lastName, this.email, this.dob,
this.createdAt, this.updatedAt,
accessTkn: this.accessToken, refreshTkn: this.refreshToken);
}
Map<String, dynamic> toJson() {
_$AccountToJson(this);
}
}
You are getting the error because you didn't initialize passed parameters, you have an empty constructor.
You have to initialize every parameter you have in your class, or allow them to be nullable with JsonSerializable(nullable: true) or JsonKey(nullable: true)
Please share all code in your class if that solution wouldn't work for you
EDIT:
The library works with reflection, I understand where was an error.
Attributes should be named the same as your parameters.
Your getters should be named the same as your parameters
Change your code with next fixes:
Account(String id, String firstName, String lastName, String email,
DateTime dob, DateTime created, DateTime updated,
String accessToken, String refreshToken) {
this._id = id;
this._firstName = firstName;
this._lastName = lastName;
this._email = email;
this._dob = dob;
this._created = created;
this._updated = updated;
this._accessToken = accessToken;
this._refreshToken = refreshToken;
}
/// Get the date the account was created.
DateTime get created {
return this._created;
}
/// get teh date the account was last updated.
DateTime get updated {
return this._updated;
}
For future reference, I would like to explain the problem above with an example and suggest a general solution for it:
json_serializable + json_annotation use the constructor parameter names as the json field keys. So there is a distinct difference between the two examples below:
#JsonSerializable()
class User {
#JsonKey(name: "first_name") final String firstName;
// In this case, the json key becomes 'first_name',
// extracted from the explicitly referenced field annotation.
const User(this.firstName);
}
#JsonSerializable()
class User {
#JsonKey(name: "first_name") String _firstName;
String get firstName => _firstName?.trim();
// In this case, the json key becomes 'firstName',
// extracted from the constructor parameter name.
// For reflection, the field and its annotation are not involved.
User(String firstName) {
this._firstName = firstName;
}
}
The reason we want to hide a field is twofold; We don't want others to be able to update its value, and we want to provide a 'corrected' (in this case, trimmed) value rather than the unvalidated value retrieved from an external source. Since we are unable to neatly hide the unvalidated value, I'd suggest we do expose it but explicitly mention its shortcomings, like so:
#JsonSerializable()
class User {
// The field is final, so its value cannot be altered as if private.
// It is exposed (sadly), but clearly mentions potential issues.
#JsonKey(name: "first_name") final String firstNameUntrimmed;
// This is the 'corrected' version available with a more pleasant field name.
String get firstName => firstNameUntrimmed?.trim();
const User(this.firstNameUntrimmed);
}