Data Modeling with Flutter using freezed package
Tags: flutter, dart, tutorial, beginners
Published: 2021-06-05 04:19:36

# Introduction

In this post we'll cover how you can create a data model using [freezed](https://pub.dev/packages/freezed) package in Flutter and learn about a few techniques that I know and have been using when building projects. We will be using [JSONPlacheolder](https://jsonplaceholder.typicode.com/) to consume a REST API with [dio](https://pub.dev/packages/dio) as our HTTP client and create a data model for the `/users` endpoint to the Flutter application.

Data modeling is a process of creating a visual representation of information that describes the a business entity of a software project. It is a technique that is being often used in most applications. A data model can also represent relationship between each business entities. Or if you know about relational databases then for a quick comparison, a data model can be an SQL table that represents it. 

You can read more in depth about [data modeling here](https://www.ibm.com/cloud/learn/data-modeling)

# Installation

First we will begin with installing a fresh Flutter project, so go over and open up your terminal and execute the following command below.

```bash
flutter create freezed_data_modeling
```

Then open the project in your IDE (Visual Studio Code) and open up the `pubspec.yaml` file and add up the following dependencies that we require,

- dependencies:
    - freezed_annotation
- dev_dependencies:
    - build_runner
    - freezed

```yaml
dependencies:
  flutter:
    sdk: flutter
  freezed_annotation:
	dio:

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner:
  freezed:
```

And finally execute `flutter pub get` when you've added those dependencies.

Next you can launch an emulator and run the project in it, but you can do it later.

# JSONPlaceholder response

Before we start creating our data models, we should know what resources are getting returned to the application. It wouldn't make sense to create data models when the data model you created does not reflect in the backend. Take a look at it carefully and identify which properties are of type `String` , `double` , `int` , and etc. 

```json
// https://jsonplaceholder.typicode.com/users
[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },

  ...
]
```

When taking a closer look at it we can identify the following,

- `id` is of type `int` because it returns a single digit
- `name` contains various characters, so this is a `String`
- `username` is a `String`
- `email` is a `String`
- `address` is a JSON object, so we'll require to create its own data model as well because we don't wanna put up a type for that as `Map<String, dynamic>` that would make less robust

    So let's identify the property types for each property in `address` field:

    - `street` is a `String`
    - `suite` is a `String`
    - `city` is a `String`
    - `zipcode` is a `String`
    - `geo` is another JSON object, so we'll do the same thing as what we are currently doing for `address`
        - `lat` can be a `String` but since we know it is "latitude" and usually they are in decimal values, so we'll use `double` for that instead
        - `lng` is `double`
- `phone` is a `String`
- `website` is a `String`
- `company` is a JSON object
    - `name` is a `String`
    - `catchPhrase` is a `String`
    - `bs` is a `String`

# Creating a data model without freezed

Usually when we start to create our data models is that we use this approach, also means that we are writing out a lot of "boilerplate" code which isn't very ideal when you have a large complex application. One could be spending a lot of time just writing up data models this way, manually adding method calls like `toJson()` to convert the data model into a JSON format which is the typical format we use when we are creating requests to a REST API, GraphQL API or any sort of backend service that you could be using.

```dart
class Geo {
  final double lat;
  final double lng;

  Geo({
    this.lat = 0.0,
    this.lng = 0.0,
  });

  Map<String, dynamic> toJson() {
    return {
      'lat': lat,
      'lng': lng,
    };
  }
}

class Address {
  final String? street;
  final String? suite;
  final String? city;
  final String? zipcode;

  Address({
    this.street,
    this.suite,
    this.city,
    this.zipcode,
  });

  Map<String, dynamic> toJson() {
    return {
      'street': street,
      'suite': suite,
      'city': city,
      'zipcode': zipcode,
    };
  }
}

class Company {
  final String? name;
  final String? catchPhrase;
  final String? bs;

  Company({
    this.name,
    this.catchPhrase,
    this.bs,
  });

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'catchPhrase': catchPhrase,
      'bs': bs,
    };
  }
}

class User {
  final int id;
  final String? username;
  final String? email;
  final Address? address;
  final String? phone;
  final String? website;
  final Company? company;

  User({
    required this.id,
    this.username,
    this.email,
    this.address,
    this.phone,
    this.website,
    this.company,
  });

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'username': username,
      'email': email,
      'address': address?.toJson(),
      'phone': phone,
      'website': website,
      'company': company?.toJson(),
    };
  }
}
```

This would take up a lot of your time and could even be counterproductive for you as a developer.

Also notice how I am putting up with the `?` annotation in each of the properties since they could potentially be `null` and that would be considered a bug and will produce crashes in the application during run time or if when our users are using our application.

You can learn more about [null-safety here](https://flutter.dev/docs/null-safety)

There are quite a lot of problems that this gives us when writing our applications since there comes a point where when we will have to copy all values and replace with a new value. In JavaScript we can simply do that with the "triple dot" notation to create a new object from an existing object. 

```jsx
const person = {
  name: "Carlo Miguel Dy",
  age: 23,
}

console.log(person)
// { "name": "Carlo Miguel Dy", "age": 23 }

const newPerson = {
  ...person,
  name: "John Doe",
}

console.log(newPerson)
// { "name": "John Doe", "age": 23 }
```

But unfortunately we can't do that with Dart yet. We can do that but it's quite a lot of "boilerplate" code.

# Creating a data model with freezed

A lot of the "boilerplate" code is eliminated when we are using the `freezed` package, what it does is it generates all those "boilerplate" code for us, we can also make use of its annotations which comes very handy. To mention a few these are `@Default` for providing a default value when this property is `null` and we also use `@JsonKey` to override the JSON key just in case the conventions is different from the backend. Some do use `camelCasing` , `snake_casing` and `PascalCasing` so these are the kind of problem it can solve with only a few lines of code.

```dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'freezed_datamodels.freezed.dart';
part 'freezed_datamodels.g.dart';

@freezed
class Geo with _$Geo {
  const factory Geo({
    @Default(0.0) double lat,
    @Default(0.0) double lng,
  }) = _Geo;

  factory Geo.fromJson(Map<String, dynamic> json) => _$GeoFromJson(json);
}

@freezed
class Address with _$Address {
  const factory Address({
    @Default('') String street,
    @Default('') String suite,
    @Default('') String city,
    @Default('') String zipcode,
    Geo? geo,
  }) = _Address;

  factory Address.fromJson(Map<String, dynamic> json) =>
      _$AddressFromJson(json);
}

@freezed
class Company with _$Company {
  const factory Company({
    @Default('') String name,
    @Default('') String catchPhrase,
    @Default('') String bs,
  }) = _Company;

  factory Company.fromJson(Map<String, dynamic> json) =>
      _$CompanyFromJson(json);
}

@freezed
class User with _$User {
  const factory User({
    required int id,
    required String username,
    required String email,
    Address? address,
    @Default('') String phone,
    @Default('') String website,
    Company? company,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
} 
```

We only have to write a few lines of code to create the data models and we are utilizing on the annotations that the `freezed` package provides for us.

To break down each data model I defined above for you,

- `Geo`
    - The `lat` property is non-nullable but has a default value of `0.0` when this value is empty
    - The `lng` property is non-nullable but has a default value of `0.0` when this value is empty
- `Address`
    - The `street` property is non-nullable but has a default value of `''` which means it will be an empty string when this value is empty
    - The `suite` property is non-nullable but has a default value of `''` which means it will be an empty string when this value is empty
    - The `city` property is non-nullable but has a default value of `''` which means it will be an empty string when this value is empty
    - The `zipcode` property is non-nullable but has a default value of `''` which means it will be an empty string when this value is empty
- `Company`
    - The `name` property is non-nullable but has a default value of `''` which means it will be an empty string when this value is empty
    - The `catchPhrase` property is non-nullable but has a default value of `''` which means it will be an empty string when this value is empty
    - The `bs` property is non-nullable but has a default value of `''` which means it will be an empty string when this value is empty
- `User`
    - The `id` property is non-nullable but is `required` and this property should never be `null`
    - The `username` property is non-nullable but is `required` and this property should never be `null`
    - The `email` property is non-nullable but is `required` and this property should never be `null`
    - The `address` property is nullable
    - The `phone` property is non-nullable but has a default value of `''` which means it will be an empty string when this value is empty
    - The `website` property is non-nullable but has a default value of `''` which means it will be an empty string when this value is empty
    - The `company` property is nullable

# Generating code using build_runner package

As the code snippet above you will notice that we have 2 lines of code that uses `part` and it contains `freezed` and `g` these are the ones that will let `freezed` package to recognize that it requires to generate a code whenever we execute the `build_runner` to build generate code for us.

To start generating code for us, execute the following command with-in the root directory of your Flutter project.

```bash
flutter pub run build_runner build --delete-conflicting-outputs
```

To break it down what each does, the `flutter pub run` will allow us to run script coming from a package like `build_runner` and the `build` is the command or script to tell the `build_runner` package to start generating code for us. It will look for files that contains the following `*.freezed.dart` and `*.g.dart` the `*` is just a wild card that means any file name that contains it will be recognized by the `build_runner` and lastly the `--delete-conflicting-outputs` flag will tell the `build_runner` package to delete any existing `*.freezed.dart` and `*.g.dart` files to prevent duplicate outputs or that it could potentially conflict with those.

So every time you might have to update your data model with new properties, you will always have to execute this the command snippet above to tell `build_runner` to generate code for us.

You can take a peek at what code was generated from the repository or directly from the links below:

- [freezed_datamodels.freezed.dart](https://github.com/carlomigueldy/freezed_data_modeling/blob/main/lib/models/freezed_datamodels.freezed.dart)
- [freezed_datamodels.g.dart](https://github.com/carlomigueldy/freezed_data_modeling/blob/main/lib/models/freezed_datamodels.g.dart)

# Copying values from a data model but only change values of specific properties

Coming back with copying values of an object in JavaScript, we can now simply do that as well with our data model that is using the `freezed` package.

```dart
final person = User(
  id: 1,
  username: 'carlomigueldy',
  email: 'carlomigueldy@gmail.com',
);

person.toJson();
// { "id": 1, "username": "carlomigueldy", "email": "carlomigueldy@gmail.com", ... }

final newPerson = person.copyWith(
  username: 'johndoe123',
);

newPerson.toJson();
// { "id": 1, "username": "johndoe123", "email": "carlomigueldy@gmail.com", ... }
```

That's really powerful. 

# Consuming the REST API

We just got a basic application installed, so remove all those comments that make the code too long in `main.dart` file.

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
```

Create a final field with type `Dio` and instantiate it with a new `Dio` instance, then create a function called `fetchUsers` that will fetch data from this endpoint [`https://jsonplaceholder.typicode.com/users`](https://jsonplaceholder.typicode.com/users) and set it in a private property with type `List<User>` called as `_users` , we can call this function when you will click on the `FloatingActionButton` or for call this function inside the `initState` lifecycle hook of a `StatefulWidget` , for convenience I will just have the code here below for you to reference on if you prefer to write it out yourself. But the full source code will be found in the repository for a better reference.

```dart
class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final Dio dio = Dio();
  List<User> _users = [];

  Future<void> fetchUsers() async {
    final response = await dio.get(
      'https://jsonplaceholder.typicode.com/users',
    );
    print(response.data);
    final List list = response.data;

    setState(() {
      _users = list.map((e) => User.fromJson(e)).toList();
    });
  }

  @override
  void initState() {
    fetchUsers();

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView.builder(
        itemCount: _users.length,
        itemBuilder: (context, index) {
          final user = _users[index];

          return ListTile(
            title: Text(user.username),
            subtitle: Text(user.email),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: fetchUsers,
        tooltip: 'Fetch Users',
        child: Icon(Icons.data_usage),
      ),
    );
  }
}
```

When you have this correctly in your code then it should look very similar to this screenshot.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8mvv1n5a5q33mevmwswo.png)
 
# Using "copyWith" method from a data model

Now let's make it a bit interesting, this is not a very practical approach when building out your applications but it will give you an idea and on how you can make use of this. There are a lot of use cases that you might require to use `copWith` method from a `freezed` data model.

For the sake of demonstration purposes, we will implement the following for when a user taps on any of the `ListTile` under the `ListView` we will change the value of the `username` property and append a string with value of " CLICKED", so for instance when "Bret" is tapped then we will have it display "Bret CLICKED". To put things into action, let's create a function that will take an argument as the index of that item of a `ListTile` then attach it on to the `onTap` property of a `ListTile` and just pass in the current `index` of that item.

```dart
void appendUsername(int index) {
    setState(() {
      _users[index] = _users[index].copyWith(
        username: '${_users[index].username} CLICKED',
      );
    });
  }

// ... 

body: ListView.builder(
        itemCount: _users.length,
        itemBuilder: (context, index) {
          final user = _users[index];

          return ListTile(
            title: Text(user.username),
            subtitle: Text(user.email),
            onTap: () => appendUsername(index),
          );
        },
      ),
```

Then we'll have the following output when any of the `ListTile` is tapped.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ivr1mldz020csa5wsivr.png)
 

# Example usage for `@JsonKey` annotation

There are certain cases that we can make use of `@JsonKey` annotation when creating our data models, say we have this data model and we have a property named as `catchPhrase` when this gets generated from the `build_runner` package, the JSON property will be equivalent to `catchPhrase` so basically it will result to something like this `json['catchPhrase']` 

In the current API we will be consuming, there'd be no problem since it uses `camelCasing` convention so we can just have that as is

But what happens if the API will have different set of conventions, and we don't do something about it, so eventually our property `catchPhrase` in our `Company` data model will always be `null` since it will never match `CatchPhrase` or `catch_phrase` that is returned in the application when it is trying to retrieve its value by `json['catchPhrase']` 

```dart
@freezed
class Company with _$Company {
  const factory Company({
    @Default('') String name,
    @Default('') String catchPhrase,
    @Default('') String bs,
  }) = _Company;

  factory Company.fromJson(Map<String, dynamic> json) =>
      _$CompanyFromJson(json);
}
```

So to fix that we will make use of the `@JsonKey` annotation, just add it before the `@Default` annotation in this case, then we can specify the JSON object property name like so

```dart
@freezed
class Company with _$Company {
  const factory Company({
    @Default('') String name,
    @JsonKey(name: 'catch_phrase') @Default('') String catchPhrase,
    @Default('') String bs,
  }) = _Company;

  factory Company.fromJson(Map<String, dynamic> json) =>
      _$CompanyFromJson(json);
}
```

Then whenever we call `Company.fromJson(json)` it will parse that coming from a JSON format into the actual `Company` data model that we defined into our application. Instead of having it retrieve by `json['catchPhrase']` we now retrieving it by what we defined in the `name` property of the `@JsonKey` annotation so in this case it will be `json['catch_phrase']`

I hope that makes sense.

Another way of how we can make use of it is when we require to convert a property into a JSON format. For example, for our `User` data model it contains a property for `Company` data model and an `Address` data model. So when we print it out we will have the following value.

```dart
// Where `user` is of type `User`

print(user.toJson());
// {id: 4, username: Karianne, email: Julianne.OConner@kory.org, address: Address(street: Hoeger Mall, suite: Apt. 692, city: South Elvis, zipcode: 53919-4257), phone: 493-170-9623 x156, website: kale.biz, company: Company(name: Robel-Corkery, catchPhrase: Multi-tiered zero tolerance productivity, bs: transition cutting-edge web services)}
```

You will notice that the `address` property is not in its JSON format, it has the following value instead which tells us it is a class with the its corresponding property and values. Which isn't very ideal whenever we will have to pass this information back into the API, it will throw an exception instead. But this doesn't happen to often when you have to pass back a huge payload to the API.

```dart
address: Address(street: Hoeger Mall, suite: Apt. 692, city: South Elvis, zipcode: 53919-4257)
```

The way we can fix that up is by using the property `fromJson` and `toJson` converters from the `@JsonKey` annotation. We declare it again before the `@Default` annotation when there exists, otherwise it's just the same spot before the actual property.

```dart
@freezed
class User with _$User {
  const User._();
  const factory User({
    required int id,
    required String username,
    required String email,
    @JsonKey(
      fromJson: User._addressFromJson,
      toJson: User._addressToJson,
    )
        Address? address,
    @Default('')
        String phone,
    @Default('')
        String website,
    @JsonKey(
      fromJson: User._companyFromJson,
      toJson: User._companyToJson,
    )
        Company? company,
  }) = _User;

  static Address? _addressFromJson(Map<String, dynamic>? json) {
    if (json == null) return null;

    return Address.fromJson(json);
  }

  static Map<String, dynamic>? _addressToJson(Address? address) {
    if (address == null) return null;

    return address.toJson();
  }

  static Company? _companyFromJson(Map<String, dynamic>? json) {
    if (json == null) return null;

    return Company.fromJson(json);
  }

  static Map<String, dynamic>? _companyToJson(Company? company) {
    if (company == null) return null;

    return company.toJson();
  }

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
```

To break it down for you,

The `address` we used the `@JsonKey` annotation and passed down the properties for `fromJson` with what we defined as a `static` member of the class which returns the same type of what the property `address` has (`Address?`) which is of type `Address` but is nullable. As per `freezed` package instructions on accessing the same member of the class, it is required that we will have to call this `const User._();`

```dart
@freezed
class User with _$User {
  const User._();

  const factory User({
	  // ...
    @JsonKey(
      fromJson: User._addressFromJson,
      toJson: User._addressToJson,
    )
        Address? address,
		// ...
  }) = _User;

  static Address? _addressFromJson(Map<String, dynamic>? json) {
    if (json == null) return null;

    return Address.fromJson(json);
  }

  static Map<String, dynamic>? _addressToJson(Address? address) {
    if (address == null) return null;

    return address.toJson();
  }

	// ...
}
```

The private static method `_addressFromJson` takes a first argument of type `Map<String, dynamic>` which represents a JSON value and is nullable, then in this method we check if the `json` argument is `null` and if that evaluates to `true` then we'll just return `null` otherwise we can call `Address.fromJson(json)` and have it return an instance of a data model `Address` that we defined.

```dart
static Address? _addressFromJson(Map<String, dynamic>? json) {
    if (json == null) return null;

    return Address.fromJson(json);
  }
```

The private static method `_addressToJson` takes a first argument of type `Address` which the data model that we defined is nullable, then in this method we check if the `address` argument is `null` and if that evaluates to `true` then we'll just return `null` otherwise we can call `address.toJson()` and have it return a JSON representation of it.

```dart
static Map<String, dynamic>? _addressToJson(Address? address) {
    if (address == null) return null;

    return address.toJson();
  }
```

# Finalizing our data models

```dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'freezed_datamodels.freezed.dart';
part 'freezed_datamodels.g.dart';

@freezed
class Geo with _$Geo {
  const factory Geo({
    @Default(0.0) double lat,
    @Default(0.0) double lng,
  }) = _Geo;

  factory Geo.fromJson(Map<String, dynamic> json) => _$GeoFromJson(json);
}

@freezed
class Address with _$Address {
  const Address._();
  const factory Address({
    @Default('')
        String street,
    @Default('')
        String suite,
    @Default('')
        String city,
    @Default('')
        String zipcode,
    @JsonKey(
      fromJson: Address._geoFromJson,
      toJson: Address._geoToJson,
    )
        Geo? geo,
  }) = _Address;

  static Geo? _geoFromJson(Map<String, dynamic>? json) {
    if (json == null) return null;

    return Geo.fromJson(json);
  }

  static Map<String, dynamic>? _geoToJson(Geo? geo) {
    if (geo == null) return null;

    return geo.toJson();
  }

  factory Address.fromJson(Map<String, dynamic> json) =>
      _$AddressFromJson(json);
}

@freezed
class Company with _$Company {
  const factory Company({
    @Default('') String name,
    @Default('') String catchPhrase,
    @Default('') String bs,
  }) = _Company;

  factory Company.fromJson(Map<String, dynamic> json) =>
      _$CompanyFromJson(json);
}

@freezed
class User with _$User {
  const User._();
  const factory User({
    required int id,
    required String username,
    required String email,
    @JsonKey(
      fromJson: User._addressFromJson,
      toJson: User._addressToJson,
    )
        Address? address,
    @Default('')
        String phone,
    @Default('')
        String website,
    @JsonKey(
      fromJson: User._companyFromJson,
      toJson: User._companyToJson,
    )
        Company? company,
  }) = _User;

  static Address? _addressFromJson(Map<String, dynamic>? json) {
    if (json == null) return null;

    return Address.fromJson(json);
  }

  static Map<String, dynamic>? _addressToJson(Address? address) {
    if (address == null) return null;

    return address.toJson();
  }

  static Company? _companyFromJson(Map<String, dynamic>? json) {
    if (json == null) return null;

    return Company.fromJson(json);
  }

  static Map<String, dynamic>? _companyToJson(Company? company) {
    if (company == null) return null;

    return company.toJson();
  }

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
```

Then as changes were made, we can then generate a new code so let's tell `build_runner` to do that for us by executing it again in your terminal, run the following command

```dart
flutter pub run build_runner build --delete-conflicting-outputs
```

# Conclusion

Cheers you have made it to this very last part! 🎉 Hope you enjoyed and learned something from this, should help you out when you strive to write clean code with-in your codebase.

We learned how we can create data models using the `freezed` package, we learned how we can make use of `@JsonKey` and creating a JSON converter for a specific field, we learned how we can use `copyWith` method to copy existing values and only replace the values that are specified in the parameters, and we learned how we can deal with any backend that returns different naming conventions for their JSON properties (`camelCasing`, `snake_casing` and `PascalCasing`). We may have only learned the basic of it and we can of course refactor some of it, but maybe we can tackle that next time, for now we are just going to make it work and that solves our problem.

If you liked this and find it useful, don't forget to show some love now hit up the like button! 💪 See you on the next one.

[💻 Full source code can be found in here](https://github.com/carlomigueldy/freezed_data_modeling)