Maybe copyWith

Pub

A lightweight package that lets you write ergonomic copyWith methods WITHOUT any code generation or build_runner.

maybe_copy_with fixes one pain only: how to tell “leave the field as‑is” from “explicitly set it to null for nullable properties—while keeping the call site short and expressive.


📦 Installation

flutter pub add maybe_copy_with

or add it manually to your pubspec.yaml:

dependencies:
  maybe_copy_with: ^<latest_version>

💡 One‑shot overview

import 'package:maybe_copy_with/maybe_copy_with.dart';

void main() {
  // Original instance
  final john = User(name: 'John', age: 18, gender: 'Male');

  // 1️⃣ Change a non‑nullable field — `gender` stays untouched
  final bob = john.copyWith(name: 'Bob');
  // or
  // final bob = john.copyWith(name: 'Bob', gender: null);

  // 2️⃣ Change both a non‑nullable and a nullable field
  final alice = bob.copyWith(
    name: 'Alice',
    gender: 'Female'.maybe(), // wrap with .maybe() to *replace* a nullable field
  );

  // 3️⃣ Explicitly reset the nullable field to null
  final android = alice.copyWith(
    gender: null.maybe(), // set to null via .maybe()
  );

  print(john);    // User(name: John, age: 18, gender: Male)
  print(bob);     // User(name: Bob, age: 18, gender: Male)
  print(alice);   // User(name: Alice, age: 18, gender: Female)
  print(android); // User(name: Alice, age: 18, gender: null)
}

class User with MaybeCopyWith<User> {
  final String name;
  final int age;
  final String? gender;

  const User({
    required this.name,
    required this.age,
    this.gender,
  });

  /// Returns a **new** [User] where only the fields you specify are changed.
  ///
  /// * `name`, `age` – plain nullable params. Pass a new value to replace the
  ///   current one, or omit (leave `null`) to keep the existing value.
  /// * `gender` – wrapped in [Maybe] so you have **three** options:
  ///   * `null` or omit → keep current `gender` value;
  ///   * `'Female'.maybe()` → set a new non‑null value;
  ///   * `null.maybe()` → explicitly reset to `null`.
  @override
  User copyWith({
    String? name,
    int? age,
    Maybe<String>? gender,
  }) {
    return User(
      name: name ?? this.name,
      age: age ?? this.age,
      gender: gender.or(this.gender),
    );
  }

  @override
  String toString() => 'User(name: $name, age: $age, gender: $gender)';
}

Why this API?

Scenario Call site
keep field as is gender: null or omit the parameter
replace with a new value gender: 'Alice'.maybe()
reset to null (for T?) gender: null.maybe()

For non‑nullable fields T, passing null already means “leave it unchanged”, so no wrapper is needed.


🔑 API essentials

/// Wrapper that marks a value as “explicitly provided”.
typedef Maybe<T>;

/// Converts a value to Maybe.
/// `null` goes through untouched, so `null.maybe()` returns `null`.
Maybe<T> maybe();

/// Returns the wrapped value, or [fallback] if the Maybe is `null`.
T? or(T? fallback);

MaybeCopyWith<T> mixin

Adds a single signature T copyWith(); so your IDE will remind you to implement it.


✨ Highlights

  • Zero dependencies — pure Dart, works on Mobile, Web and Desktop.
  • No build_runner — lack of code generation.

🚧 Limitations

  • Fields must still be listed manually — there is no reflection (dart:mirrors is unavailable on Flutter) and macros are still in IceBox.
  • Nullable fields require Maybe<T>? in the copyWith parameters.

📝 Changelog

Please see the Changelog page to know what's recently changed.

🤝 Contributions

Feel free to contribute to this project.

If you find a bug or want a feature, but don't know how to fix/implement it, please fill an issue. If you fixed a bug or implemented a feature, please send a pull request.

Libraries

maybe_copy_with