Using Dart Records for Validation
After the announcement of Dart 3.0 at Google IO 2023, I was looking for more concrete examples for records to use in my day-to-day work. Finally, I found a use case to use records for validation.
I was pretty excited when I learned about the new feature called records in Dart 3.0 about a year ago. Initially, I thought it would be similar to data classes in Kotlin, but it turned out to be different when I read the specifications.
After the announcement of Dart 3.0 at Google IO 2023, there were blogs explaining the use cases for records, but most of them focused on explaining the syntax and the various features that records can offer. However, I was looking for more concrete examples that I could start using in my day-to-day work, such as how to use pattern matching to extract the first child from a list.
While working on a small side project, I finally found a use case where I could utilize records.
And, it was for validations.
Let's say I want to validate the required fields for a task and send an appropriate error message to the caller for missing or invalid fields. Before records, there were two approaches I could have used.
1. Using Throw Exception
We create a method that throws an error with a message when the input is invalid:
And, we catch the error on the calling side like this:
However, the problem with this approach is that the method does not explicitly indicate that it will throw an error, and on the caller side, we might use this function without a try-catch block.
Some developers might address this issue with the second approach
2. Using Future
Instead of throwing an error message in a plain method, we return a Future<void>:
And we catch the error on the caller side using a try-catch block or handling catchError on the Future:
This approach works great and solves the issue from the first approach. However, it can still be verbose and hard to know, just by reading the code, whether the error is coming from the validation or some other internal error.
Additionally, catchError returns an object where we might need to know the type of exception as needed. For example, in the above example, the error object is a String.
3. Using Tuple
Tuples are used to combine related properties. We most commonly use them when we want to return multiple values from a method:
On the caller side, we extract item1 for the validation check and item2 for the error message like this:
This approach solves the problem in a cleaner way compared to Future, but it still has readability issues. What do item1 and item2 stand for? Is item1 the error message or something else?
Using Records
Apart from the other benefits mentioned in the official documentation, records can also help achieve the same functionality as Tuple2. In fact, if we start using records in our codebase, chances are that the first thing we'll replace will Tuples with records.
We can use records by defining the values inside parentheses. To learn more about the various syntax options, please refer to the official documentation. In the example with Tuple2 mentioned above, we can replace it with a record like this:
On the calling side, we access variables with $ sign:
We have replaced the Tuple2 items with a record, but we still have readability issues. We don't know from the caller's side what $1 and $2 stand for. However, with records, we can fix this on the caller side by destructuring the values into meaningful variables:
This reads better, and the beauty of records is that the compiler knows all the types, so it will throw an error if you do something wrong with the types. For example, using a boolean isValid as a string.
Although destructuring solves half of the problem by improving readability on the caller side, what about the readability issue within the validate function itself?
We can fix that by using named variables in the record within the validate function:
On the caller side, we can easily destructure those named variables using a colon (:
) inside the record:
Here, too, if you misspell the variable name or use an unmatched type, the compiler will throw an error.
That’s it for now.
Hey there, If you enjoyed this post then would you be able to do me a quick favor and share my latest blog post with your friends and colleagues? I'd really appreciate it and I think it could be valuable to them.
Thank you so much!
Great article. Really helpful