I am looking for a good way to validate freezed models. So far I came up with three approaches, which are shown in the snippet below.
#freezed
class Options with _$Options {
Options._();
factory Options._internal({required List<String> languages}) = _Options;
// #1: validation in factory constructor
factory Options({required List<String> languages}) {
if (languages.isEmpty) {
throw Exception('There must be at least one language.');
}
return Options._internal(languages: languages);
}
// #2: expose mutation methods with built-in validation
Options changeLanguages(List<String> languages) {
if (languages.isEmpty) {
throw Exception('There must be at least one language.');
}
return copyWith(languages: languages);
}
// #3: validation using custom properties
late final List<Exception> validationResult = <Exception>[
if (languages.isEmpty) Exception('There must be at least one language.'),
];
// #4: validation using a custom method
void validate() {
if (languages.isEmpty) {
throw Exception('There must be at least one language.');
}
}
}
#1: Validation inside a factory constructor. Unfortunately, this only works for newly created objects and requires further changes for copyWith.
#2: Validation inside a mutation method. This could be used in addition to #1 to run validation after object creation, but still does not work for copyWith.
#3: Exposing a property with validation errors. So far, this is my favorite approach, even though it requires users of the model to explicitly look for errors.
#4: A variation of #3, which uses a throwing method instead of providing a list of errors.
What are your thoughts on this? Do you know any better approaches or is there a part of the package API, which I have overlooked?
Freezed added support for custom asserts in v0.12.0: https://pub.dev/packages/freezed#asserts. Applying these to your example results in the following:
#freezed
abstract class Options with _$Options {
Options._();
#Assert('languages.isNotEmpty', 'There must be at least one language.')
factory Options({required List<String> languages}) = _Options;
}
However, this doesn't allow you to throw arbitrary exceptions, and asserts are only included in debug builds, not in profile/release builds.
I would probably move the validation one step below. You can create a model LanguageList:
class LanguageList {
LanguageList(this.data) {
if (data.isEmpty) throw ArgumentError('Provide at least one element');
}
final List<String> data;
#override
bool operator ==(Object other) => other is LanguageList && ListEquality<String>.equals(data, other.data);
#override
int get hashCode => DeepCollectionEquality().hash(data);
}
and use it in the Options model:
factory Options._internal({required LanguageList languages}) = _Options;
You can even make it more "compile-friendly" by making illegal states unrepresentable instead of throwing an error in runtime:
class LanguageList {
LanguageList(String head, Iterable<String> tail) : data = [head, ...tail];
final List<String> data;
#override
bool operator ==(Object other) => other is LanguageList && ListEquality<String>.equals(data, other.data);
#override
int get hashCode => DeepCollectionEquality().hash(data);
}
In this case there's just no way to create wrong instance.
To make invalid state unrepresentable, you need constructor validation. If the class is mutable, you need to validate when mutated as well, of course. None of the solutions above seem all that helpful. The best solution I have is to not use Freezed. Freezed is a great package that does really a lot. But, it maybe YAGNI in many cases. So, if you don't need all the features, bring in packages that provide just those you actually do need and hand roll those that for which there is no package.
See this discussion with Remi: https://github.com/rrousselGit/freezed/issues/830
Related
I'm trying to figure out if my updated code is the correct way to use a factory constructor with null safety. I reviewed stackoverflow and the Dart.dev language tour to try to better understand factory constructors. I have struggled to apply the concepts outlined to my code. I'm new to Flutter, Dart and coding. This is my first attempt at using a factory constructor so the primary issue is my lack of understanding and not any issues with the answers on stackoverflow or elsewhere.
After reading a lot I settled on the approach in the code below marked as Updated. The errors are now all gone and my app is behaving as I want but my fear is throwing an error instead of returning null may not be a sound approach. My approach just looks wrong to my beginner eyes. My goal is for my code to work and to also understand why I am using whatever approach I am using so that I can apply that knowledge to future situations. I can provide any additional code that may be needed to comment. Thanks in advance for the help.
Original Code that throws an error
class Job {
Job({required this.name, required this.ratePerHour});
factory Job.fromMap(Map<String, dynamic>? data) {
if (data == null) {
return null;
}
final String name = data['name'];
final int ratePerHour = data['ratePerHour'];
return Job(name: name, ratePerHour: ratePerHour);
}
final String name;
final int ratePerHour;
Map<String, dynamic> toMap() {
return {
'name': name,
'ratePerHour': ratePerHour,
};
}
}
Updated code that works
class Job {
Job({required this.name, required this.ratePerHour});
factory Job.fromMap(Map<String, dynamic>? data) {
if (data != null) {
final String name = data['name'];
final int ratePerHour = data['ratePerHour'];
return Job(name: name, ratePerHour: ratePerHour);
} else {
throw ArgumentError('Data is null');
}
}
final String name;
final int ratePerHour;
Map<String, dynamic> toMap() {
return {
'name': name,
'ratePerHour': ratePerHour,
};
}
}
Your own solution works fine however, I would check data before calling the factory.
factory Job.fromMap(Map<String, dynamic> data) {
return Job(name: data['name'], ratePerHour: data['ratePerHour']);
}
But then again why not use data and call the regular constructor?
Job? job;
if (data != null) {
job = Job(name: data['name'], ratePerHour: data['ratePerHour'])
}
https://dart.dev/guides/language/language-tour#factory-constructors
Factory constructors
Use the factory keyword when implementing a
constructor that doesn’t always create a new instance of its class.
For example, a factory constructor might return an instance from a
cache, or it might return an instance of a subtype. Another use case
for factory constructors is initializing a final variable using logic
that can’t be handled in the initializer list.
You've pointed out several true and good facts, and I feel like that you're on the right way to implement this.
I also feel like there's no straight "right" answer to this question; I think this also connects to concepts as clean code and clean architecture, which are broader than Dart and Flutter themselves
You can either:
Throw and let the caller (upper layer) handle that problem;
Print some logs and return a zero-value to the caller (in your case, an "empty" object).
Case 1 is desirable if you don't want to handle cases like that one.
Case 2 is desirable if you can afford to return something weird like a Job("job name",0) and still be good.
It really depends on what you're building. By looking at your context, I'd probably go with option 1 and try/catch that in a middle layer (maybe you want to show your user "An error occured" whenever data is null)?
Nonetheless, you might need to refactor this feature in a way that allows you not to encounter these edge cases. There's a good chance dependency inversion is your friend, here.
I want to add a TaskModel to taskModelList in setter method and then notify which I encounter the bellow error. I know changing the type of setter parameter to List will fix the issue, but is it possible to add a single item to list in set method?
Error:
The return type of getter 'taskModelList' is 'List' which
isn't a subtype of the type 'TaskModel' of its setter 'taskModelList'.
Code:
class TaskViewModel extends ChangeNotifier {
List<TaskModel> _taskModelList = [];
set taskModelList(TaskModel taskModel) {
_taskModelList.add(taskModel);
notifyListeners();
}
List<TaskModel> get taskModelList => _taskModelList;
}
Your setter currently doesn't set the List, rather than adding a new item in it. A setter sets the List with a List value,
set taskModelList(List<TaskModel> taskModelList) {
_taskModelList = taskModelList;
notifyListeners();
}
UPDATE
Remove your setter.
If you just want to add a single item use a custom method
void addItem(TaskModel task) {
_taskModelList.add(task)
notifyListeners();
}
If you are using Provider, I would suggest enriching your TaskViewModel with adding a static of method:
static TaskViewModel of(BuildContext context, {bool listen = false}) {
return Provider.of<TaskViewModel>(context, listen: listen);
}
Then you can easily listen for changes
TaskViewModel.of(context, listen: true).taskModelList
#esentis answer is correct, but it should also be noted that getters and setters are usually not necessary in Dart.
As per the Dart Style Guide
AVOID wrapping fields in getters and setters just to be "safe".
In Java and C#, it's common to hide all fields behind getters and setters (or properties in C#), even if the implementation just forwards to the field. That way, if you ever need to do more work in those members, you can do it without needing to touch the callsites. This is because calling a getter method is different than accessing a field in Java, and accessing a property isn't binary-compatible with accessing a raw field in C#.
Dart doesn't have this limitation. Fields and getters/setters are completely indistinguishable. You can expose a field in a class and later wrap it in a getter and setter without having to touch any code that uses that field.
GOOD:
class Box {
var contents;
}
BAD:
class Box {
var _contents;
get contents => _contents;
set contents(value) {
_contents = value;
}
}
I'm having a small issue converting this class using freezed since is not possible to have a default value which is not constant, so the line DateTime nocache= DateTime.now() is not possible to be transformed into #Default(DateTime.now()) DateTime nocache
Here the full code
import 'package:equatable/equatable.dart';
abstract class DynamicLinkState extends Equatable {
const DynamicLinkState();
#override
List<Object> get props => [];
}
class DynamicLinkInitial extends DynamicLinkState {
#override
String toString() => 'DynamicLinkInitial';
}
class DynamicLinkToNavigate extends DynamicLinkState {
final String path;
final DateTime nocache = DateTime.now();
DynamicLinkToNavigate({this.path});
#override
List<Object> get props => [path, nocache];
#override
String toString() => 'DynamicLinkToNavigate';
}
How can I eventually do that?
Additional context
I'm using a nocache attribute here because bloc is optimize to not send the same event multiple times, but this is a valid use case in this situation since i might expect the user to receive more then one time the same dynamic link. So the solution we found is simply to invalidate this optimization by passing an always changing nocache parameter.
So a valid solution to this question might also be to simply remove this workaround in favor of a more solid solution.
I ran into the same problem this morning. There's not a direct way that I could see to achieve this. However, a different approach to the problem may be in order.
Hopefully you are using a state management solution in your app. In my case it's RiverPod. So, rather than having the model know directly how to generate a default value for a property I elected to have my RiverPod state provider generate the value when creating the model.
So with a model like this...
#freezed class Pix with _$Pix {
const factory Pix({String? id, String? description, String? uri}) = _Pix;
}
...where I need the id to be generated (in my case it's a uuid), I have a StateNotifier handle that for me
class PixList extends StateNotifier<List<Pix>> {
PixList([List<Pix>? initialPixList]) : super(initialPixList ?? []);
void add(String description, String uri) {
state = [...state,
Pix(
id: _uuid.v4(),
description: description,
uri: uri
)
];
}
}
Since it's my state provider that should be handling creating objects, I'm effectively delegating responsibility for assigning that initial id value to the provider, not the model itself which remains nice and thin
By leveraging the Testing with async queries section of the Testing with a Mocking Framework article on MSDN, I've been able to create many successfully passing tests.
Here's my test code, which uses NSubstitute for mocks:
var dummyQueryable = locations.AsQueryable();
var mock = Substitute.For<DbSet<Location>, IDbAsyncEnumerable<Location>, IQueryable<Location>>();
((IDbAsyncEnumerable<Location>)mock).GetAsyncEnumerator().Returns(new TestDbAsyncEnumerator<Location>(dummyQueryable.GetEnumerator()));
((IQueryable<Location>)mock).Provider.Returns(new TestDbAsyncQueryProvider<Location>(dummyQueryable.Provider));
((IQueryable<Location>)mock).Expression.Returns(dummyQueryable.Expression);
((IQueryable<Location>)mock).ElementType.Returns(dummyQueryable.ElementType);
((IQueryable<Location>)mock).GetEnumerator().Returns(dummyQueryable.GetEnumerator());
sut.DataContext.Locations = mock;
var result = await sut.Index();
result.Should().BeView();
sut.Index() doesn't do much, but it makes the following query:
await DataContext.Locations
.GroupBy(l => l.Area)
.ToListAsync());
This works fine until I add a projection into the query:
await DataContext.Locations
.GroupBy(l => l.Area)
.Select(l => new LocationsIndexVM{ Area = l.Key }) // added projection
.ToListAsync());
which results in this exception:
System.InvalidOperationException
The source IQueryable doesn't implement IDbAsyncEnumerable<LocationsIndexVM>. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.
at System.Data.Entity.QueryableExtensions.AsDbAsyncEnumerable(IQueryable`1 source)
at System.Data.Entity.QueryableExtensions.ToListAsync(IQueryable`1 source)
at Example.Web.Controllers.HomeController.<Index>d__0.MoveNext() in HomeController.cs: line 25
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Example.Test.Web.Controllers.HomeControllerShould.<TempTest>d__4.MoveNext() in HomeControllerShould.cs: line 71
UPDATE: I've uploaded a small, simple solution that reproduces this problem.
Can anyone provide an example of what is required to unit test a query that is both async and contains a .Select() projection?
So I did a bit of digging, and the issue is to do with the way the TestDbAsyncEnumerable<T> exposes the IQueryProvider. My best guess as to the reasoning is below, and the solution below that.
TestDbAsyncEnumerable<T> inherits from EnumerableQuery<T>, which in turn inherits from IQueryable<T>, and explicitly implements the Provider property of this interface:
IQueryProvider IQueryable.Provider { get ... }
Given that it's implemented explicitly, I am assuming that the LINQ internals explicitly cast a type before trying to get the Provider:
((IQueryable<T>)source).Provider.CreateQuery(...);
I don't have a source on hand (and can't be bothered looking for one), but I believe the type binding rules are different for explicit implementations; essentially, the Provider property on your TestDbAsyncEnumerable<T> is not considered to be an implementation of IQueryable<T>.Provider as an explicit one exists further up the chain, so your TestDbAsyncQueryProvider<T> is never returned.
The fix for this is to make TestDbAsyncEnumerable<T> also inherit IQueryable<T> and explicitly implement the Provider property, as below (adjusted from the MSDN article you linked):
public class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
public TestDbAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable)
{ }
public TestDbAsyncEnumerable(Expression expression) : base(expression)
{ }
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
IQueryProvider IQueryable.Provider
{
get { return new TestDbAsyncQueryProvider<T>(this); }
}
}
While creating classes using Generators, it's possible to discover all subclasses of a type. You can find this technique for example in the GWT Showcase source (see full code):
JClassType cwType = null;
try {
cwType = context.getTypeOracle().getType(ContentWidget.class.getName());
} catch (NotFoundException e) {
logger.log(TreeLogger.ERROR, "Cannot find ContentWidget class", e);
throw new UnableToCompleteException();
}
JClassType[] types = cwType.getSubtypes();
I would like to do something similar, but instead of extending a class (or implementing an interface)
public class SomeWidget extends ContentWidget { ... }
, could I also do this by annotating Widgets?
#MyAnnotation(...)
public class SomeWidget extends Widget { ... }
And then finding all Widgets that are annotated with #MyAnnotation? I couldn't find a method like JAnnotationType.getAnnotatedTypes(), but maybe I'm just blind?
Note: I was able to make it work with the Google Reflections library, using reflections.getTypesAnnotatedWith(SomeAnnotation.class), but I'd prefer using the GeneratorContext instead, especially because this works a lot better when reloading the app in DevMode.
Yes - easiest way is to iterate through all types, and check them for the annotation. You might have other rules too (is public, is non-abstract) that should also be done at that time.
for (JClassType type : oracle.getTypes()) {
MyAnnotation annotation = type.getAnnotation(MyAnnotation.class);
if (annotation != null && ...) {
// handle this type
}
}
The TypeOracle instance can be obtained from the GeneratorContext using context.getTypeOracle().
Note that this will only give you access to types on the source path. That is, only types currently available based on the modules being inherited and <source> tags in use.