Dart: Which is a better practice? Using 'late' or constructor initializer list - flutter

I am modelling a Dart class with the new null safety types in mind. I believe there are two effective ways to initialize non-nullable properties, calculated from a parameter.
For this example, we will use the Favourite class.
This class uses the initializer list in the constructor.
class Favourite {
int favouriteId;
Favourite({required this.favouriteId});
Favourite.mapFromJson(dynamic json)
: this.favouriteId = json["favouriteId"];
}
This class uses the 'late' keyword.
class Favourite {
late int favouriteId;
Favourite({required this.favouriteId});
Favourite.mapFromJson(dynamic json) {
this.favouriteId = json["favouriteId"];
}
}
When would you use one over the other? Using 'late' feels risky. If I added another named constructor, the compiler would not complain about 'favouriteId' not being initialized.
Are there other options?
Thank you!

Neither.
Use a default constructor that initializes the fields themselves and a factory constructor that handles deserializing the json object:
class Favourite {
final int favouriteId;
Favourite({required this.favouriteId});
factory Favourite.fromMap(Map<String, dynamic> map) {
final favouriteId = json['favouriteId'];
assert(favouriteId != null && favouriteId is int);
return Favourite(
favouriteId: favouriteId,
);
}
}
The late keyword can be a source of headache if you don't handle it properly, so in general don't use it unless you have to.

If you're sure the json will always have a "favouriteId", you can write it like this:
class Favourite {
int favouriteId;
Favourite({required this.favouriteId});
Favourite.mapFromJson(Map<String, dynamic?> json):
assert(() {
final favouriteId = json["favouriteId"];
return favouriteId != null && favouriteId is int;
}()),
favouriteId = json["favouriteId"] as int;
}
void main() {
dynamic m = {"favouriteId":2};
final favourite = Favourite.mapFromJson(m);
print("favourite id: ${favourite.favouriteId}");
}

Related

How to do a pass/continue in .map method for Dart?

So basically say I have a class:
class Book {
late String title;
late int publishYear;
Book({required this.title, required this.publishYear});
factory Book.fromJson(Map<String, dynamic> json) {
return Book(
title: json['title'],
publishYear: json['published'],
);
}
}
Now say I want to filter a list of Books by their publish year like so:
void filterByPublishYear() {
List response = responseData; // API Response
List<Book> books = response.map((e) {
Book book = Book.fromJson(e);
if (book.publishYear > 2000) {
return book;
}
}).toList();
}
The .map() method has an error because it doesn't always return something:
A value of type 'List<Book?>' can't be assigned to a variable of type 'List'.
Try changing the type of the variable, or casting the right-hand type to 'List'.
I don't want to set the List to type Book? neither because, well, they're not supposed to be null ever.
How could I make this work? Is there a way to do a continue or a pass for .map() in dart that could make this work?
Thank you!
Sounds to me like what you want is a where method:
List<Book> books = [
Book(title: "Harry Potter", publishYear: 1997),
Book(title: "Diary of a Wimpy Kid", publishYear: 2007),
];
books = books.where((e) => e.publishYear > 2000).toList()
The where method takes a function like map, but it returns a bool, if the bool is true, it keeps the item, if it's false, it drops it from the list.
No, there isn't any way to pass or continue from within the callback of a map function. A few alternative ways to approach would be to use a combination of map and where as h8moss points out. Or alternatively you achieve the same result using fold:
List<Book> books = response.fold<List<Book>>([], (prev, element) {
Book book = Book.fromJson(element);
if (book.publishYear > 2000) {
prev.add(book);
}
return prev;
});

Assign default value in flutter constructor

I have a class named vendor
class Vendor {
int id;
String name;
Vendor({this.id , this.name});
}
Now I have another class person, in this class I have a list of vendors I want to set default value in person constructor:
class Person {
List<Vendor> vendor;
Person({this.vendor});
}
I've tried this solution but it did not work
class Person {
List<Vendor> vendor;
Person({this.vendor}) : vendor = vendor ?? const [];
}
You can set default values for optional constructor parameters using =:
class Foo {
final List<String> strings;
Foo({this.strings = const ['example']});
}
class Bar {
final List<String> strings;
Bar([this.strings = const ['example']]);
}
print(Foo().strings); // prints '[example]'
print(Bar().strings); // prints '[example]'
Note, values passed in as optional parameters must be const.

How do i invoke method on a generic type in dart

There are several models have a same structure, { type: xxx, settings: xxx}, so i would like to use a parent class "WidgetConfig" with a generic type "T" to implement this, but problem occurs when i add "fromJson" methods. How can i invoke method on a generic type or any other ways to implement this?
class BannerWidgetViewModel extends ChangeNotifier {
WidgetConfig<BannerWidgetConfig> config;
BannerWidgetViewModel(String configJson){
config = WidgetConfig.fromJson(configJson);
}
}
class BannerWidgetConfig {
String imgUrl;
String padImgUrl;
String lessonId;
BannerWidgetConfig.fromJson(json){
if (json != null) {
this.imgUrl = json['imgUrl'];
this.padImgUrl = json['padImgUrl'];
this.lessonId = json['lessonId'];
}
}
}
class WidgetConfig<T> {
WidgetType type;
WidgetConfig.fromJson(json){
if (json != null) {
this.type = json['type'];
// this.settings = T.fromJson(json['settings']); // T doesn't have fromJson method
}
}
}
then i use a abstract class but still not working.
abstract class BaseWidgetConfig {
BaseWidgetConfig.fromJson(dynamic json);
}
class WidgetConfig<T extends BaseWidgetConfig> {
WidgetType type;
T settings;
WidgetConfig.fromJson(json){
if (json != null) {
this.type = json['type'];
this.settings = T.fromJson();
}
}
}
code picture
Directly show the function as a reference.
send function as a reference here.
FireStoreHelper.getList<ModelLesson>('grade4', ModelLesson.fromJson);
get the method here.
static Future<List<T>> getList<T>(String path, Function fromJson)

differentiate undefined and null in Dart

Consider the following function:
BasicClass copyWith({
String id,
}) {
// some code behaving differently for 1) id is undefined and 2) id is explicit null
}
And consider the two parameters below:
Nothing (id is undefined)
copyWith();
Null (id is null)
copyWith(id: null);
in the copyWith method, is there any way I can make it behave differently for 1) and 2)
There is no way to differentiate null from "no parameter passed".
The only workaround (which is used by Freezed to generate a copyWith that supports null) is to cheat using a custom default value:
final undefined = Object();
class Example {
Example({this.param});
final String param;
Example copyWith({Object param = undefined}) {
return Example(
param: param == undefined ? this.param : param as String,
);
}
}
This requires typing your variables as Object though.
To fix that issue, you can use inheritance to hide the Object under a type-safe interface (again, see Freezed):
final undefined = Object();
class Example {
Example._();
factory Example({String param}) = _Example;
String get param;
void method() {
print('$param');
}
Example copyWith({String param});
}
class _Example extends Example {
_Example({this.param}): super._();
final String param;
#override
Example copyWith({Object param = undefined}) {
return Example(
param: param == undefined ? this.param : param as String,
);
}
}

How to compare the type variable in "is" operator in Dart

I couldn't find a way to store the Type value in Map so that I could use it in is operator to check the validity of type using this map later on. Also, can is operator accept Type as a variable?
For eg, Below is hypothetical code solving the problem but it's invalid.
Map<String, Type> map = {
"sku": String,
"price": double,
"quantity": int,
};
dynamic value = 10;
if(value is map["quantity"]){
print("value is of type int and int is expected for quantity value");
}
You can do something like this:
class TypeCheck<T> {
const TypeCheck();
bool typeCheck(dynamic value) => value is T;
}
void main() {
Map<String, TypeCheck> map = {
"sku": TypeCheck<String>(),
"price": TypeCheck<double>(),
"quantity": TypeCheck<int>(),
};
dynamic value = 10;
if (map["quantity"]!.typeCheck(value)) {
print("value is of type int and int is expected for quantity value");
}
}
Im not sure I fully understand I understand what you are trying to do but why don't you try something like.
bool _validate(Map productDetails){
if (productDetails.containsKey("sold_individually") && productDetails["sold_individually"] is bool) {
//return true or false
}
else if (productDetails.containsKey("stock_quantity") && productDetails["stock_quantity"] is int){
//return true or false
}
else if (productDetails.containsKey("tax_class") && productDetails["tax_class"] is String && productDetails["tax_class"].isNotEmpty) {
//return true or false
} else {
//return true or false
}
}
As for the other part of your question you wont get an error but you will always return false. In contrast if you check if a variable is dynamic it will always return true.
I don't really understand your end goal. But from what you have, I don't think you are taking advantage of the strongly-typed nature of dart.
Assuming you are getting your map from an API, you could enforce
typing manually in your code as follows;
Map<String, Type> map = {
"sku": json['key'] as String,
"price": json['key'] as double,
"quantity": json['key'] as int,
};
And avoid using dynamic when declaring variables.
OR
In the case you have a user-defined type you what to compare, you can use the equatable package on a class for instance as follows;
class CustomMap extends Equatable {
String sky;
double price;
int quantity;
// here you put the fields of a class you want for two instances of a class to be equal.
#overide
List<Object> get props => [sky, price, quantity];
}
Update from your comment
You should have a custom class for the API objects for instance;
class Item extends Equatable {
String sku;
double price;
int quantity;
Item({this.sky, this.price, this.quantity});
// factory constructor
factory Item.fromMap(Map<String, dynmic> json) {
final sku = json['sku'] as String,
final price = (json['price'] as num) as double,
final quantity = json['quantity'] as num,
return Item(sku: sku, price: price, quantity: quantity);
}
// define equatable objects
#override
List<Object> get props => [sku, price, quantity];
}
Now you can use it as follows;
Future<Item> objectsFromService(Map<String, dynamic> json ) async {
http.Response response = http.get(url);
if(response.status == 200) {
final decodedJson = json.decode(response.body);
return Item.fromJson(decodedJson);
}else{
print('Error fetch data');
return null;
}
}
Hope it helps