How To Use Built_Value Library In Flutter

In this post we will learn how to use built_value library in Flutter applications. We will break down the steps that you need to understand when working with built_value library in detail.

Introduction

We have already introduced what built value types are in our previous post. We also looked at why they are of importance in Dart and Flutter applications. In this post, we will learn how to use built_value library to setup built types, understanding each and every components of the built value model.

Setup Built_Value In Flutter App

To break down the process, we can sum it up as follows:

DecisionMentor app
  • Import the necessary package dependencies.
  • Create built value classes for your models.
  • Auto generate additional codes via build runner.

Import Packages

We start by importing the necessary packages. Although the primary package for using built value is built_value, we also need to two install additional development dependencies: build_runner and built_value_generator.

//pubspec.yaml 

dependencies:
  flutter:
    sdk: flutter
  built_value: ^6.1.6

  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^1.0.0
  built_value_generator: ^6.1.6

Create Built Value Class

For demonstration purpose, let’s assume you are creating a model for a Contact object. Ordinarily, a dart class without the built value implementation could look like this:

class Contact {
  int id;
  String fullName;
  int age;
  String mobile;
  bool isFriend;
}

Now, using the built value, the same class would appear as:

import 'package:built_value/built_value.dart';

part 'contact.g.dart';

abstract class Contact implements Built<Contact, ContactBuilder> {
  Contact._();
  factory Contact([updates(ContactBuilder b)]) = _$Contact;

  int get id;
  String get fullName;  
  @nullable
  int get age;
  @nullable
  String get mobile;
  @nullable
  bool get isFriend;
}

The built value class for Contact model looks quite confusing and complex when seen for the first time.

We will break down each of the class components in a little while. But first, let’s complete the built value class model by generating the other half of this model.

Auto Generate Additional Code

Now that we have a part of built value class ready, we are ready to generate the remaining part.

From the command terminal, run the build runner command:

flutter packages pub run build_runner build

This will generate a dart file called “contact.g.dart“.

Built Value Generated Class
Built Value Generated Class

If you take a look at this file, it contains really long boiler plate code written out for you! There are two classes here:

  • _$Contact class that extends Contact class
  • ContactBuilder class

Understanding Built Value Class In Detail

Now let’s break the class components down in a bit more detail.

  • Built Value model must be an abstract class.
abstract class Contact
  • It must implement the abstract generic Built class.
abstract class Contact implements Built
  • The first type for the Built class is the class name itself while the second one is the builder for this class. The builder is auto generated by code generator.
abstract class Contact implements Built<Contact, ContactBuilder> {
  • The built value class should itself be declared a part of a generated code.
  • The part file name should match with that of model file name i.e. contact.dart = contact.g.dart.
part 'contact.g.dart';
  • Every built value class must have a private default constructor so that the generated class can extend it.
Contact._();
  • Each properties of the class should be a getter (get) type. This is because built values are immutable types.
int get id;
String get fullName;
@nullable
int get age;
@nullable
String get mobile;
@nullable
bool get isFriend;
  • The @nullable needs to be explicitly defined for supporting pre-condition checks.
  • Next the factory method allows instantiating the properties of the built value class.
factory Contact([updates(ContactBuilder b)]) = _$Contact;
  • Although we could also explicitly define the factory method like below, a builder pattern is more convenient because it allows building the object piece by piece.
factory Contact (id, fullName) => new _$Contact._(
    id: id,
    fullName: fullName
  )
//This forces all fields to be repeated on the constructor.
//It also requires every fields to be passed at once.
//However, with a builder pattern we are not restricted!

Flutter Built Value In Action

Finally to see things in action with built value, let’s create an object instance:

var contact = Contact((b) => b
  ..id = 1
  ..fullName = "Stack Secrets");

As we can notice, built value types can be instantiated similar to regular class objects with the help of a builder method.

The created contact object is an immutable built value. We can pass it along into our application without having to worry about who modifies it and who has access to it.

We can also perform object comparison and string operations to this object.

var contact1 = Contact((b) => b
  ..id = 1
  ..fullName = "Stack Secrets");

var contact2 = Contact((b) => b
  ..id = 1
  ..fullName = "Stack Secrets");

print(contact1 == contact2); 
print(contact1);

//output

//true
//Contact {
  //id=1
  //fullName=Stack Secrets,
//}

Great! What about Serialization?

Well, that’s a topic for another post.

Conclusion

In this post we looked at the steps required to use built value library in Flutter application. We broke down each component of built value type class and also looked at it in action.

Next up is Serialization.