I have a device class in my application where one property needs to be computed and final plus some properties that can be set in the constructor or have default values. Here's the code:
class Device {
String idx;
String id;
String name;
String icon;
int order;
// have to create the idx before initializing the object because I can't
// figure out how to do that here.
Device(
{required this.idx,
required this.id,
required this.name,
this.icon = 'none',
this.order = -1});
// Creates a device from a JSON string
factory Device.fromJson(Map<String, dynamic> jsonData) {
return Device(
idx: jsonData['idx'],
id: jsonData['id'],
name: jsonData['name'],
icon: jsonData['icon'],
order: jsonData['order']);
}
// Returns the device as a String
static Map<String, dynamic> toMap(Device device) => {
'idx': device.idx,
'id': device.id,
'name': device.name,
'icon': device.icon,
'order': device.order
};
}
Basically I'm trying to set a unique index for the object so in my object list I can clearly identify a specific device. I'm using the Uuid package to generate a UUID for idx.
The only way I can make this work today is to create the idx in my other code that creates the object and pass it in. I read a lot of articles here that talk about different ways to solve this problem and I know I have to make the idx value a constant but I can't figure out how to do that and call the Uuid library.
I know it would look something like this:
Device(
{this.idx = const <<some calculation/expression>>,
required this.id,
required this.name,
this.icon = 'none',
this.order = -1});
removing the required modifier and putting a const before the value assignment. Nothing I've tried lets me call the Uuid method. Can someone help me understand how to do this?
Updating the code based on the answer from #jamesdlin:
import 'package:uuid/uuid.dart';
const uuid = Uuid();
class Device {
String idx;
String id;
String name;
String icon;
int order;
Device(
{String? idx,
required this.id,
required this.name,
this.icon = 'none',
this.order = -1})
: idx = idx ?? uuid.v1();
// Creates a device object from a JSON string
factory Device.fromJson(Map<String, dynamic> jsonData) {
return Device(
idx: jsonData['idx'],
id: jsonData['id'],
name: jsonData['name'],
icon: jsonData['icon'],
order: jsonData['order']);
}
// Returns the device object as a String
static Map<String, dynamic> toMap(Device device) => {
'idx': device.idx,
'id': device.id,
'name': device.name,
'icon': device.icon,
'order': device.order
};
}
This works, but I don't ever have a use case where I want the idx set manually, so how to I accomplish that? I could leave it like this, but I really want to better understand how to do exactly what I need.
The only way I can make this work today is to create the idx in my other code that creates the object and pass it in.
If you want the object to be able to generate its own UUID, you just can do:
const uuid = Uuid();
class Device {
String idx = uuid.v1(); // Or whatever UUID version you want.
...
or you if you want the caller to have the option to pass in a UUID string, you can use the typical technique of using null to achieve non-const default function arguments:
const uuid = Uuid();
class Device
String idx;
Device({String? idx, ...})
: idx = idx ?? uuid.v1(),
...
Note that attempting to make a const initializer for a UUID makes no sense. const means that the object is a compile-time constant, and furthermore, const objects are canonicalized, so a hypothetical const expression that generated a UUID would end up producing the same String for every Device, which would be the opposite of unique.
Update for you updated question
This works, but I don't ever have a use case where I want the idx set manually, so how to I accomplish that? I could leave it like this, but I really want to better understand how to do exactly what I need.
I don't understand what you mean since you quite obviously do have a use case for setting idx manually (your Device.fromJson factory constructor). If you instead mean that you don't have a use case for code from outside the Device class to manually set idx, then you can add a private constructor with the idx parameter and a public one without:
class Device {
String idx;
String id;
String name;
String icon;
int order;
Device({
required String id,
required String name,
String icon = 'none',
int order = -1,
}) : this._(
idx: uuid.v1(),
id: id,
name: name,
icon: icon,
order: order,
);
Device._({
required this.idx,
required this.id,
required this.name,
required this.icon,
required this.order,
});
// Creates a device object from a JSON string
factory Device.fromJson(Map<String, dynamic> jsonData) {
return Device._(
idx: jsonData['idx'],
id: jsonData['id'],
name: jsonData['name'],
icon: jsonData['icon'],
order: jsonData['order']);
}
}
or, since idx isn't final, .fromJson could assign it a value:
factory Device.fromJson(Map<String, dynamic> jsonData) {
return Device(
id: jsonData['id'],
name: jsonData['name'],
icon: jsonData['icon'],
order: jsonData['order'])
..idx = jsonData['idx'];
}
Related
I have code like this
class Human<T> { // <--- this is the superclass
final String name;
final T belongings;
Human({
required this.name,
required this.belongings,
});
}
class Athlete<T> extends Human { // <--- this is the subclass
final String sportType;
Athlete({
required String name,
required belongings,
required this.sportType,
}) : super(name: name, belongings: belongings);
}
final messi = Athlete<List<String>>( // <--- past List<String> as generic
name: "Lionel Messi",
belongings: ["Jersey", "Shoes"],
sportType: "Football",
);
final belonging = messi.belongings; // <-- the data type is dynamic, not List<String>
as you can see, I want belongings property to be generic, but after I pass List<String> as a generic type when instantiating an Athlete model, I still get dynamic data type like the image below, I expect it will be List<String> .
You need to add the <T> to Human, so it's extends Human<T>.
You also need to type the required belongings, parameter, either as
required T belongings,, or by using the new super-parameters feature and make it required super.belongings.
That is:
class Human<T> {
final String name;
final T belongings;
Human({
required this.name,
required this.belongings,
});
}
class Athlete<T> extends Human<T> { // <--- added <T>
final String sportType;
Athlete({
required super.name, // <--- used super.name
required super.belongings, // <--- used super.longings
required this.sportType,
}); // <--- no constructor call needed when it's all "super." parameters.
}
final messi = Athlete<List<String>>(
name: "Lionel Messi",
belongings: ["Jersey", "Shoes"],
sportType: "Football",
);
you have to declare belongings as a List within the human class like this:
final List<String> belongings;
Human({required this.belongings})
I am trying to convert this dart file here to use generics and I get the following error when trying to initialize an empty list in constructor.
Constant list literals can't include a type parameter as a type
argument, such as 'T'. Try replacing the type parameter with a
different
How can I create an empty list in this case. Below code can elaboreate my problem even more
old file
enum PostStatus { initial, success, failure }
class PostState extends Equatable {
const PostState({
this.status = PostStatus.initial,
this.posts = const <Post>[],
this.hasReachedMax = false,
});
final PostStatus status;
final List<Post> posts;
final bool hasReachedMax;
PostState copyWith({
PostStatus status,
List<Post> posts,
bool hasReachedMax,
}) {
return PostState(
status: status ?? this.status,
posts: posts ?? this.posts,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
);
}
#override
List<Object> get props => [status, posts, hasReachedMax];
}
new file
class PagedState<T> extends Equatable {
const PagedState({
this.status = PagedStatus.initial,
this.items = const <T>[], //ERROR HERE
this.hasReachedMax = false,
});
final PagedStatus status;
final List<T> items;
final bool hasReachedMax;
PagedState copyWith({
PagedStatus status,
List<T> items,
bool hasReachedMax,
}) {
return PagedState(
status: status ?? this.status,
items: items ?? this.items,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
);
}
#override
List<Object> get props => [status, items, hasReachedMax];
}
As the error says, constant list literals can't use a type parameter, so you must use a non-const literal: <T>[].
However, since it's a default argument and default arguments must be constants, that won't work either. You either will need to:
Use a constant sentinel value as the default and replace it with the desired default later:
const PagedState({
List<T> items = null,
this.hasReachedMax = false,
}) : items = items ?? <T>[];
Use const [] without the explicit type parameter and let automatic type conversions do the work for you.
I've been trying to figure out how to use a named constructor to construct super and sub classes from JSON. Below is my example with some notes on what I've tried in the fromJson method body. Can anyone point me in the right direction? Thanks!
class Item {
final String name;
final int id;
final String image;
final double price;
final bool available;
Item(this.name, this.id, this.image, this.price, this.available);
Item.fromJson(Map<String, dynamic> json)
: name = json['name'],
id = json['id'],
image = json['image'],
price = json['price'],
available = json['available'];
}
class CartItem extends Item {
final int quantity;
CartItem({
#required this.quantity,
#required name,
#required id,
#required price,
#required image,
#required available
}): super(id, name, image, price, available)
CartItem.fromJson(Map<String, dynamic> json)
: quantity = json['quantity'],
// for whatever ever reason, super here seems to refer to CartItem
// so this doesn't work
super.name = json['name'],
// calling 'name' without the super doesn't work either
name = json['name']
}
You should use a the super's fromJson constructor in the child class's fromJson constructor. You can pass the Map in the child directly to the super without issues. Ex:
CartItem.fromJson(Map<String, dynamic> json)
: quantity = json['quantity'],
super.fromJson(json);
I am passing a Function as an optional parameter to the constructor but I can't assign a default value.
void main() {
Person p = Person();
print(p.foo('Hello'));
}
class Person {
final String Function(String) foo;
Person({this.foo});
}
now trying to assign a default value: Person({this.foo = (val) {return val;});
produces the error: Error: Not a constant expression. I am aware the parameter must be const but using const or even static infront of (val) {return val;} does not work.
Does anyone have an idea how to solve this problem?
You can try this:
void main() {
Person pLower = Person(foo: (a) => a.toLowerCase());
print(pLower.foo('Hello'));
Person pDefault = Person();
print(pDefault.foo('Hello'));
}
class Person {
static String defaultFoo(String a) => a.toUpperCase();
final String Function(String) foo;
Person({this.foo = defaultFoo});
}
Output
hello
HELLO
You can only use constant values (aka. compile-time constants) as default values.
You cannot create a constant function literal, so there is no way to write the function in-line in the constructor.
However, references to top-level or static functions are constants, so you can declare the default value function as a static function or top-level function.
void main() {
Person p = Person();
print(p.foo('Hello')); // Prints "Hello"
}
class Person {
final String Function(String) foo;
Person({this.foo = _identity});
static String _identity(String value) => value;
}
// or as top-level.
// String _identity(String value) => value;
You can (and should) choose to make the function public if the default value is on an instance method, and you expect anyone to extend or implement your class. In that case, they need to declare the same default value.
Another option, which is often at least as useful, is to not use a default value, but replace a null before using the value:
class Person {
final String Function(String) foo;
Person({String Function(String) foo}) : foo = foo ?? _identity;
static String _identity(String value) => value;
}
or even using a non-constant value:
class Person {
final String Function(String) foo;
Person({String Function(String) foo}) : foo = (foo ?? (String x) => x);
}
For a constructor, it makes very little difference. If it was an instance method instead, using ?? to replace null avoids subclasses having to use the exact same function as default value.
Personally I recommend always using ?? instead of a default value. It's more flexible since it allows non-constant values. For non-function default values, you'll have to document the default behavior instead of just letting the dartDoc show {int x = 42}, but for functions, you'll have to document them anyway.
In Java when you are defining an enum, you can do something similar to the following, i.e. add members to an enum. Is this possible in Dart?
enum Foo {
one(1), two(2);
final num value;
Foo(this.value);
}
Starting with Dart 2.6 you can define extensions on classes (Enums included).
enum Cat {
black,
white
}
extension CatExtension on Cat {
String get name {
switch (this) {
case Cat.black:
return 'Mr Black Cat';
case Cat.white:
return 'Ms White Cat';
default:
return null;
}
}
void talk() {
print('meow');
}
}
Example:
Cat cat = Cat.black;
String catName = cat.name;
cat.talk();
Here's one more live example (uses a constant map instead of a switch):
https://dartpad.dartlang.org/c4001d907d6a420cafb2bc2c2507f72c
Dart Enhanced Enum Classes
Starting with Dart 2.17, the Enhanced Enum Classes feature has been introduced. With that, the example from the question would look like this:
enum Foo {
one(1),
two(2);
const Foo(this.value);
final num value;
}
Now, you can just use the enum class like this:
void main() {
const foo = Foo.one;
print(foo.value); // 1
}
Note that you need to update your SDK constraint as the feature requires Dart 2.17:
environment:
sdk: '>=2.17.0-0 <3.0.0'
Adding members
With enhanced enums, you can add any member to your enum as long as the constructor is const.
This also means that you can add getters or methods to existing enums, for example:
enum Cake {
cherry,
apple,
strawberry;
String get description => '$name cake';
}
Generics
Enhanced enum classes also enable you to use generics for you enums. If you combine this with members, you can do the following:
enum Bar<T extends Object> {
number<int>(42),
name<String>('creativecreatorormaybenot'),
baz(true); // Note that type inference also works.
const Bar(this.value);
final T value;
}
Mixins and interfaces
In addition to declaring members, you can also mixin mixins and implement interfaces with enhanced enums and override any missing implementations.
mixin Foo {
int get n;
}
abstract class Bar {
void printNumber();
}
enum Baz with Foo implements Bar {
one(1),
two(2);
const Baz(this.n);
#override
final int n;
#override
void printNumber() => print(n);
}
Multiple arguments
Finally note that even if I did not make use of it in any of the examples above, it is possible to have an arbitrary number of arguments (and an initializer list):
enum Foo {
bar(42, description: 'The answer to life, the universe, and everything.'),
baz(0, enabled: false, description: 'noop');
const Foo(
int number, {
this.enabled = true,
required this.description,
}) : n = number;
final int n;
final bool enabled;
final String description;
}
Dart enums are used only for the simplest cases. If you need more powerful or more flexible enums, use classes with static const fields like shown in https://stackoverflow.com/a/15854550/217408
This way you can add whatever you need.
Nope. In Dart, enums can only contain the enumerated items:
enum Color {
red,
green,
blue
}
However, each item in the enum automatically has an index number associated with it:
print(Color.red.index); // 0
print(Color.green.index); // 1
You can get the values by their index numbers:
print(Color.values[0] == Color.red); // True
See: https://www.dartlang.org/guides/language/language-tour#enums
It may not be "Effective Dart" , I add a static method inside a Helper class ( there is no companion object in Dart) .
In your color.dart file
enum Color {
red,
green,
blue
}
class ColorHelper{
static String getValue(Color color){
switch(color){
case Color.red:
return "Red";
case Color.green:
return "Green";
case Color.blue:
return "Blue";
default:
return "";
}
}
}
Since the method is in the same file as the enum, one import is enough
import 'package:.../color.dart';
...
String colorValue = ColorHelper.getValue(Color.red);
extension is good, but it cannot add static methods. If you want to do something like MyType.parse(string), consider using a class with static const fields instead (as Günter Zöchbauer suggested before).
Here is an example
class PaymentMethod {
final String string;
const PaymentMethod._(this.string);
static const online = PaymentMethod._('online');
static const transfer = PaymentMethod._('transfer');
static const cash = PaymentMethod._('cash');
static const values = [online, transfer, cash];
static PaymentMethod parse(String value) {
switch (value) {
case 'online':
return PaymentMethod.online;
break;
case 'transfer':
return PaymentMethod.transfer;
break;
case 'cash':
return PaymentMethod.cash;
default:
print('got error, invalid payment type $value');
return null;
}
}
#override
String toString() {
return 'PaymentMethod.$string';
}
}
I found this much handier than using a helper function.
final method = PaymentMethod.parse('online');
assert(method == PaymentMethod.online);
I did this (inspired form the accepted answer by #vovahost)
enum CodeVerifyFlow {
SignUp, Recovery, Settings
}
extension CatExtension on CodeVerifyFlow {
String get name {
return ["sign_up", "recovery", "settings"][index];
}
}
// use it like
CodeVerifyFlow.SignUp.name
thank me later!
There's an upcoming feature in Dart known as enhanced enums, and it allows for enum declarations with many of the features known from classes. For example:
enum Blah {
one(1), two(2);
final num value;
const Blah(this.value);
}
The feature is not yet released (and note that several things are not yet working), but experiments with it can be performed with a suitably fresh version of the tools by passing --enable-experiment=enhanced-enums.
The outcome is that Blah is an enum declaration with two values Blah.one and Blah.two, and we have Blah.one.value == 1 and Blah.two.value == 2. The current bleeding edge handles this example in the common front end (so dart and dart2js will handle it), but it is not yet handled by the analyzer.
As an improvement on the other suggestions of using Extensions, you can define your assigned values in a list or map, and the extension will be concise.
enum Numbers {
one,
two,
three,
}
// Numbers.one.value == 1
// Numbers.two.value == 2
// Numbers.three.value == 3
example with list
extension NumbersExtensionList on Numbers {
static const values = [1, 2, 3];
int get value => values[this.index];
}
example with map
extension NumbersExtensionMap on Numbers {
static const valueMap = const {
Numbers.one: 1,
Numbers.two: 2,
Numbers.three: 3,
};
int get value => valueMap[this];
}
Note: This approach has the limitation that you can not define a static factory method on the Enum, e.g. Numbers.create(1) (as of Dart 2.9). You can define this method on the NumbersExtension, but it would need to be called like NumbersExtension.create(1)
For String returns :
enum Routes{
SPLASH_SCREEN,
HOME,
// TODO Add according to your context
}
String namedRoute(Routes route){
final runtimeType = '${route.runtimeTypes.toString()}.';
final output = route.toString();
return output.replaceAll(runtimeType, "");
}
You can add extra fields and methods with my package enum_extendable.
It generates extensions on enum, so you can use your enum values in the similar way to instances of a regular Dart class.
For example, if you have enum MathOperator { plus, minus } the symbol and calculate(...) can be added to it.
So, the enum can be used in such way:
final n1 = 1;
final n2 = 2.0;
MathOperator.values.forEach((operator) {
print('$n1 ${operator.symbol} $n2 = ${operator.calculate(n1, n2)}');
});
Usage:
Add dependencies to pubspec.yaml:
dependencies:
enum_extendable_annotation:
dev_dependencies:
build_runner:
enum_extendable_generator:
Install these dependencies:
# Dart
pub get
# Flutter
flutter packages get
Add imports to your enum file:
import 'package:enum_extendable_annotation/enum_extendable_annotation.dart';
part '<your enum file name>.enum_extendable.g.dart';
Create a PODO class with fields and methods you wanted.
Create a map with instances of this PODO class for each enum value.
Annotate elements:
the enum with #ExtendableEnum();
the PODO class - #ExtendableEnumPodo();
the map of PODO instances - #ExtendableEnumValues().
Run code generator:
if your package depends on Flutter:
flutter pub run build_runner build
if your package does not depend on Flutter:
dart pub run build_runner build
The file with extensions should be generated.
Example of the enum file:
import 'package:enum_extendable_annotation/enum_extendable_annotation.dart';
part 'math_operator.enum_extendable.g.dart';
#ExtendableEnum()
enum MathOperator { plus, minus }
#ExtendableEnumPodo()
class _MathOperatorPodo {
final String symbol;
final num Function(num, num) calculate;
_MathOperatorPodo(
this.symbol,
this.calculate,
);
#ExtendableEnumValues()
static final Map<MathOperator, _MathOperatorPodo> _values = {
MathOperator.plus: _MathOperatorPodo(
'+',
(n1, n2) => n1 + n2,
),
MathOperator.minus: _MathOperatorPodo(
'-',
(n1, n2) => n1 - n2,
),
};
}