minix 1.2.0
SDKflutter
Platformandroidioswindowslinuxmacosweb
Reactive state management for Flutter with form validation, collection observables, async operations, dependency injection, and automatic memory management. Zero dependencies, high performance, and comprehensive testing support for scalable applications.
Minix 🚀 - Reactive State Management for Flutter
A powerful, lightweight, and intuitive reactive state management library for Flutter and Dart applications. Minix provides a comprehensive set of tools for managing application state with automatic reactivity, dependency injection, and memory management.
🚀 Features
- 🔄 Reactive Observables: Automatic UI updates when state changes
- 📝 Form Management: Built-in reactive forms with validation
- 📋 Collection Observables: Reactive lists and maps with automatic updates
- 🔄 Async Support: Built-in async operations handling
- 📡 Stream Integration: Reactive stream management
- 🧠 ComputedObservable: for derived state that auto-updates when dependencies change.
- ⚡️ runInAction: to batch multiple updates into a single reactive transaction.
- 💉 Dependency Injection: Simple and powerful IoC container
- 🧹 Auto Disposal: Automatic memory management to prevent leaks
- ⚡ High Performance: Optimized for minimal overhead and maximum efficiency
- 🎯 Type Safe: Full TypeScript-like type safety for Dart
- 🧪 Test Friendly: Built-in mocking support for easy testing
- 📱 Flutter Optimized: Designed specifically for Flutter widgets
- ⚡ Performance Optimized: Minimal rebuilds, maximum efficiency
📦 Installation
Add minix to your pubspec.yaml:
dependencies:
minix: ^1.2.0
Then run:
flutter pub get
🚀 Quick Start
Basic Observable
import 'package:minix/minix.dart';
// Create an observable
final counter = Observable(0);
// Use in a widget
class CounterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Observer(() {
return Text('Count: ${counter.watch()}');
});
}
}
// Update the value
//counter.value = counter.value + 1;
With Context
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ContextObserver((context) {
final count = counter.watch();
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () => counter.value++,
child: Text('Increment'),
),
],
);
});
}
}
📝 Form Management
Reactive Forms with Validation
// Create form instance globally or in a controller
final loginForm = FormObservable()
..addField(
'email',
initialValue: '',
validator: (value) {
if (value.isEmpty) return 'Email is required';
if (!value.contains('@')) return 'Invalid email format';
return null;
},
)
..addField(
'password',
initialValue: '',
validator: (value) {
if (value.isEmpty) return 'Password is required';
if (value.length < 6) return 'Password must be at least 6 characters';
return null;
},
);
class LoginForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ContextObserver((context) {
return Column(
children: [
// Email field
TextField(
onChanged: (value) => loginForm.setValue('email', value),
decoration: InputDecoration(
labelText: 'Email',
errorText: loginForm.fieldError('email').watch(),
),
),
// Password field
TextField(
onChanged: (value) => loginForm.setValue('password', value),
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
errorText: loginForm.fieldError('password').watch(),
),
),
// Submit button
ElevatedButton(
onPressed: loginForm.watchIsValid() ? _handleSubmit : null,
child: Text('Login'),
),
],
);
});
}
void _handleSubmit() {
final values = loginForm.getValues();
print('Email: ${values['email']}');
print('Password: ${values['password']}');
// You can also reset the form after submission
// loginForm.reset();
}
}
📋 List Management
Reactive Lists
// Create the list observable globally or in a controller
final todos = ListObservable<String>([
'Learn Flutter',
'Build awesome apps',
'Master Minix',
]);
class TodoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ContextObserver((context) {
final todoList = todos.watch();
return Column(
children: [
Text('Total todos: ${todos.watchLength()}'),
Expanded(
child: ListView.builder(
itemCount: todoList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todoList[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => todos.removeAt(index),
),
);
},
),
),
ElevatedButton(
onPressed: () => _addTodo(),
child: Text('Add Todo'),
),
],
);
});
}
void _addTodo() {
todos.add('New todo ${todos.length + 1}');
}
}
Advanced List Operations
final numbers = ListObservable<int>([1, 2, 3, 4, 5]);
// Filter reactive list
final evenNumbers = numbers.where((n) => n % 2 == 0);
// Transform reactive list
final doubledNumbers = numbers.map((n) => n * 2);
// Watch specific properties
Observer(() {
return Column(
children: [
Text('Count: ${numbers.watchLength()}'),
Text('Is empty: ${numbers.watchIsEmpty()}'),
Text('First: ${numbers.watchFirst() ?? 'None'}'),
Text('Last: ${numbers.watchLast() ?? 'None'}'),
],
);
});
// Bulk operations
numbers.addAll([6, 7, 8]);
numbers.removeWhere((n) => n > 5);
numbers.sort();
numbers.clear();
🗺️ Map Management
Reactive Maps
// Create the map observable globally or in a controller
final userProfile = MapObservable<String, String>({
'name': 'John Doe',
'email': 'john@example.com',
'role': 'Developer',
});
class UserProfileManager extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ContextObserver((context) {
final profile = userProfile.watch();
return Column(
children: [
Text('Profile has ${userProfile.length} fields'),
...profile.entries.map((entry) => ListTile(
title: Text(entry.key),
subtitle: Text(entry.value),
trailing: IconButton(
icon: Icon(Icons.edit),
onPressed: () => _editField(entry.key, entry.value),
),
)),
ElevatedButton(
onPressed: () => _addField(),
child: Text('Add Field'),
),
],
);
});
}
void _editField(String key, String currentValue) {
// Show dialog to edit field
userProfile.put(key, '$currentValue (edited)');
}
void _addField() {
userProfile.put('newField', 'New Value');
}
}
🔄 Async Operations
Handling Async State
// Create the async observable globally or in a controller
final dataLoader = AsyncObservable<List<String>>();
class DataLoader extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ContextObserver((context) {
final state = dataLoader.watchState();
final data = dataLoader.watchData();
final error = dataLoader.watchError();
return Column(
children: [
// Load data button
ElevatedButton(
onPressed: () => _loadData(),
child: Text('Load Data'),
),
SizedBox(height: 20),
// State-based UI
_buildContent(state, data, error),
],
);
});
}
Widget _buildContent(AsyncState state, List<String>? data, String? error) {
switch (state) {
case AsyncState.loading:
return Center(child: CircularProgressIndicator());
case AsyncState.error:
return Column(
children: [
Text('Error: $error'),
ElevatedButton(
onPressed: () => _loadData(),
child: Text('Retry'),
),
],
);
case AsyncState.success:
return Expanded(
child: ListView.builder(
itemCount: data?.length ?? 0,
itemBuilder: (context, index) {
return ListTile(title: Text(data![index]));
},
),
);
default:
return Text('Press "Load Data" to start');
}
}
void _loadData() {
dataLoader.execute(() async {
// Simulate API call
await Future.delayed(Duration(seconds: 2));
// Simulate random failure
if (DateTime.now().millisecondsSinceEpoch % 3 == 0) {
throw Exception('Network error');
}
return ['Item 1', 'Item 2', 'Item 3'];
});
}
}
🌊 Stream Integration
Working with Streams
// Create the stream observable globally or in a controller
final messageStream = StreamObservable<String>();
class ChatMessages extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ContextObserver((context) {
final state = messageStream.watchState();
final message = messageStream.watchData();
final hasData = messageStream.watchHasData();
return Column(
children: [
Text('Stream State: ${state.toString()}'),
if (hasData)
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(message ?? 'No message'),
),
),
Row(
children: [
ElevatedButton(
onPressed: () => _startListening(),
child: Text('Start'),
),
ElevatedButton(
onPressed: messageStream.isListening ? messageStream.pause : null,
child: Text('Pause'),
),
ElevatedButton(
onPressed: messageStream.isPaused ? messageStream.resume : null,
child: Text('Resume'),
),
],
),
],
);
});
}
void _startListening() {
// Create a stream (e.g., from WebSocket or Firebase)
final stream = Stream.periodic(
Duration(seconds: 2),
(count) => 'Message ${count + 1}',
);
messageStream.listen(stream);
}
}
💉 Dependency Injection
Service Registration and Usage
// Define a service
class ApiService extends Injectable {
@override
void onInit() {
print('ApiService initialized');
}
Future<List<String>> fetchData() async {
await Future.delayed(Duration(seconds: 1));
return ['Data 1', 'Data 2', 'Data 3'];
}
@override
void onDispose() {
print('ApiService disposed');
}
}
// Register services
void main() {
// Register singleton
Injector.put(ApiService());
// Register lazy singleton
Injector.lazyPut(() => ApiService());
// Register with scope
Injector.putScoped(ApiService(), 'user_session');
// Register with tag
Injector.putTagged(ApiService(), 'primary_api');
runApp(MyApp());
}
// Use in widgets
class DataWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ContextObserver((context) {
// Get service instance
final apiService = Injector.find<ApiService>();
return FutureBuilder<List<String>>(
future: apiService.fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data!.map((item) => ListTile(
title: Text(item),
)).toList(),
);
}
return CircularProgressIndicator();
},
);
});
}
}
// Clean up when needed
void cleanupUserSession() {
Injector.disposeScope('user_session');
}
🎯 Computed Values
Derived State
// Create observables globally or in a controller
final items = ListObservable<CartItem>();
final taxRate = Observable(0.08); // 8% tax
// Computed values automatically update when dependencies change
final subtotal = ComputedObservable(
() => items.value.fold(0.0, (sum, item) => sum + (item.price * item.quantity)),
[items],
);
final tax = ComputedObservable(
() => subtotal.value * taxRate.value,
[subtotal, taxRate],
);
final total = ComputedObservable(
() => subtotal.value + tax.value,
[subtotal, tax],
);
class ShoppingCart extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ContextObserver((context) {
return Column(
children: [
Text('Items: ${items.watchLength()}'),
Text('Subtotal: \${subtotal.watch().toStringAsFixed(2)}'),
Text('Tax: \${tax.watch().toStringAsFixed(2)}'),
Text('Total: \${total.watch().toStringAsFixed(2)}',
style: TextStyle(fontWeight: FontWeight.bold)),
// Items list
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
title: Text(item?.name ?? ''),
subtitle: Text('\${item?.price.toStringAsFixed(2)} x ${item?.quantity}'),
trailing: IconButton(
icon: Icon(Icons.remove_circle),
onPressed: () => items.removeAt(index),
),
);
},
),
),
ElevatedButton(
onPressed: () => _addItem(),
child: Text('Add Item'),
),
],
);
});
}
void _addItem() {
items.add(CartItem('Item ${items.length + 1}', 10.0, 1));
}
}
class CartItem {
final String name;
final double price;
final int quantity;
CartItem(this.name, this.price, this.quantity);
}
🧪 Testing Support
Mocking Observables
import 'package:flutter_test/flutter_test.dart';
import 'package:minix/minix.dart';
void main() {
group('Counter Tests', () {
late Observable<int> counter;
setUp(() {
counter = Observable(0);
});
tearDown(() {
counter.dispose();
});
test('should increment counter', () {
expect(counter.value, 0);
counter.value = 1;
expect(counter.value, 1);
});
test('should notify listeners on change', () {
var notified = false;
counter.addListener(() => notified = true);
counter.value = 5;
expect(notified, true);
});
});
}
🛠️ Advanced Configuration
Custom Equality and Error Handling
// Custom equality function
final userObservable = Observable(
User('John', 25),
equals: (a, b) => a.name == b.name && a.age == b.age,
);
// Global error handling
void main() {
// Set global error handler for actions
ActionController.setDefaultErrorHandler((error, stackTrace) {
print('Global error: $error');
// Log to crash reporting service
});
// Enable injection logging
Injector.enableLogs = true;
runApp(MyApp());
}
Performance Optimization
class OptimizedWidget extends StatelessWidget {
final Observable<int> counter = Observable(0);
@override
Widget build(BuildContext context) {
return Observer(
() => Text('Count: ${counter.watch()}'),
// Only rebuild when certain conditions are met
shouldRebuild: () => counter.value % 2 == 0,
);
}
}
// For better performance with StatelessWidget, you can also use controllers
class CounterController {
final counter = Observable(0);
void increment() => counter.value++;
void decrement() => counter.value--;
void dispose() => counter.dispose();
}
// Register controller globally or with dependency injection
final counterController = CounterController();
class CounterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Observer(() {
return Column(
children: [
Text('Count: ${counterController.counter.watch()}'),
Row(
children: [
ElevatedButton(
onPressed: counterController.increment,
child: Text('+'),
),
ElevatedButton(
onPressed: counterController.decrement,
child: Text('-'),
),
],
),
],
);
});
}
}
📚 API Reference
Core Classes
Observable<T>: Basic reactive value containerObserver: Widget that rebuilds when observables changeContextObserver: Observer with BuildContext accessListObservable<T>: Reactive list implementationMapObservable<K,V>: Reactive map implementationFormObservable: Form management with validationAsyncObservable<T>: Async operation state managementStreamObservable<T>: Stream integration with reactive stateComputedObservable<T>: Derived values that auto-updateInjector: Dependency injection containerAutoDispose: Automatic resource cleanup mixin
Helper Classes
ActionController: Batched state updatesSelector<T,R>: Derived observable valuesInjectable: Base class for injectable services
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🔗 Links
⭐ Show your support
Give a ⭐️ if this project helped you!
Made with ❤️ by the Jai Prakash Thawait