atomx 1.0.0
atomx: ^1.0.0 copied to clipboard
A value-centric state management library for Flutter, built on ValueNotifier. Atomx treats values and states as equals, allowing you to attach state directly to values instead of pages.
Atomx #
A value-centric state management library for Flutter, built on ValueNotifier. Atomx treats values and states as equals, allowing you to attach state directly to values instead of pages.
âĻ Features #
| ðŊ Value-Centric | ð Reactive | ðĻ Composable | ðŠķ Lightweight |
|---|---|---|---|
| State belongs to values | Automatic UI updates | Single builder | Native Flutter |
ð Why Atomx? #
ðŊ Value-Centric #
State belongs to values, not pages. This means:
- Data owns its state (loading, error, success)
- UI simply reflects the data's state
- Components can be reused anywhere since they're not tied to page state
- State changes are automatically propagated to all listeners
- Testing is simpler as you can test state logic independently
For example, the same messages data can be displayed in different ways:
// Chat list Component
class ChatListComponent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AtomxBuilder(
builder: (context) {
final messages = GetIt.I<ChatController>().messages;
return ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) => MessageTile(message: messages[index]),
);
},
);
}
}
// Chat grid Component
class ChatGridComponent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AtomxBuilder(
builder: (context) {
final messages = GetIt.I<ChatController>().messages;
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: messages.length,
itemBuilder: (context, index) => MessageCard(message: messages[index]),
);
},
);
}
}
ð Reactive #
UI automatically updates when values or states change. This means:
- No need for manual state propagation
- UI updates automatically when data changes
- No need for setState or other manual update triggers
For example, updating a user's profile automatically updates all UI components:
class ProfileController {
final user = AtomxState<User, UserState>(User.empty(), UserState.initial);
Future<void> updateProfile(String name) async {
try {
user.update(state: UserState.loading);
final updatedUser = await api.updateProfile(name);
user.update(value: updatedUser, state: UserState.loaded);
} catch (e) {
user.update(state: UserState.error);
}
}
}
// All these widgets will update automatically when user changes
class ProfileHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AtomxBuilder(
builder: (context) {
final user = GetIt.I<ProfileController>().user;
return Text(user.value.name);
},
);
}
}
class ProfileStats extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AtomxBuilder(
builder: (context) {
final user = GetIt.I<ProfileController>().user;
return Text('${user.value.posts} posts');
},
);
}
}
ðĻ Composable #
Combine multiple value states in a single builder. This means:
- No need for multiple builders or listeners
- Clean and readable code
- Better performance as all states are handled in one place
For example, handling multiple states in a single builder:
class ProductDetails extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AtomxBuilder(
builder: (context) {
final product = GetIt.I<ProductController>().product;
final reviews = GetIt.I<ProductController>().reviews;
final related = GetIt.I<ProductController>().related;
// Handle all loading states in one place
if (product.state == ProductState.loading ||
reviews.state == ReviewsState.loading ||
related.state == RelatedState.loading) {
return LoadingIndicator();
}
// Handle all error states in one place
if (product.state == ProductState.error ||
reviews.state == ReviewsState.error ||
related.state == RelatedState.error) {
return ErrorView();
}
// Build UI with all data
return Column(
children: [
ProductHeader(product: product.value),
ReviewsList(reviews: reviews.value),
RelatedProducts(products: related.value),
],
);
},
);
}
}
ðŠķ Lightweight #
Built on Flutter's native ValueNotifier. This means:
- No external dependencies
- Small bundle size
- Familiar API for Flutter developers
For example, using native Flutter patterns:
// Familiar ValueNotifier pattern
final counter = Atomx<int>(0);
// Easy to understand state updates
counter.update(value: counter.value + 1);
// Simple state management
final user = AtomxState<User, UserState>(User.empty(), UserState.initial);
user.update(value: newUser, state: UserState.loaded);
ðĶ Observable Types #
// 1. Basic value
final counter = Atomx<int>(0);
// 2. Value with state
final user = AtomxState<User, UserState>(User.empty(), UserState.initial);
// 3. List
final todos = AtomxList<Todo>([]);
// 4. Map
final users = AtomxMap<String, User>({});
// 5. List with state
final messages = AtomxListState<Message, MessagesState>([], MessagesState.initial);
// 6. Map with state
final contacts = AtomxMapState<String, Contact, ContactsState>({}, ContactsState.initial);
ðŽ Example: Chat App #
// Create a controller to manage the chat domain
class ChatController {
final currentUser = AtomxState<User, UserState>(User.empty(), UserState.initial);
final messages = AtomxListState<Message, MessagesState>([], MessagesState.initial);
final contacts = AtomxMapState<String, Contact, BaseState>({}, const LoadingState());
Future<void> loadCurrentUser() async {
currentUser.update(state: UserState.loading);
try {
final user = await fetchCurrentUser();
currentUser.update(value: user, state: UserState.loaded);
} catch (e) {
currentUser.update(state: UserState.error);
}
}
}
// Create a reusable component that reacts to the controller
class MessageList extends StatelessWidget {
final Function(Message) onMessageSelected;
const MessageList({
required this.onMessageSelected,
super.key,
});
@override
Widget build(BuildContext context) {
final controller = GetIt.I<ChatController>();
return AtomxBuilder(
builder: (context) {
final user = controller.currentUser;
final messages = controller.messages;
final contacts = controller.contacts;
if (user.state == UserState.loading ||
messages.state == MessagesState.loading ||
contacts.state is LoadingState) {
return CircularProgressIndicator();
}
return ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
final contact = contacts[message.senderId];
final isCurrentUser = message.senderId == user.value.id;
return ListTile(
leading: isCurrentUser ? null : CircleAvatar(
backgroundImage: NetworkImage(contact?.avatarUrl ?? ''),
),
title: Text(contact?.name ?? 'Unknown'),
subtitle: Text(message.content),
trailing: isCurrentUser ? Icon(Icons.check) : null,
onTap: () => onMessageSelected(message),
);
},
);
},
);
}
}
// Use the component in any page
class ChatPage extends StatelessWidget {
const ChatPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Chat')),
body: MessageList(
onMessageSelected: (message) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MessageDetailsPage(messageId: message.id),
),
);
},
),
);
}
}
See the example app for a complete implementation.