Although I thought being familiar with programming language Dart, i stumbled upon this syntax in an example for Bloc:
class AuthenticationState extends Equatable {
const AuthenticationState._({
this.status = AuthenticationStatus.unknown,
this.user = User.empty,
});
const AuthenticationState.unknown() : this._();
const AuthenticationState.authenticated(User user)
: this._(status: AuthenticationStatus.authenticated, user: user);
const AuthenticationState.unauthenticated()
: this._(status: AuthenticationStatus.unauthenticated);
final AuthenticationStatus status;
final User user;
#override
List<Object> get props => [status, user];
}
I know how to define a class constant and a const constructor.
However, why is the classname prefixed here everywhere?
const AuthenticationState._({
this.status = AuthenticationStatus.unknown,
this.user = User.empty,
});
That's a named constructor. In dart you can define constructors in two ways, either using ClassName or ClassName.someOtherName.
Eg: Consider you have a class called person with 2 variables, name and carNumber. Everyone has a name but carNumber is not necessary. In that situation, if you implement the default constructor, you have to initialise it like:
Person("Name", "");
So if you want to add some syntactic sugar, you can add a named constructor like below:
class Person {
String name;
String carNumber;
// Constructor creates a person with name & car number
Person(this.name, this.carNumber);
// Named constructor that only takes name and sets carNumber to ""
Person.withOutCar(String name): this(name, "");
// Named constructor with no arguments, just assigns "" to variables
Person.unknown(): this("", "");
}
And you can initialise the object like:
Person.withOutCar("Name");
In the above example the named constructors are being redirected to your actual constructor with pre-defined default values.
You can read more about named constructors here: Dart Language Tour
As the above answer was not really clear/complete to me, when I stumbled upon this, please me let me know, whether my understanding is correct:
const AuthenticationState._({
this.status = AuthenticationStatus.unknown,
this.user = User.empty,
});
The constructor using ._() is private to the library, I guess this is a security feature, so you can create an instance only from within the library.
const AuthenticationState.unknown() : this._();
If the object is created as .unknown, the default constructor from above is called (as private), such using the default values. In the other 2 cases, one or both default values are replaced.
Related
I am trying to make a plugin for authentication. It will act like a "wrapper" (but with additional functionality for each plugin) for different already existing packages for different platforms.
By the additional functionality I mean
Web implementation already has state management
Android implementation is AppAuth, so here I need to add my own state management
I am trying to use the PlatformInterface way, but I cannot find any guide how to make it instantiable with constructor params.
Let's say I have this code:
abstract class KeycloakAuth extends PlatformInterface {
KeycloakAuth()
: super(token: _token);
static final Object _token = Object();
static late KeycloakAuth _instance = KeycloakAuth._setPlatform();
static KeycloakAuth get instance => _instance;
static set platform(KeycloakAuth instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
factory KeycloakAuth._setPlatform() {
if (Platform.isAndroid) {
return KeycloakAuthAppAuth();
} else {
throw UnimplementedError('The current platform ${Platform.operatingSystem} is not supported.');
}
}
}
This would work, but in my app I would like to do this:
final _keycloakAuth = KeycloakAuth(keycloakUrl: 'xxx', clientId: 'yyy');
If I simply tried to add params like this:
abstract class KeycloakAuth extends PlatformInterface {
final String keycloakUrl;
final String clientId;
KeycloakAuth({
required this.keycloakUrl,
required this.clientId,
})
: super(token: _token);
...
I wouldn't be able to pass them inside the _setPlatform() method since this is static method.
Also KeycloakAuthAppAuth extends KeycloakAuth, so I would need to pass those parameters back and forth, like instantiate KeycloakAuth, this instantiates KeycloakAuthAppAuth which needs to pass those parameters back via super()...
Any tips for this?
I know I can make a method initialize({String keycloakUrl, String clientId}) with my parameters, but I would still like to know if it's possible to make it using the constructor.
I gathered the following understanding for creating a singleton in dart with params
class Foo extends ChangeNotifier {
late String channel;
void instanceMemberFunction () {
print('Foo created with channel $channel')
}
static final Foo _instance = Foo._internal();
Foo._internal() {
instanceMemberFunction();
}
factory Foo({
required String channel
}) {
_instance.channel = channel;
return _instance;
}
}
and I am calling the instance like so
Foo({channel: "bar"})
Now I want to have some working that if I use
Foo({channel: "baz"})
Then a new instance is created and it's okay in that case to destroy the old one. How can I achieve this in dart?
It seems like you've copied some existing example for creating a singleton without fully understanding what it's doing and why. The core parts are:
The single instance is stored in a global or static variable.
The class has one or more public factory constructors that returns that global/static variable, initializing it if necessary.
All other constructors for the class are private to force consumers to go through the factory constructors.
Therefore, if you want your factory constructor to replace its singleton based on its argument, you need to:
Make your factory constructor check if the argument is appropriate for the existing instance. If it is, return the existing instance. If not (or if there is no existing instance), create and return a new instance.
Since you need to check if the existing instance is initialized, make it nullable. (You alternatively could initialize it to a non-null sentinel value, e.g. Foo._internal(channel: '').
Pass the argument along to the private constructor.
class Foo extends ChangeNotifier {
final String channel;
void instanceMemberFunction () {
print('Foo created with channel $channel');
}
static Foo? _instance;
Foo._internal({required this.channel}) {
instanceMemberFunction();
}
factory Foo({required String channel}) {
if (channel != _instance?.channel) {
_instance = Foo._internal(channel: channel);
}
return _instance!;
}
}
Note that this implementation will create a new object if the constructor argument changes, which isn't very singleton-like. Depending on what you want to do, you could:
Return a new object (which could allow multiple simultaneous instances).
Return the existing object.
Return the existing object, but mutate it with the constructor argument.
My Code
class Book {
String title;
String author;
int numOfPages;
Book(String title, String author, int pages) {
this.title = title;
this.author = author;
this.numOfPages = pages;
}
}
void main() {
Book bk = Book("Modern Operating Systems", "S.Tannabeaum", 1250);
print(bk.title);
}
Hey, I'm pretty a newbie to dart and programming. Here actually I wanted to make a class and it's constructor and three instances within it. And when I wanted to make an object from this class, I caught up with this error!
My code's error message!
I think something is wrong with my code, any help would be appreciable:)
There are two problems in your code. First, constructors in Dart has two "phases" where you first initialize the object and then runs the constructor body before returning the object to the caller of the constructor.
That means that you are here creating a Book object first without setting the three variables. Yes, you are setting these variables later in the constructor body but at that time it is too late.
The next problem is that if you are not setting value for a variable in Dart it will always default to the value null. With Dart 2.12, we got non-nullable types by default (NNBD) which mean all types in Dart does not allow the value null unless specified. You specify the validity of the null value by typing a ? after the name of the type. E.g. String? allows a variable to point to a String object, or null.
In this case, we don't need to specify nullable types since the problem is mostly you need to move the initialization of the variables from the constructor body in to initialization phase of the object like this:
class Book {
String title;
String author;
int numOfPages;
Book(String title, String author, int pages)
: this.title = title,
this.author = author,
this.numOfPages = pages;
}
The same can be rewritten as the following which is also the recommended way to do it:
class Book {
String title;
String author;
int numOfPages;
Book(this.title, this.author, this.numOfPages);
}
Since we are here just directly referring to each field we want to give a value. Dart will then automatically assign the values with the parameters from the constructor.
If your constructor takes a lot of arguments, it might be more readable to use named arguments. The required keyword here means that we most provide a given named parameter. If not specified, the named argument is optional (which means we most provide a default value or allow null for our parameter to be valid):
class Book {
String title;
String author;
int numOfPages;
Book({
required this.title,
required this.author,
required this.numOfPages,
});
}
void main() {
final book = Book(
title: "Modern Operating Systems",
author: "S.Tannabeaum",
numOfPages: 1250,
);
print(book.title); // Modern Operating Systems
}
So after dart made new keyword optional,
we can initialize an object with exact same syntax but different internal implementation.
class Color {
int r = 0, g = 0, b = 0;
Color({this.r, this.b, this.g});
//Named constructors
Color.red() //Implementation
Color.cyan() //Implementation
// Static Initializers
static Color red() => //Initialze with parameter
static Color cyan() => //Initialze with parameter
}
We can use them like this regardless of being it a named constructor or static method:
Color red = Color.red();
Color cyan = Color.cyan();
What is the place to use each of them?
In practice there is little difference between a factory constructor and a static method.
For a generic class, it changes where you can (and must) write a type parameter:
class Box<T> {
T value;
Box._(this.value);
factory Box.withValue(this.value) => Box<T>._(value);
static Box<T> fromValue<T>(T value) => Box<T>._(value);
}
...
var box1 = Box<int>.withValue(1);
var box2 = Box.fromValue<int>(2);
So, for generic classes, factory constructors are often what you want. They have the most pleasant syntax.
For non-generic classes, there is very little difference, so it's mainly about signaling intent. And deciding which category the name goes into in the DartDoc.
If the main objective of the function is to create a new object, make it a constructor.
If the main objective is to do some computation and eventually return an object (even if it's a new object), make it a static function.
That's why parse methods are generally static functions.
In short, do what feels right for your API.
Constructors and static functions are different. You usually create a named constructor that returns an instance of an object with some predefined values. For example, you have a class called Person which stores Name and Job. You can create this named constructor Person.doctor(name) which you will return a Person object with Job = 'doctor'
class Person{
final name;
final job;
Person(this.name, this.job);
Person.doctor(this.name, {this.job = "doctor"});
}
Static functions or variable persists on all the instance of a class. Let us say, Person has a static variable called count. You increment the count variable whenever an instance of Person is created. You can call Person.count anywhere later in your code to get the value of count (Number of instances of Person)
class Person{
final name;
final job;
static int count;
Person(this.name, this.job){
count++;
}
Person.doctor(this.name, {this.job = "doctor"});
}
Another very useful feature of static class methods is that you can make them asynchronous, i.e. wait for full initialisation in case this depends on some asynchronous operation:
Future<double> getCurrentPrice(String ticker) async {
double price;
// for example, fetch current price from API
price = 582.18;
return price;
}
class Stock {
String ticker;
double currentPrice=0.0;
Stock._(this.ticker);
static Future<Stock> stockWithCurrentPrice(String ticker) async {
Stock stock = Stock._(ticker);
stock.currentPrice = await getCurrentPrice (ticker);
return stock;
}
}
void main() async {
Stock stock = await Stock.stockWithCurrentPrice('AAPL');
print ('${stock.ticker}: ${stock.currentPrice}');
}
Another benefit of the distinction between named constructor and static function is that in the documentation generated the function will be either filed in the construction section or the methods section, which further makes it's intentions clearer to the reader.
A person looking for a constructor in the constructor section of the documentation will easily discover the named constructors as opposed to having to also dig through the static functions section too.
I have API communication service in my Flutter app with 10+ different services, and 100+ API calls that heed to parse data. In order to reuse code I've decided to create some common parsing code that is going to parse data from API:
ApiResponse handleObjectResponse({
#required http.Response serverResponse,
#required Function objectConstructor,
}) {
if (serverResponse.statusCode == 200) {
dynamic responseObject = objectConstructor(json.decode(serverResponse.body));
return ApiResponse(responseObject: responseObject);
} else {
ApiError error = responseHasError(serverResponse.body);
return ApiResponse(error: error);
}
}
This way I am able to parse JSON object from API in a reusable way no matter what the Object class is, just by passing constructor function to this method.
When I call this method in any of the Services I've created for fetching data like this:
handleObjectResponse(serverResponse: response, objectConstructor: ChartData.fromJson);
I get error: The getter 'fromJson' isn't defined for the class 'ChartData'.
Try importing the library that defines 'fromJson', correcting the name to the name of an existing getter, or defining a getter or field named 'fromJson'.
Where I think the problem is is in this model class and factory statement, but I don't know how to fix it:
class ChartData {
List<ChartDataPoint> points;
ChartData({
this.points,
});
factory ChartData.fromJson(Map<String, dynamic> json) {
List jsonPoints = json["data"];
return ChartData(
points: List.generate(jsonPoints.length,
(i) => ChartDataPoint.fromJsonArray(jsonPoints[i])));
}
}
You cannot pass constructors as functions. You need to create a function what will call the constructor instead:
(int a) => Foo(a);
Just a 2022 update: since 2.15 it's possible by Class.new, see the complete issue: https://github.com/dart-lang/language/issues/216.
class A {
final String a;
const A(this.a);
#override
String toString() => 'A($a)';
}
class B {
final String b;
const B(this.b);
#override
String toString() => 'B($b)';
}
void main() {
final List<Object Function(String)> constructors = [A.new, B.new];
for (final Object Function(String) constructor in constructors) {
final Object instance = constructor('My Constructor Parameter');
if (instance is A) {
print(instance.toString());
}
}
}
Note that if you're using named params, both class constructors must have the same param name, otherwise the constructor signatures won't match and then it will generate this static error:
The element type X can't be assigned to the list type Y.