cesium 1.1.2
cesium: ^1.1.2 copied to clipboard
A lightweight state management library focusing on Listenable, ChangeNotifier and ValueNotifier to complement flutter instead of revamp it
Cesium #
A lightweight Flutter state management library built around Listenable, ChangeNotifier, and ValueNotifier to complement the Flutter ecosystem without replacing it.
Cesium gives you small, composable building blocks for async loading states, derived values, HTTP-backed resources, and service registration so you can keep your app logic simple and reactive.
Features #
FutureResource<T>for loading, success, and error states with aValueNotifierHttpResource<T>for HTTP-backed data sources with debounce, dependency reloading, and progress-aware loading statesPaginatedHttpResource<T>for paged results and list pagination patterns, including inline start, loading, error, and end-of-list statesComputedResource<T>for derived values that automatically recompute when dependencies changeCesiumHttpServicefor injectable HTTP clients with base options, interceptors, and reset support for testingHttpResourceBaseto unify request execution, debounce behavior, dependency tracking, and HTTP progress updatesCesiumServicebase type plus override/reset helpers for dependency injection and test isolationManagedListenerMixinto make listener cleanup easier in stateful widgets- Fluent listenable extensions for building widgets directly from resources and notifiers
- Centralized error handling via
Cesium.logError()andCesium.setErrorHandler()
Installation #
Add this to your pubspec.yaml:
dependencies:
cesium: ^1.1.2
Then import it in your Dart code:
import 'package:cesium/cesium.dart';
Getting started #
Cesium is designed to be used alongside standard Flutter widgets and state patterns. It does not replace State, ChangeNotifier, or ValueNotifier; instead, it adds useful wrappers around them for common app needs.
Usage #
1. Loading state with FutureResource #
import 'package:cesium/cesium.dart';
import 'package:flutter/material.dart';
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
late final FutureResource<Map<String, dynamic>> profile;
@override
void initState() {
super.initState();
profile = FutureResource(() => fetchProfile());
}
Future<Map<String, dynamic>> fetchProfile() async {
await Future.delayed(const Duration(milliseconds: 500));
return {'name': 'Ada Lovelace'};
}
@override
Widget build(BuildContext context) {
return profile.pipe(
loading: (_) => const Center(child: CircularProgressIndicator()),
error: (_, error) => Center(child: Text('Error: $error')),
value: (context, data) => Center(child: Text(data['name'])),
);
}
@override
void dispose() {
profile.dispose();
super.dispose();
}
}
2. Derived values with ComputedResource #
final items = ValueNotifier<List<String>>(['alpha', 'beta', 'gamma']);
final filteredItems = ComputedResource(
() => items.value.where((item) => item.startsWith('a')).toList(),
[items],
);
final widget = filteredItems.pipe((context, value) {
return Text(value.join(', '));
});
When items changes, filteredItems recalculates automatically.
3. HTTP resources with HttpResource #
final posts = HttpResource<List<Post>>(
() => 'https://jsonplaceholder.typicode.com/posts',
(json) => (json as List)
.map((item) => Post.fromJson(item as Map<String, dynamic>))
.toList(),
debounceDuration: const Duration(milliseconds: 250),
);
final page = posts.pipeProgress(
loading: (context, progress) => Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(),
if (progress > 0 && progress < 1)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text('${(progress * 100).toStringAsFixed(0)}%'),
),
],
),
),
error: (_, error) => Text('Failed to load posts: $error'),
value: (context, list) => ListView.builder(
itemCount: list.length,
itemBuilder: (_, index) => Text(list[index].title),
),
);
HttpResourceBase handles request execution, dependency-triggered reloads, debounce delays, and progress updates automatically.
4. Paginated resources #
final comments = PaginatedHttpResource<Comment>(
() => 'https://example.com/comments',
(json) => Comment.fromJson(json),
(page) => {'page': page},
getMaxPages: (comment) => 5,
);
comments.loadMore();
PaginatedHttpResource keeps accumulating values across pages and supports inline header, loading, error, and end-of-list widgets through pipePaginated(...) when you want a list-style UI.
For example, you can add fixed widgets at the top of the list before the paginated items with inlineStart:
comments.pipePaginated(
loading: (_, progress) => const Center(child: CircularProgressIndicator()),
error: (_, error) => Center(child: Text('Error: $error')),
inlineStart: (_) => [
const Padding(
padding: EdgeInsets.all(12),
child: Text('Latest posts'),
),
],
inlineLoading: (_, progress) => Padding(
padding: const EdgeInsets.all(12),
child: Center(child: CircularProgressIndicator(value: progress)),
),
inlineError: (_, error) => Padding(
padding: const EdgeInsets.all(12),
child: Text('Could not load more: $error'),
),
inlineEnd: (_) => const Padding(
padding: EdgeInsets.all(12),
child: Text('You reached the end'),
),
itemBuilder: (context, post, index) => ListTile(title: Text(post.title)),
getItems: (post) => [post],
);
The inlineStart widgets render before the paginated items, while inlineLoading, inlineError, and inlineEnd remain appended after the loaded content in the same list.
5. Services and dependency injection #
class AuthService extends CesiumService {
bool loggedIn = false;
@override
void reset() {
loggedIn = false;
notifyListeners();
}
}
void main() {
register<AuthService>(() => AuthService());
final auth = injectService<AuthService>();
}
You can also override a registered service for tests or environment-specific configuration:
registerOverride<AuthService>(() => TestAuthService());
final auth = injectService<AuthService>();
The shared CesiumHttpService is also injectable, which makes it easier to replace the default HTTP client or reset it between tests.
final http = ServiceProvider.withOverride(
() => CesiumHttpService(),
() => MockCesiumHttpService(),
);
6. Widget piping helpers #
final notifier = ValueNotifier<String>('hello');
final widget = notifier.pipe(
(context, value) => Text(value),
);
The library also includes pipeChild helpers for reusing a static child widget while only rebuilding the dynamic portion of the UI.
Error handling #
You can set a global error handler:
Cesium.setErrorHandler((error) {
debugPrint('Cesium error: $error');
});
This is used by the async resource types when a request or computation fails, and it can also be triggered manually via Cesium.logError(error).
Why Cesium? #
Cesium is a small, pragmatic toolkit for Flutter apps that want:
- clearer async UI states
- a lightweight alternative to larger state management frameworks
- minimal boilerplate for derived values and remote resources
- easy integration with standard Flutter
Listenablepatterns
Additional information #
- Repository: see the package source and tests in this workspace for the current implementation.
- Issues and feedback: open an issue in the package repository if available.
- Contributions are welcome for improvements, bug fixes, and additional helpers that fit the library's lightweight philosophy.