DF - Pod

Buy Me A Coffee

Dart & Flutter Packages by DevCetra.com & contributors.

Pub Package MIT License


Summary

This package provides tools for managing app state using ValueNotifier-like objects called Pods (an acronym for "Points of Data"). For a practical demonstration of how Pods work in conjunction with GetIt for state management, refer to this example. For a full feature set, please refer to the API reference.

Quickstart

ℹ️ Defining a Pod:

// Define a Pod, similar to how you would define a ValueNotifier.
final pNumbers = Pod<List<int>>([1, 2, 3, 4, 5]);

ℹ️ Using a PodBuilder in the UI:

final pNumbers = Pod<List<int>>([1, 2, 3, 4, 5]);

// Use the PodBuilder in your UI, similar to a ValueListenableBuilder.
PodBuilder(
  pod: pNumbers,
  builder: (context, snapshot) {
    final numbers = snapshot.value!;
    return Text('Count: ${numbers.length}');
  },
);

// You can also use a regular old ValueListenableBuilder.
ValueListenableBuilder(
  valueListenable: _pCounter1,
  builder: (context, value, child) {
    return Text('Count: $value');
  },
);

ℹ️ Using PodBuilder with Futures:

// PodBuilders can also take Futures.
final pNumbers = Future.delayed(const Duration(seconds: 3), () => Pod<int>(1));

PodBuilder(
  pod: pNumbers,
  builder: (context, snapshot) {
    final numbers = snapshot.value;
    final completed = numbers != null;
    if (completed) {
      return Text('Count: ${numbers.length}');
    } else {
      return Text('Loading...');
    }
  },
);

ℹ️ Setting and Updating a Pod:

final pNumbers = Pod<List<int>>([1, 2, 3, 4, 5]);

// Set a Pod with the set function. This will trigger all associated PodBuilders to rebuild.
pNumbers.set([1, 2, 4]);

// Update a Pod with the update function. This will also trigger all associated PodBuilders to rebuild.
pNumbers.update((e) => e..add(5));

ℹ️ Disposing of Pods:

final pNumbers = Pod<List<int>>([1, 2, 3, 4, 5]);

// Dispose of Pods when they are no longer needed.
pNumbers.dispose();

ℹ️ PodBuilder Optimization:

// If the Pod<T> type T is a primitive type or implements Equatable*,
// the PodBuilder will only rebuild if the Pod's value actually changed.
final pHelloWorld = Pod('Hello World');

// This will NOT trigger a rebuild, as String is a primitive, pass-by-value type.
pHelloWorld.set('Hello World');

* Find the Equatable package here: https://pub.dev/packages/equatable

ℹ️ Transforming a Pod into a ChildPod:

final pNumbers = Pod<List<int>>([1, 2, 3, 4, 5]);

// A Pod can be transformed into a ChildPod using the map function.
final pLength = pNumbers.map((e) => e!.length);

final ChildPod<List<int>, int> pSum = pNumbers.map((e) => e!.reduce((a, b) => a + b));

PodBuilder(
  pod: pSum,
  // Changing pNumbers will trigger a rebuild.
  builder: (context, snapshot) {
    final sum = snapshot.value!;
    return Text('Sum: ${sum}');
  },
);

ℹ️ Further Mapping a ChildPod:

final pNumbers = Pod<List<int>>([1, 2, 3, 4, 5]);
final pLength = pNumbers.map((e) => e!.length);

// A ChildPod can be further mapped into another ChildPod.
final pInverseLength = pLength.map((e) => 1 / e!);

ℹ️ Reducing Multiple Pods into a ChildPod:

final pNumbers = Pod<List<int>>([1, 2, 3, 4, 5]);
final pLength = pNumbers.map((e) => e!.length);
final pInverseLength = pLength.map((e) => 1 / e!);

// Pods can also be reduced into a single ChildPod:
final pZero = pLength.reduce(pInverseLength, (p1, p2) => p1.value * p2.value);

ℹ️ Restrictions on ChildPods:


final Pod<String> pParent = Pod('I am a Parent');

pParent.update((e) => e.replaceAll('Parent', 'Father')); // ✔️ OK!

final ChildPod<String, String> pChild = pParent.map((e) => e.replaceAll('Father', 'Son'));

// A ChildPod cannot be set or updated directly. When its parent changes,
// its value is immediately updated from its mapping function.
pChild.update((e) => e.replaceAll('Son', 'Daughter')); // ❌ Syntax error!

// Attempting to add listeners or dispose of a ChildPod will result in a syntax
// error if you've set the `protected_member` rule in your
// `analysis_options.yaml` file. This design eliminates the need for direct
// disposal of a ChildPod via the dispose() method.

// These will trigger syntax errors if you've correctly set up your
// analysis_options.yaml:
pChild.addListener(() {}); // ❌ ChildPods do not take listeners!
pChild.dispose(); // ❌ ChildPods do not need to be disposed of!

pParent.addListener(() => print('Parent changed!')); // ✔️ OK!
pParent.dispose(); // ✔️ OK! Disposes pChild as well, its children, their children, and so on.

ℹ️ Using Multiple Pods with PodListBuilder:

// You can use multiple Pods with a PodListBuilder.
PodListBuilder(
  podList: [pLength, pSum],
  builder: (context, snapshot) {
    final [length, sum] = snapshot.value!.toList();
    return Text('Length is $length and sum is $sum');
  },
);

ℹ️ Using PollingPodBuilder for Nullable Pods:

// Use a PollingPodBuilder when your Pod is initially nullable and will soon be updated to a non-null value.
// This approach is useful for prototyping and quick demonstrations but is not recommended for production code.
// The [podPoller] function is called periodically until it returns a non-null value.

Pod<List<int>>? pNumbers;

PollingPodBuilder(
  podPoller: () => pNumbers,
  builder: (context, snapshot) {
    final numbers = snapshot.value!;
    return Text('Count: ${numbers.length}');
  },
);

pNumbers = Pod<List<int>>([1, 2, 3, 4, 5]);

ℹ️ Using PodListCallbackBuilder:

The PodListCallbackBuilder widget allows you to build UI elements based on a dynamically generated list of Pods, reacting to changes in the Pods and the list itself. Unlike PodListBuilder, which listens to a static list of Pods, PodListCallbackBuilder can handle a chain of Pod dependencies, where changes in one Pod trigger the inclusion of additional Pods in the list. This makes it ideal for scenarios where Pods depend on the state of other Pods, allowing the list of Pods to evolve at runtime.

final pAppServices = Pod<AppServices?>(null);

PodListCallbackBuilder(
  listCallback: isLoggedInChain(),
  builder: (context, snapshot) {
    final isLoggedIn = isLoggedInSnapshot();
    return isLoggedIn == null
        ? CircularProgressIndicator()
        : Text('Logged In?: $isLoggedIn');
  },
);

// Define the listCallback function that returns the chain of Pods to listen to.
List<GenericPod?> isLoggedInChain() {
  return [
    // The root Pod: when pAppServices becomes non-null, it will provide access to pLoginService.
    pAppServices,
    // When pLoginService becomes non-null, it will provide access to pIsLoggedIn.
    pAppServices.value?.pLoginService,
    // pIsLoggedIn is only available when pLoginService is initialized and non-null.
    pAppServices.value?.pLoginService?.value.pIsLoggedIn,
  ];
}

// A utility function to easily get the current value of isLoggedIn.
bool? isLoggedInSnapshot() {
  return pAppServices.value?.pLoginService?.value.pIsLoggedIn?.value;
}

⚠️ Installation & Setup

  1. Use this package as a dependency by adding it to your pubspec.yaml file (see here).

  2. Update your analysis_options.yaml file to enforce syntax errors when attempting to access protected members or override non-virtual members. This is highly recommended because:

  • invalid_use_of_protected_member: error: Certain methods in this package are protected to ensure they are only used within controlled contexts, preserving the integrity and consistency of the state management pattern. Enforcing this rule helps prevent misuse that could lead to unexpected behavior or security issues.

  • invalid_override_of_non_virtual_member: error: Non-virtual members are not designed to be overridden, as doing so could compromise the internal logic and functionality of the service. Enforcing this rule ensures that the core behavior of the package remains stable and predictable, preventing accidental or unauthorized changes.

include: package:flutter_lints/flutter.yaml

analyzer:
  errors:
    invalid_use_of_protected_member: error
    invalid_override_of_non_virtual_member: error

Contributing and Discussions

This is an open-source project, and we warmly welcome contributions from everyone, regardless of experience level. Whether you're a seasoned developer or just starting out, contributing to this project is a fantastic way to learn, share your knowledge, and make a meaningful impact on the community.

Ways you can contribute:

  • Buy me a coffee: If you'd like to support the project financially, consider buying me a coffee. Your support helps cover the costs of development and keeps the project growing.
  • Share your ideas: Every perspective matters, and your ideas can spark innovation.
  • Report bugs: Help us identify and fix issues to make the project more robust.
  • Suggest improvements or new features: Your ideas can help shape the future of the project.
  • Help clarify documentation: Good documentation is key to accessibility. You can make it easier for others to get started by improving or expanding our documentation.
  • Write articles: Share your knowledge by writing tutorials, guides, or blog posts about your experiences with the project. It's a great way to contribute and help others learn.

No matter how you choose to contribute, your involvement is greatly appreciated and valued!


Chief Maintainer:

📧 Email Robert Mollentze at robmllze@gmail.com

Dontations:

If you're enjoying this package and find it valuable, consider showing your appreciation with a small donation. Every bit helps in supporting future development. You can donate here:

https://www.buymeacoffee.com/robmllze


License

This project is released under the MIT License. See LICENSE for more information.

Libraries

df_pod
A package offering tools to manage app state using ValueNotifier-like objects called Pods.