loupe_flutter 0.0.1
loupe_flutter: ^0.0.1 copied to clipboard
`Loupe` Flutter compatible widget for integration with the non invasive `loupe` reactive library
Loupe Flutter #
A Flutter widget integration for the loupe reactive state management library.
Loupe Flutter provides a Loupe widget that automatically rebuilds when watched objects change, making it easy to integrate loupe's reactive system with Flutter's declarative UI.
Features #
- Automatic rebuilds - Widget rebuilds when any watched object changes
- Simple API - Just provide a build function and a list of objects to watch
- Memory safe - Automatically unsubscribes when widget is disposed
- Lightweight - Minimal overhead, built on top of the zero-dependency loupe package
Installation #
Add to your pubspec.yaml:
dependencies:
loupe_flutter:
git:
url: https://github.com/your-repo/loupe_flutter.git
ref: main
loupe:
git:
url: https://github.com/your-repo/loupe.git
ref: main
Or if published to pub.dev:
dependencies:
loupe_flutter: ^1.0.0
loupe: ^1.0.0
Usage #
Basic Usage #
import 'package:flutter/material.dart';
import 'package:loupe/loupe.dart';
import 'package:loupe_flutter/loupe_flutter.dart';
class Counter {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyChanges(this);
}
}
class MyApp extends StatelessWidget {
final counter = Counter();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Loupe(
watchList: [counter],
build: (context) => Text('Count: ${counter.count}'),
),
),
floatingActionButton: FloatingActionButton(
onPressed: counter.increment,
child: const Icon(Icons.add),
),
),
);
}
}
Watching Multiple Objects #
class UserProfile {
String name;
int age;
UserProfile(this.name, this.age);
void updateName(String newName) {
name = newName;
notifyChanges(this);
}
void updateAge(int newAge) {
age = newAge;
notifyChanges(this);
}
}
class Settings {
String theme = 'light';
void toggleTheme() {
theme = theme == 'light' ? 'dark' : 'light';
notifyChanges(this);
}
}
Loupe(
watchList: [userProfile, settings],
build: (context) => Column(
children: [
Text('Name: ${userProfile.name}'),
Text('Age: ${userProfile.age}'),
Text('Theme: ${settings.theme}'),
],
),
)
Nested Loupe Widgets #
Loupe(
watchList: [user],
build: (context) => Column(
children: [
Text('User: ${user.name}'),
Loupe(
watchList: [user.settings],
build: (context) => Text('Settings: ${user.settings.theme}'),
),
],
),
)
API Reference #
Loupe Widget #
A StatefulWidget that rebuilds when watched objects change.
Constructor:
Loupe({
Key? key,
required Widget Function(BuildContext) build,
required List<Object> watchList,
})
Parameters:
key- Optional widget keybuild- A function that builds the widget tree. Called on every rebuild.watchList- A list of objects to watch for changes. WhennotifyChanges()is called on any of these objects, the widget rebuilds.
Important Notes #
- Object Identity: Like the underlying loupe package, this widget uses object identity to track changes. Primitive types (int, String, bool, etc.) will not work correctly. Always use reference types (class instances).
- Manual Notification: You must call
notifyChanges(obj)explicitly when an object changes. The widget does not automatically detect changes. - Automatic Cleanup: The widget automatically unsubscribes from all watched objects when it is disposed, preventing memory leaks.
- BuildContext: The
buildcallback receives the currentBuildContext, just like any other Flutter widget builder.
Comparison with Other State Management Solutions #
| Feature | Loupe Flutter | Provider | Riverpod | Bloc |
|---|---|---|---|---|
| Dependencies | Minimal | Flutter | Many | Many |
| Learning Curve | Low | Low | Medium | High |
| Boilerplate | Minimal | Minimal | Minimal | High |
| Testability | Easy | Easy | Easy | Medium |
| Reactivity | Manual | Automatic | Automatic | Manual |
Loupe Flutter is ideal when you want:
- A simple, explicit reactive system
- Minimal dependencies
- Full control over when notifications happen
- Easy integration with existing code
Example: Complete Counter App #
import 'package:flutter/material.dart';
import 'package:loupe/loupe.dart';
import 'package:loupe_flutter/loupe_flutter.dart';
class Counter {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyChanges(this);
}
void decrement() {
_count--;
notifyChanges(this);
}
void reset() {
_count = 0;
notifyChanges(this);
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Loupe Counter',
theme: ThemeData(primarySwatch: Colors.blue),
home: const CounterPage(),
);
}
}
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
final counter = Counter();
return Scaffold(
appBar: AppBar(title: const Text('Loupe Counter')),
body: Center(
child: Loupe(
watchList: [counter],
build: (context) => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'${counter.count}',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
onPressed: counter.decrement,
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),
const SizedBox(width: 20),
FloatingActionButton(
onPressed: counter.reset,
tooltip: 'Reset',
child: const Icon(Icons.exposure_zero),
),
const SizedBox(width: 20),
FloatingActionButton(
onPressed: counter.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
],
),
],
),
),
),
);
}
}
License #
MIT License.