flutter_stasis_core 0.2.0
flutter_stasis_core: ^0.2.0 copied to clipboard
Pure Dart core of the Stasis ecosystem. StateObject, ViewModelState, Command, CommandPolicy and CommandResult — no Flutter, no third-party dependencies.
flutter_stasis_core #
Pure Dart core of the Stasis ecosystem.
No Flutter dependency. No third-party result type. Just the contracts that power everything else.
What's in this package #
| Class | Description |
|---|---|
ViewModelState<F, S> |
Sealed lifecycle — InitialState, LoadingState, SuccessState, ErrorState |
StateObject<F, S, Self> |
Base for immutable per-screen state |
Command<F, R> |
Contract for executable async work |
CommandResult<F, R> |
Sealed result — CommandSuccess, CommandFailure |
CommandPolicy |
Concurrency strategy — parallel, droppable, restartable, sequential |
CommandAction |
Executor that wires command + policy + callbacks |
StateFailure |
Optional base class for typed failures |
ViewModelState #
Four states that cover every async lifecycle — no ambiguous combinations:
sealed class ViewModelState<F, S> {
T when<T>({
required T Function() initial,
required T Function() loading,
required T Function(S data) success,
required T Function(F failure) error,
});
}
Usage:
state.when(
initial: () => const SizedBox(),
loading: () => const CircularProgressIndicator(),
success: (data) => DataWidget(data),
error: (failure) => ErrorWidget(failure.message),
);
StateObject #
Immutable state container for a single screen. Extend it and add your own fields:
class SearchState extends StateObject<AppFailure, List<Result>, SearchState> {
const SearchState({
required super.state,
this.query = '',
});
final String query;
// Derive from lifecycle — never duplicate
List<Result>? get results => dataOrNull;
String? get errorMessage => failureOrNull?.message;
@override
SearchState withState(ViewModelState<AppFailure, List<Result>> state) =>
copyWith(state: state);
SearchState copyWith({
ViewModelState<AppFailure, List<Result>>? state,
String? query,
}) => SearchState(
state: state ?? this.state,
query: query ?? this.query,
);
@override
List<Object?> get props => [state, query];
}
Built-in getters:
state.isLoading // true when LoadingState
state.isSuccess // true when SuccessState
state.isError // true when ErrorState
state.dataOrNull // S? — non-null only when SuccessState
state.failureOrNull // F? — non-null only when ErrorState
Command #
A unit of async work that returns CommandResult<F, R>:
abstract class Command<F, R> {
Future<CommandResult<F, R>> call();
}
Wrap any function with TaskCommand:
final command = TaskCommand<AppFailure, List<Project>>(
() => repository.getAll(),
);
Map the success value without touching the ViewModel:
final command = TaskCommand<AppFailure, List<ProjectEntity>>(
() => repository.getAll(),
).map((entities) => entities.map(ProjectCard.fromEntity).toList());
CommandResult #
final result = await command();
result.fold(
onFailure: (failure) => print('Error: ${failure.message}'),
onSuccess: (data) => print('Got ${data.length} items'),
);
// Or use the accessors
final data = result.resultOrNull;
final failure = result.failureOrNull;
CommandPolicy #
Control concurrency per execution:
await CommandAction.execute(
command: searchCommand,
onLoading: ...,
onError: ...,
onSuccess: ...,
policy: CommandPolicy.restartable, // cancels previous on new call
);
| Policy | Behaviour |
|---|---|
parallel (default) |
Every call runs independently |
droppable |
Ignores new calls while one is in-flight |
restartable |
Keeps only the latest call's callbacks |
sequential |
Queues calls, runs one at a time |
This package is framework-agnostic #
flutter_stasis_core has no Flutter SDK dependency and no opinion on how results are produced. It works with plain Future, with dartz Either, with fpdart, or any other style via adapters.
If you want Flutter widgets and the full ViewModel base class, add flutter_stasis.
License #
MIT