Can this class be reconfigured to accept named parameters in Flutter? - flutter

Background code
I have a class called Result that I'm using to pass on some error codes and error messages to an error screen.
class Result<T>{
Result._();
factory Result.loading(T msg) = LoadingState<T>;
factory Result.success(T value) = SuccessState<T>;
factory Result.error(T title, T msg, T errorcode) = ErrorState<T>;
}
class ErrorState<T> extends Result<T> {
final T title;
final T msg;
final T errorcode;
ErrorState(this.title, this.msg, this.errorcode) : super._();
//final T msg;
}
This class is called as follows:
return Result.error("Error","Status code not 200", 1);
My problem
Functionally it all works great.
The problem is I see myself in the future having to refer back to my class Result code to remember what each field represents which may become even more problematic if I want to add in more fields down the line
I'd rather convert this so that the fields are named
My question
Can this class be converted so that it is called like this (with a name describing the parameter):
return Result.error(title:"Error", msg:"Status code not 200", errorcode:1);

If you want to change Result.error to accept named parameters without breaking existing call sites, you can't do that. Parameters can be either positional or named, but not both.
If you're okay with breaking existing call sites, then you can just make them required named parameters. Since you're using redirecting constructors, you either will need to:
Change the signature of the redirectee constructor (in your case, ErrorState) to exactly match that of Result.error:
factory Result.error(
{required T title, required T msg, required T errorcode}) = ErrorState<T>;
...
ErrorState({required this.title, required this.msg, required this.errorcode})
: super._();
Or change Result.error to be a non-redirecting constructor:
factory Result.error(
{required T title, required T msg, required T errorcode}) {
return ErrorState<T>(title, msg, errorcode);
}
(If you do want to avoid breaking existing callers, you could add a separate named constructor (or static method) that uses named parameters, and you could optionally deprecate the old constructor.)
As an aside, it does not make sense that Result and ErrorState are generic classes. Do you really expect title and msg to not be Strings? Does it really make sense for the type of title, msg, and errorcode to be the same? (With the example you've shown, you'll end up with Result<Object>, which defeats the point of using a generic class.)

Related

How to stub getter or a method of freezed dataclass in Flutter / Dart

I have a freezed data class with some fields. A getter method returns nested elements of one attribute for easier access.
#freezed
class Airport with _$Airport {
const Airport._();
const factory Airport({
required String identifier
required String type,
required List<Runway> runways,
}) = _Airport;
List<Ils> get allIls => runways
.map((runway) => runway.allIls)
.expand((ils) => ils)
.toList();
}
I use the Airport class inside a test where the getter allIls is called. I don't want to fill runways with legit data, instead I directly want to stub the getter method allIls and have it return a list of objects.
What I tried:
Mock the Airport class:
class MockAirport extends Mock implements Airport {}
My test:
test('',
() async {
final airport = MockAirport();
final ilsList = [ils1, il2];
when(airport.allIls).thenReturn(ilsList);
expect(...);
});
However, this gives me the following error:
type 'Null' is not a subtype of type 'List<Ils>'MockAirport.allIls
I have also tried a "normal" method instead of a getter, with the same results:
List<Ils> allIls2() => runways
.map((runway) => runway.allIls)
.expand((ils) => ils)
.toList();
...
when(airport.allIls2.call()).thenReturn(ilsList);
Any idea what I could do?
It looks like you missed a step in setting up your Mocks.
You need to add the GenerateMocks or GenerateNiceMocks attribute somewhere in your library to auto-generate your Airport mocks. I like to have the attribute in the same file as my test, though that can lead to repeated mocks throughout your test files. After you have the attribute, you generate the mock using build_runner.
Lastly, in your second example with the "normal" method, you don't need to add the .call() to your when statement. In fact, adding that will cause the call to fail. It should instead just be when(airport.allIls2()).thenReturn(ilsList).

Initialize all Flutter class instances with same ID and no need to provide argument

I am working on a flutter app and wanted one of my classes to have the same id property. This is because I can have either an event or an exception, but I would like the same function to manage both. To do this, I have a switch statement that checks res.id to determine which type of event it is. The response can either be an Event instance or an Exception instance.
Exception is an import class and I'd rather not have to wrap it in an Event instance so I thought I could just hardcode id = EventIds.error. This way every error will have an id that matches an error event - thus it can be accessed and dealt with in the original switch statement.
My issue is I don't want to have to go all throughout my code and add a new argument to each instantiation. See the code below.
Exception.dart
class Exception {
/// Always initialize with id that is error id
/// This is for onCaptureEvent
int id = EventIds.error;
int code = 0;
String message = 'no error';
String? method;
String? details;
Exception(id, this.code, this.message, [this.method, this.details]);
}
Instantiation Current
Exception ex = new Exception(-93, 'Unable to validate')
I want to be able to have every instance of Exception have an id of EventIds.error WITHOUT having to go through every instantiation in my code and add it like so:
Exception ex = new Exception(EventIds.error, -93, 'Unable to validate')
Is this achievable in Flutter?
It was really simple. I just needed to write out my Exception class like so:
Exception.dart
class Exception {
/// Always initialize with id that is error id
/// This is for onCaptureEvent
int id = EventIds.error;
int code = 0;
String message = 'no error';
String? method;
String? details;
Exception(this.code, this.message, [this.method, this.details]);
}
This way the instances will always use the default value for id. This is also safer bc now the user cannot change the ID if they wanted to provide another argument (Exception(supplied_id, code, message)) because it will throw a syntax error saying the second argument is supposed to be a string.

Deserialise JSON response from API into type in Dart

I'm going out of my mind trying to figure this out because it's such a simple thing to do and there's no information on it anywhere.
I have an API call that is returning a Student which is defined like this
class Student {
String photo;
String upn;
String fullName;
String studentClass;
String studentYear;
}
I'm using the dart http library to make a call to an API that returns an array of a number of students. All I want to do is deserialise the string returned by the API into a typed list so that I can actually do something with it.
studentFuture.then((res) {
log(res.body.toString());
List<dynamic> dynamicList = jsonDecode(res.body);
var student = dynamicList[0] as Student;
});
Trying to cast it to Student using as doesn't work, I just get this
Unhandled Exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Student' in type cast
All I want is to be able to do something like this like you can in C#
var obj = DeserialiseJson<Type>(jsonString);
No matter what I try I can't get the string to be deserialised into an object, how do I do this?
You can't do this in Dart/Flutter in the way that you want. This is because in order to accomplish this functionality in C#, Java, or any other strongly-typed language that supports this feature, the language uses reflection under the hood to map JSON fields to object fields.
Dart also has reflection functionality in the dart:mirror library. However, this library is disabled in Flutter due to its tendency to create large app footprints due to all the debug information for various types and fields and things needing to be included in the compiled binary. (That's the very simple explanation: the official reason given by the Flutter team goes into more detail.)
What this means is when you deserialize a JSON string, you get an object that is essentially a List<dynamic> or Map<String, dynamic> (depending on the JSON string in question; it may even just be some primitive value). Assuming the latter, you cannot directly convert between a Map and a Student because they are completely different types with nothing in common. There is no implicit way to take the Map and generate a Student because the reflection tools necessary to do so don't exist within a Flutter app. As a result, you get an error saying you cannot convert a Map to a Student, as you can see.
To work around this, you are encouraged to add a toJson/fromJson method to your model class so you can explicitly generate an instance of the class from a deserialized JSON string:
class Student {
String photo;
String upn;
String fullName;
String studentClass;
String studentYear;
factory Student.fromJson(Map<String, dynamic> jsonData) {
return Student()
..photo = jsonData['photo']
..upn = jsonData['upn']
..fullName = jsonData['fullName']
..studentClass = jsonData['studentClass']
..studentYear = jsonData['studentYear'];
}
}
And use it like so:
studentFuture.then((res) {
log(res.body.toString());
List<dynamic> dynamicList = jsonDecode(res.body);
var student = Student.fromJson(dynamicList[0]);
});
This is all well and good, but if you have more than a few model classes you will quickly realize that adding this boilerplate code to every model class gets really tedious really quick. That's why various packages exist to try and alleviate the burden with automatic code generation, such as json_serializable and freezed.
TL;DR: What you are asking for is not currently possible in Flutter Dart, and that isn't likely to change any time soon. You need to either write a manual factory constructor to make the conversion or use a code-gen package that generates the constructors for you.
Have a look at https://pub.dev/packages/json_serializable which, through code generation, will give you methods like
#JsonSerializable()
class Student {
String photo;
String upn;
String fullName;
String studentClass;
String studentYear;
Student({this.photo, this.upn, this.fullName, this.studentClass, this.studentYear});
factory Student.fromJson(Map<String, dynamic> json) => _$StudentFromJson(json);
Map<String, dynamic> toJson() => _$StudentToJson(this);
}
Then you can do:
var list = jsonDecode(res.body);
for (var map in list) {
print(Student.fromJson(map)); // Student class
}

Dart Class with ":" vs Dart Class without [duplicate]

I have a class that I am creating that looks like this:
class Movie {
final String title, posterPath, overview;
Movie(this.title, this.posterPath, this.overview);
Movie.fromJson(Map json) {
title = json["title"];
posterPath = json["poster_path"];
overview = json['overview';
}
}
I am getting a warning that says that "The final variables 'overview', 'posterPath', & '1' more must be initialized. There are also warnings around each variable saying 'title' can't be used as a setter because it is final.
When I write the constructor using this syntax, the warnings go away:
Movie.fromJson(Map json)
: title = json["title"],
posterPath = json["poster_path"],
overview = json['overview'];
What exactly is going on here?
Dart objects must be fully initialized before anyone gets a reference to the new object. Since the body of a constructor can access this, the object needs to be initialized before entering the constructor body.
To do that, generative Dart constructors have an initializer list, looking similiar to C++, where you can initialize fields, including final fields, but you cannot access the object itself yet. The syntax:
Movie.fromJson(Map json)
: title = json["title"],
posterPath = json["poster_path"],
overview = json['overview'];
uses an initializer list (the list of assignments after the :) to initialize the final instance variables title, posterPath and overview.
The first constructor uses an "initializing formal" this.title to directly put the parameter into the field.
The constructor
Movie(this.title, this.posterPath, this.overview);
is effectively a shorthand for:
Movie(String title, String posterPath, String overview)
: this.title = title, this.posterPath = posterPath, this.overview = overview;
Your constructor can combine all of these and a body:
Movie(this.title, this.posterPath, String overview)
: this.overview = overview ?? "Default Overview!" {
if (title == null) throw ArgumentError.notNull("title");
}
(A const constructor cannot have a body, but it can have an initializer list with some restrictions on the allowed expressions to ensure that they can be evaluated at compile-time).
Dart separates properties initialization from the constructor body.
A constructor has 3 parts :
the name/parameters definition
properties initialization/super call/asserts
A body, similar to a function immediately run on construction
Both the initialization and body parts are optional.
final variables must be initialized on the first 2 parts. They cannot be initialized inside the body.
A full constructor will look like the following :
MyClass(int value)
: assert(value > 0),
property = value,
super();
{
print("Hello World");
}
The main purpose of this initializer part is for body-less constructors which allows const constructors, a dart specific feature. See How does the const constructor actually work? for more details on these.
I just found some documentation around this, & it seams that the second version with the : is what's called the "initializer list" which allows you to initialize instance variables before the constructor body runs.
There is more detail around this in the documentation here.

Flutter required keyword

I don't really understand how required works. For example I've seen this code:
class Test{
final String x;
Test({
required this.x
});
factory Test.initial(){
return Test(x: "");
}
}
But what should required do here? Seems like it makes an optional parameter a non optional parameter.
Update
As of Dart 2.12, the required keyword replaces the #required meta annotation. For detailed info look into the official FAQ. The following answer has been updated to reflect both this and null safety.
Parameters required by default
The parameters of a class constructor or function are required by default.
class Test {
final String x;
Test(this.x);
}
You're not allowed to do this:
final value = Test();
// 1 positional argument(s) expected, but 0 found.
You must do this:
final value = Test('hello');
Optional named parameters
If you surround a parameter with curly braces, though, in addition to becoming a named parameter, it also becomes optional.
Since it's optional, the property must either be nullable like this:
class Test {
final String? x;
Test({this.x});
}
Or it has to have a default value like this:
class Test {
final String? x;
Test({this.x = ''});
}
So now this is ok:
final value = Test();
And so is this:
final value = Test(x: 'hello');
Required named parameters
Sometimes you don't want to allow a parameter to be null and there is no natural default variable. In that case you can add the required keyword in front of the parameter name:
class Test {
final String x;
Test({required this.x});
}
This is not ok anymore:
final value = Test();
// The named parameter 'x' is required, but there's no corresponding argument.
But this is still fine:
final value = Test(x: 'hello');
Dart 2.12 (null safety):
Beginning with Dart 2.12, the #required annotation is now replaced by the required keyword. You should mark your field required if it is mandatory for others to pass some value to it.
For example:
class Foo {
final int a; // Mandatory? Use 'required'
final int b; // Not mandatory? Don't use 'required'
Foo({
required this.a, // Marked 'required'
this.b = 1,
});
}
Usage:
Foo(); // Error: 'a' is required
Foo(a: 0); // Good
Foo(a: 0, b: 1); // Good
#required is an annotation that will create a warning for you to remember that the named parameter is necessary for the class to work as expected.
It will not create compile errors, at least for what I know.
#required bounds you to pass #required marked arguments while creating object of Class. For example, while showing a dialog, you'd mark context as required since, you cannot show dialog without having a valid context. But, you should not overuse it.
Short answer: Named parameters are optional by default in Dart. We prefer them to positional params for ease of use. In this case, the named parameters also might be expected to hold some value all the time (non-nullable) - from initialization itself. Hence, the double effort.
He could use default value initialization of the parameters instead of 'required', if the values were compile-time constants, and that doesn't seem to be the case here.
Positional parameters can be required or optional, which we pass in order when calling. The following is an example of required positional parameters' usage:
class Object{
String name;
int value;
Object(this.name, this.value=100); //auto type inference
}
final one = Object("Name here", 50); // All parameters are needed to call.
Named parameters are another type of optional parameters. Flutter APIs use named parameters and in our UI code, it is preferred to use named parameters instead of positional parameters. Reason being readability and clarity when reading code or calling the constructors later on at several parts of the code. You would have seen this as the case with all Widgets, Styles. For if it were to be positional it would be difficult to keep track of them upon calling with the sheer amount of methods that would be in use, and dynamic type inference could also be at work.
void display({required String name, int value1, int value2=100}) {...;} //named params
display(value1: 50, name: "Calculated name");
NOTE:
If exists, required positional parameters have to come first. Either named or optional positional params can follow(NOT BOTH).
String say(String from, String msg, [String? device]) { //req. pos params and opt pos params.
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
Remove
required
in constructor.
Instead write
final String? x;
So, it becomes as:
class Test{
final String? x;
Test({
this.x
});
factory Test.initial(){
return Test(x: "");
}
}