freezed icon indicating copy to clipboard operation
freezed copied to clipboard

Generate a builder-like method as an alternative to copyWith for modifying immutable Freezed objects

Open zeekhuge opened this issue 3 years ago • 6 comments

Is your feature request related to a problem? Please describe. So lets say I am taking input from the user using a form. The input from the form is stored in freezed-object FormData. Now, I want to be able to incrementally create the FormData object. This can easily be done using Builder Pattern. Such a Builder object should also allow to validate the data before actually creating the FormData.

Describe the solution you'd like Something similar to Builder Pattern which would allow to create the final object from data coming in steps.

zeekhuge avatar Jun 08 '22 20:06 zeekhuge

Hello! Could you give a clear example of what you would expect Freezed to do?

rrousselGit avatar Jun 08 '22 20:06 rrousselGit

Consider the following scenario : (using pseudo code below)

/*************************************/
/***** File - data_object.dart *****/

@freezed
DataObject {
    String? id;
    String name;
    String phoneNumber;
    DateTime? createdAt;
    // createdAt & id can be null as they will be added by the server, when saved
}

/*************************************/
/***** File - some_form.dart *****/

StatelessWidget SomeForm {

  final db = Database();

   // Temporary variables
   String name; 
   String phoneNumber;
   
    build () => Column [
        TextInput("Enter name", onChanged: (input) => name = input),
        TextInput("Enter phoneNumber", onChanged: (input) => phoneNumber = input),
        Button("Submit",  onTap: () => db.save(DataObject(name: name, phoneNumber: phoneNumber))) 
    ]
}


/*************************************/
/********* File - database.dart ************/

class Database () {
    save (DataObject data) {
        // saves data into DB 
        // ...
    }
}

Now lets focus on the form widget - SomeForm here.

The above code is fine when the DataObject in question is simple enough. However, imagine a DataObject with 10s of properties. The SomeForm for such a data-object will have to create many temporary variables to temporarily save the onChanged data from the inputs. A "builder pattern" (similar to what built_value has, but then built_value has a lot of other problems) which would allow to add data to the builder of the object as we get it from the inputs, can help.

Furthermore, we could add validation logic to such builders, to put specific constraints over the values these objects could contain.

zeekhuge avatar Jul 25 '22 07:07 zeekhuge

built_value has this mechanism, also AutoValue for Java allows us to extract a builder from an immutable object.

It would be nice to have a PersonBuilder generated for us, if we have a Person freezed object, and a toBuilder that returns that object's mutable Builder. This has to be done carefully as this will demand some customizations, like how can we validate the data before building it. Fortunately there are tons of use cases already documented in AutoValue and built_value.

feinstein avatar Aug 28 '22 15:08 feinstein

builders are also really nice when you need to mutate multiple collections (at arbitrary depth), especially beyond simply adding values

pseudocode

@freezed Foo {
  Map<String, String> myMap,
  List<String> myList,
  bool? nullableBool,
}

final foo = Foo({}, ['yo'], true);

foo.build((b) => b
  ..myMap['addThis'] = 'this was just added'
  ..myList.remove('yo')
  ..nullableBool = null,
);

vs

foo.copyWith(
  myMap: { ...foo.myMap, 'addThis': 'this was just added' },
  myList: List.from(foo.myList)..remove('yo'),
  nullableBool: ?????????????
)

lukepighetti avatar Nov 05 '22 17:11 lukepighetti

I agree with the value. @unfreeezed was released as an in-between step for this.
At some point, this will exist.

Although I'll admit I have other more important things to do. Especially when in the future Dart will have the necessary features to make Freezed redudant.

rrousselGit avatar Nov 05 '22 18:11 rrousselGit

In the JS world this is handled by ImmerJS which basically creates a copy that then replaces the original data.

import produce from "immer"

const nextState = produce(baseState, draft => {
    draft[1].done = true
    draft.push({title: "Tweet about it"})
})

Maybe something like this could be made in Dart too, so it's able to work with any type of immutable data, not just freezed.

satvikpendem avatar Jan 10 '23 01:01 satvikpendem