zeytin_state 0.0.1
zeytin_state: ^0.0.1 copied to clipboard
A lightning-fast, reactive state management solution built strictly for ZeytinX.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:zeytinx/zeytinx.dart';
import 'package:zeytin_state/zeytin_state.dart';
// 1. Initialize ZeytinX and ZeytinSync globally or via a service locator (e.g. get_it)
late final ZeytinX coreDB;
late final ZeytinSync zeytinSync;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 2. Setup Database
coreDB = ZeytinX("my_app_namespace", "main_truck");
await coreDB.initialize("./my_local_db"); // Adjust path for your platform
// 3. Setup ZeytinSync State Manager
zeytinSync = ZeytinSync(coreDB);
zeytinSync.init();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Zeytin State Example',
home: ProfileScreen(),
);
}
}
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
// 4. Define your reactive states using ZM<T>
late ZM<ZeytinXUserModel> myUser;
late ZM<int> userScore;
final String _userTag = "user_jea_123";
@override
void initState() {
super.initState();
// 5. Bind the state to a specific Box and Tag in the database.
// Use the `mapper` to convert raw Map<String, dynamic> data to your custom Models.
myUser = zeytinSync.bind<ZeytinXUserModel>(
"users", // Database Box
_userTag, // Document Tag
ZeytinXUserModel.empty(), // Default value before fetch
mapper: (dynamic data) {
if (data == null) return ZeytinXUserModel.empty();
return ZeytinXUserModel.fromJson(Map<String, dynamic>.from(data));
},
);
// Binding a simple primitive type (No mapper needed)
userScore = zeytinSync.bind<int>(
"scores",
_userTag,
0, // Default value
mapper: (dynamic data) => data?['score'] ?? 0,
);
_createInitialData();
}
Future<void> _createInitialData() async {
// Create dummy data if it doesn't exist to show how UI reacts.
bool exists = await coreDB.exists(box: "users", tag: _userTag);
if (!exists) {
var dummyUser = ZeytinXUserModel.empty().copyWith(
uid: _userTag,
username: "JeaFriday",
biography: "Zeytin State is awesome!",
);
await coreDB.add(box: "users", tag: _userTag, value: dummyUser.toJson());
await coreDB.add(box: "scores", tag: _userTag, value: {"score": 100});
}
}
@override
void dispose() {
// 6. VERY IMPORTANT: Prevent memory leaks!
// Unbind and dispose your ZMs when the widget is destroyed.
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("Zeytin State Example"),
centerTitle: true,
backgroundColor: Colors.green.shade700,
),
body: Center(
// 7. Use ZMListener to listen to one or multiple ZMs.
// The UI will ONLY rebuild when the database data for these specific tags changes.
child: ZMListener(
listenables: [myUser, userScore],
childBuilder: () {
final user = myUser.value;
final score = userScore.value;
if (user.username.isEmpty) {
return const CircularProgressIndicator();
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
radius: 60,
backgroundColor: Colors.green.shade200,
child: Text(
user.username[0].toUpperCase(),
style: const TextStyle(fontSize: 40, color: Colors.white),
),
),
const SizedBox(height: 20),
Text(
user.username,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text(
user.biography,
style: const TextStyle(fontSize: 18, color: Colors.grey),
),
const SizedBox(height: 30),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(20),
),
child: Text(
"Score: $score 🏆",
style: TextStyle(
fontSize: 20,
color: Colors.green.shade800,
),
),
),
],
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton.extended(
heroTag: "btn1",
onPressed: () async {
// 8. UPDATE THE DATABASE DIRECTLY!
// Do NOT call setState() or myUser.set() manually.
// ZeytinSync will detect the database write operation and update the UI automatically!
await coreDB.update(
box: "users",
tag: _userTag,
value: (currentValue) async {
currentValue["username"] = "Jea_${DateTime.now().second}";
currentValue["biography"] =
"Updated at: ${DateTime.now().toLocal().toString().split('.')[0]}";
return currentValue;
},
);
},
label: const Text("Change Profile"),
icon: const Icon(Icons.person),
backgroundColor: Colors.green.shade700,
),
const SizedBox(height: 10),
FloatingActionButton.extended(
heroTag: "btn2",
onPressed: () async {
// Same here, just update the database.
await coreDB.update(
box: "scores",
tag: _userTag,
value: (current) async {
current["score"] = (current["score"] ?? 0) + 10;
return current;
},
);
},
label: const Text("Add +10 Score"),
icon: const Icon(Icons.add),
backgroundColor: Colors.orange.shade700,
),
],
),
);
}
}