minix 1.0.0
minix: ^1.0.0 copied to clipboard
A powerful, lightweight state management and dependency injection solution for Flutter applications with reactive programming capabilities.
example/main.dart
import 'package:flutter/material.dart';
import 'package:minix/minix.dart';
void main() {
// Enable DI logging for development
Injector.enableLogs = true;
// Register dependencies
_setupDependencies();
runApp(const MyApp());
}
void _setupDependencies() {
// Register services
Injector.put<CounterService>(CounterService());
Injector.lazyPut<ApiService>(() => ApiService());
// Register ViewModels
Injector.lazyPut<CounterViewModel>(() => CounterViewModel());
Injector.lazyPut<UserViewModel>(() => UserViewModel());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Minix Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Minix Examples')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildExampleCard(
'Basic Counter',
'Simple reactive counter with Observer',
() => Navigator.push(context,
MaterialPageRoute(builder: (_) => CounterScreen())),
),
_buildExampleCard(
'Async Operations',
'Loading states with AsyncObservable',
() => Navigator.push(context,
MaterialPageRoute(builder: (_) => AsyncScreen())),
),
_buildExampleCard(
'Stream Example',
'Real-time data with StreamObservable',
() => Navigator.push(context,
MaterialPageRoute(builder: (_) => StreamScreen())),
),
_buildExampleCard(
'MVVM Pattern',
'Architecture example with DI',
() => Navigator.push(context,
MaterialPageRoute(builder: (_) => const MVVMScreen())),
),
],
),
);
}
Widget _buildExampleCard(String title, String subtitle, VoidCallback onTap) {
return Card(
margin: const EdgeInsets.only(bottom: 16),
child: ListTile(
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(subtitle),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: onTap,
),
);
}
}
// =============================================================================
// BASIC COUNTER EXAMPLE
// =============================================================================
class CounterScreen extends StatelessWidget {
CounterScreen({super.key});
final counter = Observable(0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Basic Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Counter Value:'),
const SizedBox(height: 16),
// Observer automatically rebuilds when counter.value changes
Observer(() {
return Text(
'${counter.value}',
);
}),
const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => counter.value--,
child: const Text('-'),
),
ElevatedButton(
onPressed: () => counter.value = 0,
child: const Text('Reset'),
),
ElevatedButton(
onPressed: () => counter.value++,
child: const Text('+'),
),
],
),
],
),
),
);
}
}
// =============================================================================
// ASYNC OPERATIONS EXAMPLE
// =============================================================================
class AsyncScreen extends StatelessWidget {
AsyncScreen({super.key});
final userAsync = AsyncObservable<User>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Async Operations')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ElevatedButton(
onPressed: () => _loadUser(),
child: const Text('Load User Data'),
),
const SizedBox(height: 20),
Expanded(
child: Observer(() {
// Reactive UI based on async state
if (userAsync.isLoading) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Loading user data...'),
],
),
);
}
if (userAsync.hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red, size: 64),
const SizedBox(height: 16),
Text(
'Error: ${userAsync.error}',
style: const TextStyle(color: Colors.red),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => _loadUser(),
child: const Text('Retry'),
),
],
),
);
}
if (userAsync.hasData) {
final user = userAsync.data!;
return Center(
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 50,
child: Text(user.name[0]),
),
const SizedBox(height: 16),
Text(
user.name,
),
Text(user.email),
const SizedBox(height: 16),
Text(
'ID: ${user.id}',
style: const TextStyle(color: Colors.grey),
),
],
),
),
),
);
}
return const Center(
child: Text('No data loaded yet'),
);
}),
),
],
),
),
);
}
Future<void> _loadUser() async {
await userAsync.execute(() async {
// Simulate API call
await Future.delayed(const Duration(seconds: 2));
// Simulate random success/error
if (DateTime.now().millisecond % 3 == 0) {
throw Exception('Network error occurred');
}
return User(
id: '${DateTime.now().millisecond}',
name: 'John Doe',
email: 'john.doe@example.com',
);
});
}
}
// =============================================================================
// STREAM EXAMPLE
// =============================================================================
class StreamScreen extends StatelessWidget {
StreamScreen({super.key});
final messageStream = StreamObservable<String>();
final messages = Observable<List<String>>([]);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Stream Example')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => _startStream(),
child: const Text('Start Stream'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: () => _stopStream(),
child: const Text('Stop Stream'),
),
),
],
),
),
Expanded(
child: Observer(() {
return ListView.builder(
itemCount: messages.value.length,
itemBuilder: (context, index) {
final message = messages.value[index];
return ListTile(
leading: const Icon(Icons.message),
title: Text(message),
subtitle: Text('Received at ${DateTime.now().toString().substring(11, 19)}'),
);
},
);
}),
),
Observer(() {
return Container(
padding: const EdgeInsets.all(16),
color: messageStream.isListening ? Colors.green[100] : Colors.grey[100],
child: Row(
children: [
Icon(
messageStream.isListening ? Icons.radio_button_checked : Icons.radio_button_off,
color: messageStream.isListening ? Colors.green : Colors.grey,
),
const SizedBox(width: 8),
Text(
messageStream.isListening ? 'Stream Active' : 'Stream Inactive',
style: TextStyle(
color: messageStream.isListening ? Colors.green[800] : Colors.grey[800],
fontWeight: FontWeight.bold,
),
),
],
),
);
}),
],
),
);
}
void _startStream() {
// Create a sample stream
final stream = Stream.periodic(
const Duration(seconds: 1),
(index) => 'Message ${index + 1}: ${DateTime.now().toString().substring(11, 19)}',
);
messageStream.listen(
stream,
onData: (message) {
// Add to messages list
final currentMessages = List<String>.from(messages.value);
currentMessages.insert(0, message);
// Keep only last 10 messages
if (currentMessages.length > 10) {
currentMessages.removeLast();
}
messages.value = currentMessages;
},
);
}
Future<void> _stopStream() async {
await messageStream.cancel();
}
}
// =============================================================================
// MVVM PATTERN EXAMPLE
// =============================================================================
class MVVMScreen extends StatelessWidget {
const MVVMScreen({super.key});
@override
Widget build(BuildContext context) {
final viewModel = Injector.find<CounterViewModel>();
return Scaffold(
appBar: AppBar(title: const Text('MVVM Pattern')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Observer(() {
return Text(
'Counter: ${viewModel.counter}',
);
}),
const SizedBox(height: 16),
Observer(() {
return Text(
'Status: ${viewModel.status}',
style: TextStyle(
color: viewModel.isEven ? Colors.green : Colors.orange,
fontWeight: FontWeight.bold,
),
);
}),
const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: viewModel.decrement,
child: const Text('- Decrement'),
),
ElevatedButton(
onPressed: viewModel.reset,
child: const Text('Reset'),
),
ElevatedButton(
onPressed: viewModel.increment,
child: const Text('+ Increment'),
),
],
),
],
),
),
);
}
}
// =============================================================================
// MODELS & SERVICES
// =============================================================================
class User {
User({required this.id, required this.name, required this.email});
final String id;
final String name;
final String email;
}
class CounterService {
final _counter = Observable(0);
int get counter => _counter.value;
Observable<int> get counterObservable => _counter;
void increment() => _counter.value++;
void decrement() => _counter.value--;
void reset() => _counter.value = 0;
}
class ApiService {
Future<User> fetchUser(String id) async {
await Future.delayed(const Duration(seconds: 1));
return User(id: id, name: 'API User', email: 'api@example.com');
}
}
class CounterViewModel {
final CounterService _service = Injector.find<CounterService>();
int get counter => _service.counter;
String get status => isEven ? 'Even Number' : 'Odd Number';
bool get isEven => _service.counter % 2 == 0;
void increment() => _service.increment();
void decrement() => _service.decrement();
void reset() => _service.reset();
}
class UserViewModel {
final _user = AsyncObservable<User>();
final ApiService _api = Injector.find<ApiService>();
AsyncObservable<User> get user => _user;
Future<void> loadUser(String id) async {
await _user.execute(() => _api.fetchUser(id));
}
}