Shared Properties

Let's continue with our Weather example (you can find it here).

Define a Shared Property

In freezed, when a property is defined on all constructors, it is automatically considered as being a shared property.

For modddels, you should explicitly define shared properties this way :

@Modddel(
  // validationSteps: [...],
  sharedProps: [
    SharedProp('int', 'temperature'),
  ],
)

As you can see, you provide an array of SharedProp. Inside the SharedProp, you provide the type and the name of the shared property (which should be common to all factory constructors).

Now, you can directly access the temperature property from the super-sealed classes ValidWeather, InvalidWeather and InvalidWeatherValue, without case-modddels pattern matching :

// before :
final temperature = validWeather.mapWeather(
    sunny: (validSunny) => validSunny.temperature,
    rainy: (validRainy) => validRainy.temperature,
);

// after
final temperature = validWeather.temperature;

If you want to directly access the temperature property from the Weather super-sealed class too, temperature should first be accessible from the case-modddels Sunny and Rainy. Which means that it should be annotated with @withGetter in both factory constructors :

factory Weather.sunny({
  @withGetter required int temperature,
}) // { ... }

factory Weather.rainy({
  @withGetter required int temperature,
  required double rainIntensity,
}) // { ... }

Now you can do :

final temperature = weather.temperature;

Rules to keep in mind

A shared property should be of the same kind across all case-modddels (either a member or a dependency).

A shared property should have a parameter in all factory constructors.

If you want to share a parameter that is not present in all case-modddels, consider instead declaring it in the case-modddels where it's not present with a Null type. For example, let's suppose you want to share the rainIntensity parameter :

factory Weather.sunny({
  required int temperature,
  // We add this :
  Null rainIntensity,
}) // { ... }

factory Weather.rainy({
  required int temperature,
  required double rainIntensity,
}) // { ... }

Now you can add SharedProp('double?', 'rainIntensity').

For IterableEntity/Iterable2Entity2, if the shared property is the member parameter, its type must match the TypeTemplate.

Using a common supertype

The type of the shared property doesn't have to be the same in all factory constructors. Rather, the type specified inside the SharedProp should be a common supertype.

For example :

@Modddel(
  // validationSteps: [...],
  sharedProps: [
    SharedProp('num', 'price'),
    SharedProp('String?', 'summary'),
  ],
)
class Book extends MultiValueObject<InvalidBook, ValidBook> with _$Book {

  factory Book.fiction({
    required double price,
    required String summary,
  }) // { ... }
  
  factory Book.nonFiction({
    required int price,
    String? summary,
  }) // { ... }

  //...
}

As you can see, num is a common supertype of double and int, and String? is a common supertype of String and String?.

Shared Properties and Param Transformations

The param transformations (valid, null and non-null) accross the different case-modddels are taken into account for narrowing the type of the shared property in the different super-sealed classes.

For example : Let's say we have this User SimpleEntity :

@Modddel(
  validationSteps: [
    ValidationStep([
      Validation('incomplete', FailureType<UserIncompleteFailure>()),
    ]),
    ValidationStep([
      contentValidation,
    ]),
  ],
  sharedProps: [
    SharedProp('Username?', 'username'),
    SharedProp('Age?', 'age'),
  ]
)
class User extends SimpleEntity<InvalidUser, ValidUser> with _$User {
  User._();

  factory User.appUser({
    @NullFailure('incomplete', UserIncompleteFailure.noUsername())
      required NormalUsername? username,
    @validParam required ValidAge age,
  }) {
    return _$User._createAppUser(username: username, age: age);
  }

  factory User.moderator({
    @NullFailure('incomplete', UserIncompleteFailure.noUsername()) 
      required ModeratorUsername? username,
    @NullFailure('incomplete', UserIncompleteFailure.noAge()) 
      required Age? age,
  }) {
    return _$User._createModerator(username: username, age: age);
  }

  // validate methods ....
}

In this example, User is a union of two SimpleEntities : AppUser and Moderator. They have two shared properties : username and age. Username is a union of two ValueObjects : NormalUsername and ModeratorUsername. Age is a ValueObject.

This table represents the types of these two shared properties in the different super-sealed classes :

propertyUserInvalidUserEarlyInvalidUserMidValidUser

username

Username?

Username?

Username

ValidUsername

age

Age?

Age?

Age

ValidAge

For username :

  • The type of username becomes non-nullable in InvalidUserMid, because at that point username is not null in the two case-modddels (InvalidAppUserMid and InvalidModeratorMid) : the two NullFailures have been processed in the previous validationStep.

  • The type of username becomes valid (ValidUsername) in ValidUser, because at that point username is valid in the two case-modddels (ValidAppUser and ValidModerator) : the contentValidation has been processed in the previous validationStep.

For age :

  • The type of age becomes non-nullable in InvalidUserMid, because at that point age is not null in the two case-modddels :

    • In InvalidAppUserMid : age was already non-nullable.

    • In InvalidModeratorMid : the NullFailure has been processed in the previous validationStep.

  • The type of age becomes valid (ValidAge) in ValidUser, because at that point age is valid in the two case-modddels :

    • In ValidAppUser : age was already valid, because it's annotated with @validParam.

    • In ValidModerator : the contentValidation has been processed in the previous validationStep.

This example only showcases the Valid and Non-null param transformations, but it works the same way for Null param transformations : If a shared parameter becomes 'Null' in all case-modddels, then it becomes 'Null' in the appropriate super-sealed class.

Disabling Param Transformations for a Shared Property

Sometimes, you may want to disable a certain kind of param transformation for a shared property.

You can do so easily :

SharedProp('Price?', 'price', 

  // Ignoring Valid Param Transformations. This means that, even if the
  // type of [price] becomes valid in all case-modddels, this shared 
  // property won't have its type [Price?] narrowed down to [ValidPrice?].
  //
  ignoreValidTransformation: true,

  // Ignoring Non-Null Param Transformations. This means that, even if the
  // type of [price] becomes non-nullable in all case-modddels, this
  // shared property won't have its type [Price?] narrowed down to [Price].
  //
  ignoreNonNullTransformation: true,

  // Ignoring Null Param Transformations. This means that, even if the
  // type of [price] becomes 'Null' in all case-modddels, this shared  
  // property won't have its type [Price?] narrowed down to [Null].
  //
  ignoreNullTransformation: true,
),