Type validation in dart

Sandro Maglione
Get in touch with meMobile development
10 January 2023
•3 min read

Sandro Maglione
Mobile development
Most of the times primitive types are too generic, and this can cause problems.
Think about a generic app which has a User
class. Every User
generally has an id
associated with it. You could implement this as follows:
class User {
final String id;
const User(this.id);
}
This works, but it may cause problems. In fact, not every String
is a valid id
. Nonetheless, this model allows any String
, without validation.
You could implement validation before creating a User
:
/// Getting `id` (`String`) from an API or somewhere else 👇
if (isValidUuid(id)) {
return User(id);
} else {
throw Exception("Not a valid id");
}
Nonetheless, also this solution is not ideal. In fact, we still have a User
containing a String
as id
.
We cannot be sure that the
id
will still be valid throughout the lifecycle of our application. Someone may accidentally change this value or use it improperly.
Type-safe models in dart: Uuid
The ideal type-safe solution is to introduce a new type specific for id
.
This type cannot be accessed or created unless the provided String
is valid.
We can call this type Uuid
. The implementation is as follows:
import 'package:fpdart/fpdart.dart';
bool isValidUuid(String str) => true; // Validation here ☝️
class Uuid {
final String id;
/// Private constructor, not accessible from outside 🔑
const Uuid._(this.id);
/// The only way to get a [Uuid] is to use `make`
///
/// This ensures that every [Uuid] is in the correct format ☑️
static Option<Uuid> make(String id) =>
isValidUuid(id) ? some(Uuid._(id)) : none();
}
- Define a validation function
isValidUuid
. This function acts as a guard, blocking all invalid values from becomingUuid
- Create a
Uuid
class and give it a private constructor. In this way, it is not possible to create an instance ofUuid
from outside the class Uuid
contains anid
attribute asString
, which we will be able to assign and access only after proper validation- Define a
static make
function that acts as a validation constructor. This function returnsOption
since the validation may fail (read more aboutOption
here)
We then assign the Uuid
type to User
instead of String
:
class User {
final Uuid uuid;
const User(this.uuid);
}
Now we are required to validate every id
before we are allowed to create a new User
:
void main() {
final optionUuid = Uuid.make("");
/// Cannot create [User] unless [Uuid] is verified 🔐
if (optionUuid is Some<Uuid>) {
final user = User(optionUuid.value);
}
}
Now we are safe to use User
everywhere in the app. We can access the valid String
from Uuid
as follows: user.uuid.id
.
We are also sure that no one will be allowed to change the
id
unless they properly validate it again.
That's because the only way of creating a Uuid
is to use make
, which validates every time any String
.
We are safe again now. Happy coding 🤝