pub package popularity likes pub points building

Features

This package provides a stream-based way to represent stateful services in Dart.

Primary benefits of using this package include:

Simplicity

The service's state is represented using a single (preferably immutable) value, that can only be changed from inside the service itself.

Isolation

All operations on the service's state are serialized using a single event queue, which reduces the complexity of managing concurrent state changes and minimizes the risk of race conditions.

Safety

If an update fails, the service will automatically revert to the state it had before the update started. This makes it simple to implement complex state changes such as optimistic UI updates, without having to worry about leaving the service in an inconsistent state.

Portability

Since the service is based on Dart's own primitives, it can be used with any state management solution, or even without one.

Transferable knowledge

Streams are an incredibly powerful concept that is widely used across the entire software development industry. Unlike things like Listenables, Notifiers, etc., once you familiarize yourself with streams, you'll be able to transfer that knowledge to almost any other framework or programming language that you'll pick up in the future.

Getting started

To use this package, add stateful_service as a dependency in your pubspec.yaml file.

dependencies:
  stateful_service: ^1.0.0

Creating a service

To create a service, import the package in your Dart code and create a subclass of StatefulService<T>.

import 'package:stateful_service/example.dart';

class UserService extends StatefulService<User> {
  UserService({required super.initialState});
  
  ...

  /// Updates the user's name.
  Future<void> updateName(String newName) => update((user) async {
    await _api.updateName(newName);
    return user.withName(newName);
  });

  /// Updates the user's name, updating listeners (e.g. the UI) optimistically. Will roll back if
  /// the call to `_api.updateName` fails.
  Future<void> updateNameOptimistic(String newName) => streamUpdates((user, save) async* {
    yield user.withName(newName);
    await _api.updateName(newName);
  });
}

Consuming a service

StatefulServices are stream-based, which means that you can consume state updates the same way you would consume any other stream in Dart, with the added convenience of always having access to the service's current state via the state getter.

E.g. using a StreamBuilder in Flutter:


Widget build(BuildContext context) => StreamBuilder(
  stream: service.stream,
  builder: (_, __) => MyWidget(service.state),
);

Additional information

NOTE: To further reduce the risk of race conditions and other unexpected behavior, it is recommended to use immutable collection types for any collections that are part of the service's state. Some popular packages that provide such types are built_collection, dartz and fast_immutable_collections.