zeytin_state 0.0.1
zeytin_state: ^0.0.1 copied to clipboard
A lightning-fast, reactive state management solution built strictly for ZeytinX.
Zeytin State ๐ซโก #
Developed with love by JeaFriday!๐๐ซ
A lightning-fast, reactive state management solution built strictly for ZeytinX.
Say goodbye to manual setState, Riverpod, or GetX if your app heavily relies on local storage. Zeytin State bridges the gap between your ZeytinX database and your Flutter UI.
When your database changes, your UI changes. Automatically. Instantly.
๐ Why Zeytin State? #
- Zero Manual Updates: You never have to manually update your UI state. Just write to your database (
coreDB.update()), andZeytinSyncwill automatically catch the background stream and rebuild the exact widget listening to that specific tag. - Smart Rebuilds: It compares the old and new data. If the data hasn't actually changed, it aborts the rebuild, saving you precious FPS.
- Auto Garbage Collection: Built-in safeguards against memory leaks. If a widget dies, Zeytin State safely ignores it.
- Custom Model Mappers: Directly map raw database JSON (
Map<String, dynamic>) to your complex Dart models effortlessly.
๐ฆ Installation #
Add it to your pubspec.yaml:
dependencies:
zeytinx: ^??? # You already know the latest version!!
zeytin_state: ^1.0.0
๐ Quick Start #
Here is a complete example of how to make a truly reactive UI.
1. Initialize the Engine #
Put this in your main.dart or your dependency injection setup (like GetIt).
import 'package:zeytinx/zeytinx.dart';
import 'package:zeytin_state/zeytin_state.dart';
late final ZeytinX coreDB;
late final ZeytinSync zeytinSync;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Boot up the DB
coreDB = ZeytinX("my_app_namespace", "main_truck");
await coreDB.initialize("./my_local_db");
// 2. Start the State Manager and let it listen to DB changes
zeytinSync = ZeytinSync(coreDB);
zeytinSync.init();
runApp(const MyApp());
}
2. Bind and Listen in Your UI #
Use ZM<T> to declare your reactive variables, bind them in initState, and wrap your UI with ZMListener.
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
// Define your reactive Zeytin Manager (ZM) variables
late ZM<ZeytinXUserModel> myUser;
late ZM<int> userScore;
final String _userTag = "user_jea_123";
@override
void initState() {
super.initState();
// Bind a complex model using a mapper
myUser = zeytinSync.bind<ZeytinXUserModel>(
"users", // The Box
_userTag, // The Tag
ZeytinXUserModel.empty(), // Default loading value
mapper: (dynamic data) {
if (data == null) return ZeytinXUserModel.empty();
return ZeytinXUserModel.fromJson(Map<String, dynamic>.from(data));
},
);
// Bind a primitive type
userScore = zeytinSync.bind<int>(
"scores",
_userTag,
0,
mapper: (dynamic data) => data?['score'] ?? 0,
);
}
@override
void dispose() {
// CRITICAL: Always unbind and dispose to prevent memory leaks!
zeytinSync.unbind("users", _userTag, myUser);
zeytinSync.unbind("scores", _userTag, userScore);
myUser.dispose();
userScore.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Reactive Profile")),
body: Center(
// Wrap the part of your UI that needs to react
child: ZMListener(
listenables: [myUser, userScore],
childBuilder: () {
final user = myUser.value;
final score = userScore.value;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Username: ${user.username}"),
Text("Score: $score"),
],
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
// WE ONLY UPDATE THE DATABASE!
// No setState(), no manual ZM updates.
// ZeytinSync will instantly catch this and rebuild the ZMListener.
await coreDB.update(
box: "scores",
tag: _userTag,
value: (current) async {
current["score"] = (current["score"] ?? 0) + 10;
return current;
},
);
},
child: const Icon(Icons.add),
),
);
}
}
๐ง Core Concepts #
ZeytinSync #
The brain of the operation. It attaches itself to ZeytinX's observer stream. Whenever a PUT, UPDATE, BATCH, or DELETE operation happens, it looks up its registry and triggers the matching UI components.
ZM<T> (Zeytin Manager) #
This is your reactive container. It holds the current value of type T. It natively extends Flutter's ChangeNotifier.
bind<T>(box, tag, defaultValue, {mapper}) #
Tells the sync engine: "Hey, keep an eye on this Box and Tag. If it changes in the DB, fetch the new data, run it through my mapper, and update my ZM variable." It also performs an initial fetch so your UI populates immediately if the data already exists.
ZMListener #
A smart widget builder. Pass it a list of ZM variables, and it will only rebuild its childBuilder when those specific variables receive a change notification.
๐งน Memory Management #
Reactivity is powerful but can lead to memory leaks if neglected. Always unbind your ZM variables in your widget's dispose method.
Note: Even if you forget, Zeytin State has an internal garbage collector that sweeps disposed ZMs, but explicitly unbinding is the best practice for performance.
Developed with love by JeaFriday. Support Zeytin! ๐