Freezed ❄ – Data Class & Union in One Dart Package

Freezed ❄ – Data Class & Union in One Dart Package

Freezed ❄ – Data Class & Union in One Dart Package. How would you like it if Dart had data classes and sealed classes as we know them from Kotlin? Freezed can be used for both data classes and unions! This means you'll get automatically generated value equality, copyWith, exhaustive switch, and even JSON serialization support from one place! Apart from freezed, we're also going to add json_serializable as it's nicely integrated.

How would you like it if Dart had data classes and sealed classes as we know them from Kotlin? It would be perfect but while these features are being worked on, they're still in the distant future. Now we have the next best thing though - we're all about to get freezed with the new package by Rémi Rousselet, the creator of provider.

But there's a package for that...

If you've been following Dart's algebraic data type scene for a while you might be thinking "We have sealed_unions, super_enum, sum_types... Why do we need yet another package?". (Yes, I did cover all of those packages with tutorials 😉_)_ Similarly, if you are an avid follower of the data class scene, built_value, equatable and even certain VS Code extensions may come to mind. So, why again another package?

I could talk for quite some time about the insufficiencies of the aforementioned packages so I'll keep it short. They are either too verbose, too restrictive or too ugly to use on a daily basis. That is a reason in itself to switch to freezed which provides a succint and elegant syntax.

Still not convinced? Well, **freezed **can be used for _both_data classes and unions! This means you'll get automatically generated value equality, copyWith, exhaustive switch, and even JSON serialization support from one place! Basically you get built_value and sum_types without all the weirdness and boilerplate.

Setting up the project

We will use a console application in this tutorial but everything applies to Flutter as well. First, let's add the needed packages to pubspec.yaml. Apart from freezed, we're also going to add json_serializable as it's nicely integrated.

pubspec.yaml

dependencies:
  json_annotation: ^3.0.1
  meta: ^1.1.8

dev_dependencies:
  build_runner:
  freezed: ^0.1.3+1
  json_serializable: ^3.2.5
Creating a data class

In case you're not familiar with the term data class, it's simply a class with value equality, copyWith method, its fields are immutable and it usually easily supports serialization. Also, if you're familiar with Kotlin, you know that you can heavily cut down on the boilerplate by defining fields directly in the constructor like this:

kotlin_data_class.kt

data class User(val name: String, val age: Int)

Compare that to the amount of code needed with pure Dart:

dart_regular_data_class.dart

@immutable
class User {
  final String name;
  final int age;

  User(this.name, this.age);

  User copyWith({
    String name,
    int age,
  }) {
    return User(
      name ?? this.name,
      age ?? this.age,
    );
  }

  @override
  bool operator ==(Object o) {
    if (identical(this, o)) return true;

    return o is User && o.name == name && o.age == age;
  }

  @override
  int get hashCode => name.hashCode ^ age.hashCode;
}

Things aren't going that great for Dart so far. Of course, you can go with built_value but then you'll have to deal with its weird syntax. But fear not because the simplicity of freezed is just a few keystrokes away. Let's create a new file freezed_classes.dart.

freezed_classes.dart

import 'package:meta/meta.dart';

part 'freezed_classes.freezed.dart';

@immutable
abstract class User with _$User {
  const factory User(String name, int age) = _User;
}

We're going to create all the freezed classes inside this file so that we don't have to specify the import and part statements over and over again.

Not bad, is it? Sure, there's still some amount of boilerplate but it's pretty standard and minimal. After running every Flutter developer's favorite command...

👨‍💻 terminal

flutter pub run build_runner watch --delete-conflicting-outputs

We can now use our User data class with all it's glory and utilize immutability, value equality and copyWith.

main.dart

void main() {
  final user = User('Matt', 20);
  // user.age = 5; // error, User is immutable
  final user2 = user.copyWith(name: 'John');

  final sameValueUser1 = User('abc', 123);
  final sameValueUser2 = User('abc', 123);
  print(sameValueUser1 == sameValueUser2); // true

  print(user); // User(name: Matt, age: 20)
}

The constructor parameters of our data class User are currently positional. That doesn't need to be the case though as you can make them optional or named.

freezed_classes.dart

@immutable
abstract class User with _$User {
  const factory User(String name, {int age}) = _User;
}

Adding JSON serialization

Serializing data to and from JSON is dead simple with json_serializable and, thankfully, **freezed **was built to work well with it! No more custom and weird serialization as with built_value! Since we've already added it as a dependency, we just need to add the little boilerplate needed for json_serializable's generator to kick in.

Make sure to import json_annotation.dart and provide a regular part '*.**g**.dart' statement. Notice that we define only the fromJson method and that we don't annotate the class with @JsonSerializable.

freezed_classes.dart

import 'package:json_annotation/json_annotation.dart';
import 'package:meta/meta.dart';

part 'freezed_classes.freezed.dart';
part 'freezed_classes.g.dart';

@immutable
abstract class User with _$User {
  const factory User(String name, int age) = _User;
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

This will generate all the necessary code to call toJson as an instance method and fromJson as a factory.

main.dart

void main() {
  final user = User('Matt', 20);
  final Map<String, dynamic> serialized = user.toJson();
  final User deserialized = User.fromJson(serialized);
}

Serialization is also fully supported with unions which is very cool 😎 More about them below.

Creating a union/sealed class

These kind of simple data classes are enough in themselves to use freezed but the elegance with which you can create unions is just 👌 magnificent. If you used any other sealed class wannabe packages, you know about their weird syntax or limitations. Let's again take a look at the grace of Kotlin's sealed classes:
kotlin_sealed_classes.kt

// Nest classes to access them only as Operation.Add or Operation.Subtract
sealed class Operation {
  class Add(val value: Int) : Operation()
  class Substract(val value: Int) : Operation()
}

// Don't nest classes if you want to instantiate Add or Subtract directly
sealed class Operation
class Add(val value: Int) : Operation()
class Substract(val value: Int) : Operation()

fun main() {
  ...
  // exhaustive "switch", notifies you if you forget to handle a case
  when(operation) {
    is Add -> //do something
    is Subtract -> // do something
  }
}

Even if you know nothing about Kotlin, you should understand what's going on. Can we replicate this to a tee with freezed? You bet we can! Unlike with the other other wannabe sealed class packages, freezed allows even for the nested/non-nested paradigm. Let's see what's possible.

"Nested" sealed classes

The beauty of nested sealed classes in Kotlin is that you cannot instantiate the individual subclasses individually. With freezed, this means that writing final operation = Add(); is invalid while writing final operation = Operation.add() is valid. How can we accomplish such a thing? It's easy! Just make the generated class for the union case private.

freezed_classes.dart

@immutable
abstract class OperationNested with _$OperationNested {
  // "Nested" unions have private generated classes (underscore)
  const factory OperationNested.add(int value) = _Add;
  const factory OperationNested.subtract(int value) = _Subtract;
}

Then we can instantiate a union case by calling the factory method and use the when method to exhaustively switch over all of the possible cases.

main.dart

void main() {
  final result = performOperation(2, OperationNested.add(2));
  print(result); // 4
}

// Function pretending to do something useful
int performOperation(int operand, OperationNested operation) {
  // Like switch statement but forgetting about a case will result in info/error
  return operation.when(
    add: (value) => operand + value,
    subtract: (value) => operand - value,
  );
}

Parameters of the when method are @required. However, Dart only gives you a non-fatal "info" message by default. To promote this friendly message to be a full-blown error, enable custom lint rules

"Non-nested" sealed classes

It's sometimes nice to not have to call factory constructors but instead instantiate individual case classes directly. This can be useful with BLoC events and states, for example. Achieving this is as simple as making the generated union case classes public.

freezed_classes.dart

@immutable
abstract class OperationNonNested with _$OperationNonNested {
  // "Non-nested" unions have public generated classes (no underscore)
  const factory OperationNonNested.add(int value) = Add;
  const factory OperationNonNested.subtract(int value) = Subtract;
}

This allows for two ways of instantiation - either use the factory or instantiate the case class directly.

main.dart

void main() {
  // Still possible to use the factory
  final result1 = performOperation(2, OperationNonNested.add(2));
  // But non-nested union cases can also be instantiated directly
  final result2 = performOperation(2, Add(2));

  print(result1); // 4
  print(result2); // 4
}

int performOperation(int operand, OperationNonNested operation) {
  // When method still works even with cases instantiated directly
  return operation.when(
    add: (value) => operand + value,
    subtract: (value) => operand - value,
  );
}

Other switch-like methods

We've seen when so far but there are 3 more methods. Of course, there's the non-exhaustive maybeWhen which allows you to ignore certain union cases and provide a fallback orElse function to run instead.
main.dart

return operation.maybeWhen(
  add: (value) => operand + value,
  // ignoring subtract
  orElse: () => -1,
);

Then there is map and its companion maybeMap. They're very similar to when and maybeWhen but instead of passing you the destructured value (int in the case of our example union), they pass you the actual case class holding the data (Add or Subtract in our example).

main.dart

int performOperation(int operand, OperationNonNested operation) {
  return operation.map(
    add: (Add caseClass) => operand + caseClass.value,
    subtract: (Subtract caseClass) => operand - caseClass.value,
  );
}

Having access to the actual case class can be useful if you want to, for example, call copyWith on it or if you're implementing a BlocBuilder widget where you want to map incoming states to Widgets.

BONUS: VS Code snippets

The boilerplate you need to write with **freezed **is minimal but it's still there. I'm sure you'll find these snippets useful. Just hit F1 or Ctrl/Cmd + Shift + P, input _"Configure User Snippets"_and edit the dart.json file.
dart.json

{
  ...
  "Part statement": {
    "prefix": "pts",
    "body": [
      "part '${TM_FILENAME_BASE}.g.dart';",
    ],
    "description": "Creates a filled-in part statement"
  },
  "Part 'Freezed' statement": {
    "prefix": "ptf",
    "body": [
      "part '${TM_FILENAME_BASE}.freezed.dart';",
    ],
    "description": "Creates a filled-in freezed part statement"
  },
  "Freezed Data Class": {
    "prefix": "fdataclass",
    "body": [
      "@immutable",
      "abstract class ${1:DataClass} with _${1:DataClass}{",
      "  const factory ${1:DataClass}(${2}) = _${1:DataClass};",
      "}"
    ],
    "description": "Freezed Data Class"
  },
  "Freezed Union": {
    "prefix": "funion",
    "body": [
      "@immutable",
      "abstract class ${1:Union} with _${1:Union}{",
      "  const factory ${1:Union}.${2}(${4}) = ${3};",
      "}"
    ],
    "description": "Freezed Union"
  },
  "Freezed Union Case": {
    "prefix": "funioncase",
    "body": [
      "const factory ${1:Union}.${2}(${4}) = ${3};"
    ],
    "description": "Freezed Union Case"
  },
  ...
}

Show some support to Rémiby giving this package a like on pub and a star on GitHub! We finally have a a data/sealed class solution which doesn't compromise on anything.

Flutter for Beginners 2020 - Build a Flutter App with Google's Flutter & Dart

Flutter for Beginners 2020 - Build a Flutter App with Google's Flutter & Dart

Flutter tutorial for Beginners 2020 - Build a Flutter App with Google's Flutter & Dart. Flutter Introduction for Beginners: Get Started with Flutter and learn how to build an iOS and Android app with Flutter! What is Flutter? How Flutter & Dart Code Gets Compiled? Why Choose Flutter? Flutter is Google's UI toolkit for crafting beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.

Free Flutter Introduction for Beginners: Get Started with Flutter and learn how to build an iOS and Android app with Flutter!


Content:

  • Introduction 00:03
  • What is Flutter 01:50
  • Flutter's Architecture 07:44
  • How Flutter & Dart Code Gets Compiled
  • To Native Apps 12:31
  • Understanding Flutter Versions 15:44
  • Flutter macOS Setup 18:34
  • macOS Development Environment 37:24
  • Flutter Windows Setup 41:32
  • Windows Development Environment 01:00:48
  • Flutter & Material Design 01:04:34
  • Flutter Alternatives 01:05:49
  • Course Outline 01:11:56
  • How To Get The Most Out Of The
  • Course 01:18:36
  • Module Introduction 01:21:17
  • Creating a New Project 01:23:42
  • An Overview of the Generated Files &
  • Folders 01:35:57
  • Analyzing the Default App 01:44:18
  • Dart Basics 01:49:50
  • More Dart Basics 02:07:08
  • Building an App From Scratch 02:20:15
  • Running the App on an Emulator 02:31:18
  • Class Constructors & Named
  • Arguments 02:34:43
  • First Summary & Additional Syntax 02:43:13
  • Building a Widget Tree 02:51:24
  • Visible (Input / Output) & Invisible
  • (Layout / Control) Widgets 02:59:04
  • Adding Layout Widgets 03:02:17
  • Connecting Functions & Buttons 03:09:09
  • Anonymous Functions 03:16:40
  • Updating Widget Data (Or: Using
  • StatelessWidget Incorrectly) 03:20:01
  • Updating Correctly with Stateful
  • Widgets 03:26:38
  • A Brief Look Under The Hood 03:38:36
  • Using Private Properties 03:41:04
  • Creating a New, Custom Widget 03:46:21
  • First Styling & Layouting Steps 03:58:11
  • Enums & Multiple Constructors 04:06:12
  • Official Docs & The Widget Catalog 04:10:19
  • Passing Callback Functions Around 04:12:59
  • Introducing Maps 04:23:37
  • Mapping Lists to Widgets 04:29:30
  • final vs const 04:39:54
  • Introducing "if" Statements 04:50:45
  • [DART DEEP DIVE] More on "if"
  • Statements 04:57:36
  • [DART DEEP DIVE] The "null" Value 05:08:52
  • Outputting Widgets Conditionally 05:10:55
  • Splitting the App Into Widgets 05:13:16
  • Calculating a Total Score 05:23:13
  • Getters & "else-if" 05:30:59
  • Resetting the Quiz 05:37:34
  • Wrap Up 05:42:28

Flutter Tutorial for Beginners 2 - Learning Dart Basics | Basics of Dart

Flutter Tutorial for Beginners 2 - Learning Dart Basics | Basics of Dart

Flutter Tutorial for Beginners 2 - Learning Dart Basics | Basics of Dart programming language. We will learn the basics of Dart. Dart is Object Oriented Language Developed By Google which is Primarily used for building websites, servers, and mobile apps. Flutter is an open-source UI software development kit created by Google.Flutter is SDK that makes creating high-performing, modern and good looking apps. Flutter is easy and Works for both Android and iOS. Flutter is An open-source toolkit, developed by Google.

In this video we will learn the basics of Dart. Dart is Object Oriented Language Developed By Google which is Primarily used for building websites, servers, and mobile apps.
Welcome to this course on Flutter Tutorials for beginners. Flutter is an open-source UI software development kit created by Google.Flutter is SDK that makes creating high-performing, modern and good looking apps. Flutter is easy and Works for both Android and iOS. Flutter is An open-source toolkit, developed by Google.