From 248a5ce854c4fc3a62b4c5eadfffcd9893f6ddf0 Mon Sep 17 00:00:00 2001 From: Jeremias Nunez Date: Sat, 25 Jan 2025 23:36:38 -0400 Subject: [PATCH 1/5] feat: version 0.1.12 ready --- example/lib/main.dart | 5 +- example/lib/screens/add_category_screen.dart | 5 +- example/lib/screens/add_task_screen.dart | 5 +- .../lib/screens/consumer_example_screen.dart | 61 +++++++ example/lib/screens/home_screen.dart | 8 +- example/lib/screens/main_screen.dart | 151 +++++++++++------- .../screens/multi_store_provider_screen.dart | 82 ++++++++++ example/lib/screens/search_screen.dart | 5 +- example/lib/store/sample_store.dart | 26 +++ example/lib/store/todo_store.dart | 99 ++++-------- example/lib/todo_provider.dart | 20 --- 11 files changed, 310 insertions(+), 157 deletions(-) create mode 100644 example/lib/screens/consumer_example_screen.dart create mode 100644 example/lib/screens/multi_store_provider_screen.dart create mode 100644 example/lib/store/sample_store.dart delete mode 100644 example/lib/todo_provider.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 56e8a21..40aa1a7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_stores_example/screens/main_screen.dart'; import 'package:flutter_stores_example/store/todo_store.dart'; -import 'package:flutter_stores_example/todo_provider.dart'; import 'package:upper_flutter_stores/upper_flutter_stores.dart'; void main() { @@ -15,8 +14,8 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { final todoStore = TodoStore(); - return TodoProvider( - todoStore: todoStore, + return StoreProvider( + store: todoStore, child: StoreLifecycleManager( child: MaterialApp( title: 'Flutter Stores TODO Example', diff --git a/example/lib/screens/add_category_screen.dart b/example/lib/screens/add_category_screen.dart index e2425b9..e501c8b 100644 --- a/example/lib/screens/add_category_screen.dart +++ b/example/lib/screens/add_category_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_stores_example/todo_provider.dart'; +import 'package:flutter_stores_example/store/todo_store.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; class AddCategoryScreen extends StatefulWidget { const AddCategoryScreen({Key? key}) : super(key: key); @@ -19,7 +20,7 @@ class _AddCategoryScreenState extends State { @override Widget build(BuildContext context) { - final store = TodoProvider.of(context).todoStore; + final store = StoreProvider.of(context); return Scaffold( appBar: AppBar( diff --git a/example/lib/screens/add_task_screen.dart b/example/lib/screens/add_task_screen.dart index 02fe1a4..3495c06 100644 --- a/example/lib/screens/add_task_screen.dart +++ b/example/lib/screens/add_task_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_stores_example/todo_provider.dart'; +import 'package:flutter_stores_example/store/todo_store.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; class AddTaskScreen extends StatefulWidget { const AddTaskScreen({Key? key}) : super(key: key); @@ -15,7 +16,7 @@ class _AddTaskScreenState extends State { @override Widget build(BuildContext context) { - final store = TodoProvider.of(context).todoStore; + final store = StoreProvider.of(context); return Scaffold( appBar: AppBar( diff --git a/example/lib/screens/consumer_example_screen.dart b/example/lib/screens/consumer_example_screen.dart new file mode 100644 index 0000000..866526a --- /dev/null +++ b/example/lib/screens/consumer_example_screen.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_stores_example/store/todo_store.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class ConsumerExampleScreen extends StatelessWidget { + const ConsumerExampleScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('StoreConsumer Example'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: StoreConsumer( + builder: (context, store) { + final tasks = store.tasks; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Tasks:', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + if (tasks.isEmpty) + const Text('No tasks available.') + else + Expanded( + child: ListView.builder( + itemCount: tasks.length, + itemBuilder: (context, index) { + final task = tasks[index]; + return ListTile( + title: Text(task['title'] ?? ''), + subtitle: Text(task['description'] ?? ''), + trailing: IconButton( + icon: Icon( + task['done'] == true + ? Icons.check_circle + : Icons.radio_button_unchecked, + color: task['done'] == true + ? Colors.green + : Colors.grey, + ), + onPressed: () => store.completeTask(index), + ), + ); + }, + ), + ), + ], + ); + }, + ), + ), + ); + } +} diff --git a/example/lib/screens/home_screen.dart b/example/lib/screens/home_screen.dart index 36e865a..0e3c429 100644 --- a/example/lib/screens/home_screen.dart +++ b/example/lib/screens/home_screen.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:flutter_stores_example/todo_provider.dart'; +import 'package:flutter_stores_example/store/todo_store.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - final store = TodoProvider.of(context).todoStore; + final store = StoreProvider.of(context); return ValueListenableBuilder( - valueListenable: store.todoStore, + valueListenable: store, builder: (context, value, _) { final tasks = store.tasks; - if (!store.hasTasks) { return const Center(child: Text('No tasks available.')); } diff --git a/example/lib/screens/main_screen.dart b/example/lib/screens/main_screen.dart index 0a958be..3e6cb7f 100644 --- a/example/lib/screens/main_screen.dart +++ b/example/lib/screens/main_screen.dart @@ -1,78 +1,62 @@ import 'package:flutter/material.dart'; +import 'package:flutter_stores_example/screens/consumer_example_screen.dart'; +import 'package:flutter_stores_example/screens/multi_store_provider_screen.dart'; import 'package:flutter_stores_example/screens/search_screen.dart'; +import 'package:flutter_stores_example/store/todo_store.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; import 'home_screen.dart'; import 'add_task_screen.dart'; import 'add_category_screen.dart'; -import 'package:flutter_stores_example/todo_provider.dart'; class MainScreen extends StatelessWidget { const MainScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - final store = TodoProvider.of(context).todoStore; + final store = StoreProvider.of(context); return Scaffold( appBar: AppBar( title: const Text('TODO App'), - actions: [ - PopupMenuButton( - onSelected: (value) { - switch (value) { - case 'Undo': - store.undo(); - break; - case 'Redo': - store.redo(); - break; - case 'Take Snapshot': - store.takeSnapshot(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Snapshot taken!')), - ); - break; - } - }, - itemBuilder: (_) => const [ - PopupMenuItem(value: 'Undo', child: Text('Undo')), - PopupMenuItem(value: 'Redo', child: Text('Redo')), - PopupMenuItem( - value: 'Take Snapshot', child: Text('Take Snapshot')), - ], - ), - IconButton( - icon: const Icon(Icons.search), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const SearchScreen()), - ); - }, - ), - IconButton( - icon: const Icon(Icons.add_task), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const AddTaskScreen()), - ); - }, - ), - // Navigate to Add Category Screen - IconButton( - icon: const Icon(Icons.category), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const AddCategoryScreen()), - ); - }, + actions: buildActions(store, context), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Expanded(child: HomeScreen()), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const ConsumerExampleScreen()), + ); + }, + child: const Text('Go to Consumer Example Screen'), + ), + const SizedBox(height: 8), + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const MultiStoreProviderScreen()), + ); + }, + child: const Text('Go to MultiStoreProvider Example Screen'), + ), + ], + ), ), ], ), - body: const HomeScreen(), floatingActionButton: ValueListenableBuilder( - valueListenable: store.todoStore, + valueListenable: store, builder: (context, value, _) { final pendingTasks = store.tasks.where((task) => task['done'] != true).toList(); @@ -86,4 +70,59 @@ class MainScreen extends StatelessWidget { ), ); } + + List buildActions(TodoStore store, BuildContext context) { + return [ + PopupMenuButton( + onSelected: (value) { + switch (value) { + case 'Undo': + store.undo(); + break; + case 'Redo': + store.redo(); + break; + case 'Take Snapshot': + store.takeSnapshot(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Snapshot taken!')), + ); + break; + } + }, + itemBuilder: (_) => const [ + PopupMenuItem(value: 'Undo', child: Text('Undo')), + PopupMenuItem(value: 'Redo', child: Text('Redo')), + PopupMenuItem(value: 'Take Snapshot', child: Text('Take Snapshot')), + ], + ), + IconButton( + icon: const Icon(Icons.search), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const SearchScreen()), + ); + }, + ), + IconButton( + icon: const Icon(Icons.add_task), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const AddTaskScreen()), + ); + }, + ), + IconButton( + icon: const Icon(Icons.category), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const AddCategoryScreen()), + ); + }, + ), + ]; + } } diff --git a/example/lib/screens/multi_store_provider_screen.dart b/example/lib/screens/multi_store_provider_screen.dart new file mode 100644 index 0000000..8383907 --- /dev/null +++ b/example/lib/screens/multi_store_provider_screen.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_stores_example/store/sample_store.dart'; +import 'package:flutter_stores_example/store/todo_store.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class MultiStoreProviderScreen extends StatelessWidget { + const MultiStoreProviderScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MultiStoreProvider( + definitions: [ + StoreDefinition(TodoStore()), + StoreDefinition(SampleStore()), + ], + child: Scaffold( + appBar: AppBar( + title: const Text('MultiStoreProvider Example'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Todo Store Tasks:', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + Expanded( + child: StoreConsumer( + builder: (context, todoStore) { + final tasks = todoStore.tasks; + return tasks.isEmpty + ? const Text('No tasks available.') + : ListView.builder( + itemCount: tasks.length, + itemBuilder: (context, index) { + final task = tasks[index]; + return ListTile( + title: Text(task['title'] ?? ''), + subtitle: Text(task['description'] ?? ''), + trailing: IconButton( + icon: Icon( + task['done'] == true + ? Icons.check_circle + : Icons.radio_button_unchecked, + color: task['done'] == true + ? Colors.green + : Colors.grey, + ), + onPressed: () => + todoStore.completeTask(index), + ), + ); + }, + ); + }, + ), + ), + const Divider(), + const Text( + 'Another Store Data:', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + Expanded( + child: StoreConsumer( + builder: (context, sampleStore) { + final sampleData = sampleStore.sampleData; + return Text( + 'Sample Data: $sampleData', + style: const TextStyle(fontSize: 16), + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/screens/search_screen.dart b/example/lib/screens/search_screen.dart index 09517b2..e718ca3 100644 --- a/example/lib/screens/search_screen.dart +++ b/example/lib/screens/search_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_stores_example/todo_provider.dart'; +import 'package:flutter_stores_example/store/todo_store.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; class SearchScreen extends StatefulWidget { const SearchScreen({Key? key}) : super(key: key); @@ -14,7 +15,7 @@ class _SearchScreenState extends State { @override Widget build(BuildContext context) { - final store = TodoProvider.of(context).todoStore; + final store = StoreProvider.of(context); return Scaffold( appBar: AppBar( diff --git a/example/lib/store/sample_store.dart b/example/lib/store/sample_store.dart new file mode 100644 index 0000000..57725c0 --- /dev/null +++ b/example/lib/store/sample_store.dart @@ -0,0 +1,26 @@ +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class SampleStore extends StoreInterface> { + SampleStore() + : super( + {'sampleData': 'Hello multistore!'}, + enableDebugging: true, + ) { + initializePersistence(); + } + + String get sampleData { + final data = state['sampleData']; + if (enableDebugging) { + print('SampleStore: Retrieved sampleData: $data'); + } + return data ?? 'No Data'; + } + + void updateSampleData(String newData) { + if (enableDebugging) { + print('SampleStore: Updating sampleData to: $newData'); + } + set({'sampleData': newData}); + } +} diff --git a/example/lib/store/todo_store.dart b/example/lib/store/todo_store.dart index ec7212f..5214b94 100644 --- a/example/lib/store/todo_store.dart +++ b/example/lib/store/todo_store.dart @@ -1,11 +1,11 @@ import 'package:upper_flutter_stores/upper_flutter_stores.dart'; /// TodoStore for managing tasks in the TODO app -class TodoStore { - final Store> todoStore; +class TodoStore extends StoreInterface> { + bool isInitialized = false; TodoStore() - : todoStore = Store>( + : super( { 'tasks': >[], 'categories': ['General'], @@ -35,86 +35,73 @@ class TodoStore { }, toJson: (state) => state, ) { - _initializePersistence(); + initializePersistence(); } - /// Initialize persistence - Future _initializePersistence() async { - try { - await todoStore.initializePersistence(); - print('TodoStore: State restored from persistence: ${todoStore.state}'); - } catch (e) { - print('TodoStore: Failed to initialize persistence - $e'); - } - } - - /// Save the current state persistently - Future _persistState() async { - try { - await todoStore.persist(); - print('TodoStore: State persisted successfully.'); - } catch (e) { - print('TodoStore: Failed to persist state - $e'); - } - } - - /// Initialize middleware - void setupMiddleware() { - todoStore.addMiddleware((oldState, newState) { - print('Middleware: State changed from $oldState to $newState'); - }); + @override + Future initializePersistence() async { + await super.initializePersistence(); + isInitialized = true; + notifyListeners(); } /// Retrieve all tasks List> get tasks { - return todoStore.state['tasks'] ?? []; + final data = List>.from(state['tasks'] ?? []); + print('Tasks retrieved: $data'); + return data; } - /// Retrieve all categories List get categories { - return List.from(todoStore.state['categories'] ?? []); + final data = List.from(state['categories'] ?? []); + print('Categories retrieved: $data'); + return data; } /// Add a task Future addTask(Map task) async { final updatedTasks = [...tasks, task]; - todoStore.set({ - ...todoStore.state, + set({ + ...state, 'tasks': updatedTasks, }); - await _persistState(); // Save persistently + await persistState(); } /// Remove a task by index Future removeTask(int index) async { + print('Removing task at index: $index'); final updatedTasks = List>.from(tasks) ..removeAt(index); - todoStore.set({ - ...todoStore.state, + print('Updated tasks: $updatedTasks'); + set({ + ...state, 'tasks': updatedTasks, }); - await _persistState(); // Save persistently + print('State after removing task: ${state['tasks']}'); + await persistState(); + print('Task removed and state persisted.'); } /// Mark a task as completed Future completeTask(int index) async { final updatedTasks = List>.from(tasks); updatedTasks[index]['done'] = !(updatedTasks[index]['done'] ?? false); - todoStore.set({ - ...todoStore.state, + set({ + ...state, 'tasks': updatedTasks, }); - await _persistState(); // Save persistently + await persistState(); } /// Add a category Future addCategory(String category) async { final updatedCategories = [...categories, category]; - todoStore.set({ - ...todoStore.state, + set({ + ...state, 'categories': updatedCategories, }); - await _persistState(); // Save persistently + await persistState(); // Save persistently } /// Check if there are any tasks @@ -135,28 +122,4 @@ class TodoStore { .contains(query.toLowerCase())) .toList(); } - - /// Undo last change - void undo() { - if (todoStore.canUndo) { - todoStore.undo(); - } - } - - /// Redo last undone change - void redo() { - if (todoStore.canRedo) { - todoStore.redo(); - } - } - - /// Take a snapshot of the current state - void takeSnapshot() { - todoStore.takeSnapshot(); - } - - /// Replay a snapshot by index - void replaySnapshot(int index) { - todoStore.replaySnapshot(index); - } } diff --git a/example/lib/todo_provider.dart b/example/lib/todo_provider.dart deleted file mode 100644 index f667009..0000000 --- a/example/lib/todo_provider.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter_stores_example/store/todo_store.dart'; - -class TodoProvider extends InheritedWidget { - final TodoStore todoStore; - - const TodoProvider({ - Key? key, - required Widget child, - required this.todoStore, - }) : super(key: key, child: child); - - static TodoProvider of(BuildContext context) { - return context.dependOnInheritedWidgetOfExactType()!; - } - - @override - bool updateShouldNotify(TodoProvider oldWidget) => - todoStore != oldWidget.todoStore; -} From 2fca1b698197625efdf0f568293bf7f7ffe6b318 Mon Sep 17 00:00:00 2001 From: Jeremias Nunez Date: Sat, 25 Jan 2025 23:36:50 -0400 Subject: [PATCH 2/5] feat: version 0.1.12 ready --- CHANGELOG.md | 8 + README.md | 2 +- docs/API_DEFINITION.md | 189 ++++++++++++++++++++++ docs/ARCHITECTURE.md | 99 ++++++++++++ docs/MULTISTORE_PROVIDER.md | 132 +++++++++++++++ docs/PROVIDER_OVERVIEW.md | 124 ++++++++++++++ docs/README.md | 98 +++++++++++ docs/SEPARATED_ASYNC.md | 105 ++++++++++++ docs/SEPARATED_BASE.md | 82 ++++++++++ docs/SEPARATED_PERSISTENT.md | 101 ++++++++++++ docs/SEPARATED_SNAPSHOTS.md | 87 ++++++++++ docs/SEPARATED_UNDOABLE.md | 144 +++++++++++++++++ docs/SEPARATED_UNDO_REDO.md | 104 ++++++++++++ docs/SEPERATED_COMPUTED.md | 63 ++++++++ docs/SEPERATED_OVERVIEW.md | 23 +++ docs/STORE_CONSUMER.md | 92 +++++++++++ docs/STORE_PROVIDER.md | 124 ++++++++++++++ docs/SUGGESTED_ARCHITECTURED.md | 187 +++++++++++++++++++++ docs/UNIFIED_MIDDLEWARE.md | 130 +++++++++++++++ docs/UNIFIED_PERSISTENT.md | 81 ++++++++++ docs/UNIFIED_SNAPSHOTS.md | 99 ++++++++++++ docs/UNIFIED_STORE.md | 71 ++++++++ docs/USE_CASES.md | 161 ++++++++++++++++++ lib/src/independent/persistent_store.dart | 10 ++ lib/src/store/multi_store_provider.dart | 26 +++ lib/src/store/store_consumer.dart | 15 ++ lib/src/store/store_definition.dart | 25 +++ lib/src/store/store_interface.dart | 129 +++++++++++++++ lib/src/store/store_provider.dart | 24 +++ lib/src/unified/store.dart | 27 +++- lib/upper_flutter_stores.dart | 7 + pubspec.yaml | 5 +- test/store/store_provider_test.dart | 95 +++++++++++ 33 files changed, 2662 insertions(+), 7 deletions(-) create mode 100644 docs/API_DEFINITION.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/MULTISTORE_PROVIDER.md create mode 100644 docs/PROVIDER_OVERVIEW.md create mode 100644 docs/README.md create mode 100644 docs/SEPARATED_ASYNC.md create mode 100644 docs/SEPARATED_BASE.md create mode 100644 docs/SEPARATED_PERSISTENT.md create mode 100644 docs/SEPARATED_SNAPSHOTS.md create mode 100644 docs/SEPARATED_UNDOABLE.md create mode 100644 docs/SEPARATED_UNDO_REDO.md create mode 100644 docs/SEPERATED_COMPUTED.md create mode 100644 docs/SEPERATED_OVERVIEW.md create mode 100644 docs/STORE_CONSUMER.md create mode 100644 docs/STORE_PROVIDER.md create mode 100644 docs/SUGGESTED_ARCHITECTURED.md create mode 100644 docs/UNIFIED_MIDDLEWARE.md create mode 100644 docs/UNIFIED_PERSISTENT.md create mode 100644 docs/UNIFIED_SNAPSHOTS.md create mode 100644 docs/UNIFIED_STORE.md create mode 100644 docs/USE_CASES.md create mode 100644 lib/src/store/multi_store_provider.dart create mode 100644 lib/src/store/store_consumer.dart create mode 100644 lib/src/store/store_definition.dart create mode 100644 lib/src/store/store_interface.dart create mode 100644 lib/src/store/store_provider.dart create mode 100644 test/store/store_provider_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 8014df9..05e27da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,14 @@ All notable changes to this project will be documented in this file. ### Added - Updated documentation github url +## [0.1.12] - 2025-01-25 +### Added +- Added StoreProvided +- Added ConsumerProvider +- Added MultiStoreProvided +- Added StoreDefinition +- Added Documentation + ### Improved - Optimized `_notifyListeners` in `BaseStore` for better performance with large subscriber counts. - Refactored individual stores to support lifecycle management and debugging seamlessly. diff --git a/README.md b/README.md index ee0ec9b..eda3ed1 100644 --- a/README.md +++ b/README.md @@ -206,4 +206,4 @@ flutter test ## πŸ“š Documentation -Check the [full documentation](https://github.com/upperdo/upper_flutter_stores) for more details and advanced examples. +Check the [full documentation](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/README.md) for more details and advanced examples. diff --git a/docs/API_DEFINITION.md b/docs/API_DEFINITION.md new file mode 100644 index 0000000..6aa89f8 --- /dev/null +++ b/docs/API_DEFINITION.md @@ -0,0 +1,189 @@ +# API Reference: **upper_flutter_stores** + +This document provides a comprehensive overview of the API definitions, interfaces, and core components available in the **upper_flutter_stores** package. + +--- + +## Overview +The **upper_flutter_stores** package provides a structured and flexible API to manage application state efficiently. The package includes key classes, interfaces, and utilities for state management, middleware, persistence, and more. + +--- + +## Core Interfaces and Classes + +### 1. `BaseStore` +#### Description: +The foundational class for state management, offering basic functionality for handling state and notifying listeners. + +#### Key Methods: +- **`T get state`**: Retrieves the current state. +- **`void set(T newState)`**: Updates the state and notifies listeners. +- **`void addListener(void Function() listener)`**: Registers a listener for state changes. +- **`void removeListener(void Function() listener)`**: Unregisters a listener. + +--- + +### 2. `Store` +#### Description: +A unified store that integrates advanced features like undo/redo, persistence, snapshots, and computed values. + +#### Key Features: +- Undo/Redo functionality. +- Persistence support. +- Middleware integration. +- Snapshot and replay support. + +#### Key Methods: +- **`void undo()`**: Reverts to the previous state. +- **`void redo()`**: Restores the last undone state. +- **`Future persist()`**: Saves the current state persistently. +- **`void takeSnapshot()`**: Captures the current state as a snapshot. +- **`void replaySnapshot(int index)`**: Reverts to a specific snapshot. + +--- + +### 3. `StoreInterface` +#### Description: +An abstract base interface for creating custom stores with specific features. + +#### Key Methods: +- **`T get state`**: Retrieves the current state. +- **`void set(T newState)`**: Updates the state. +- **`Future initializePersistence()`**: Initializes persistent storage for the state. +- **`void undo()`**, **`void redo()`**: Undo/redo functionality for state changes. +- **`void takeSnapshot()`**, **`void replaySnapshot(int index)`**: Snapshot management. + +--- + +### 4. `StoreProvider` +#### Description: +An InheritedWidget for injecting a store into the widget tree. + +#### Key Methods: +- **`static T of(BuildContext context)`**: Retrieves the store of type `T` from the widget tree. + +--- + +### 5. `StoreConsumer` +#### Description: +A widget that listens to store changes and rebuilds its child widget. + +#### Key Parameters: +- **`Widget Function(BuildContext context, T store)`**: A builder function that rebuilds on state changes. + +--- + +### 6. `MultiStoreProvider` +#### Description: +A utility for injecting multiple stores into the widget tree. + +#### Key Parameters: +- **`List definitions`**: A list of store definitions to provide. +- **`Widget child`**: The child widget to wrap with providers. + +--- + +### 7. `Middleware` +#### Description: +A function that intercepts state transitions, allowing pre-processing or validation. + +#### Function Signature: +```dart +typedef Middleware = void Function(T oldState, T newState); +``` + +--- + +## Advanced Features + +### 1. Persistence +#### Description: +Provides a mechanism to persist state using `SharedPreferences` or other storage backends. + +#### Key Classes: +- **`PersistentStore`**: A store that saves and restores state persistently. +- **`Store`**: Unified store with built-in persistence support. + +#### Key Methods: +- **`Future initialize()`**: Initializes persistent storage. +- **`Future persist()`**: Saves the current state. + +--- + +### 2. Undo/Redo +#### Description: +Tracks state history for reverting or reapplying changes. + +#### Key Classes: +- **`UndoableStore`**: Adds undo/redo capabilities to a store. +- **`Store`**: Unified store with built-in undo/redo support. + +#### Key Methods: +- **`void undo()`**: Reverts to the previous state. +- **`void redo()`**: Restores the last undone state. + +--- + +### 3. Snapshots +#### Description: +Allows capturing and replaying state snapshots for debugging or time travel. + +#### Key Classes: +- **`SnapshotStore`**: Enables snapshot management. +- **`Store`**: Unified store with snapshot support. + +#### Key Methods: +- **`void takeSnapshot()`**: Captures the current state as a snapshot. +- **`void replaySnapshot(int index)`**: Restores a specific snapshot. + +--- + +### 4. Async State Management +#### Description: +Manages asynchronous tasks and updates state upon completion. + +#### Key Classes: +- **`AsyncStore`**: Handles async operations. +- **`Store`**: Unified store with async support. + +#### Key Methods: +- **`Future runAsync(Future Function() task)`**: Runs an async task and updates the state on completion. + +--- + +### 5. Computed State +#### Description: +Automatically calculates derived state based on dependencies. + +#### Key Classes: +- **`ComputedStore`**: Computes derived state. +- **`Store`**: Unified store with computed state support. + +#### Key Parameters: +- **`T Function()`**: A function to compute the derived state. +- **`List`**: Dependencies for the computed state. + +--- + +## Store Definitions +### `BaseStoreDefinition` and `StoreDefinition` +#### Description: +Used with `MultiStoreProvider` to define stores and wrap widgets with their respective providers. + +#### Example: +```dart +final definitions = [ + StoreDefinition(TodoStore()), + StoreDefinition(SampleStore()), +]; + +MultiStoreProvider( + definitions: definitions, + child: MyApp(), +); +``` + +--- + +## Conclusion +The **upper_flutter_stores** package offers a flexible and powerful API for state management. With its modular design, advanced features, and intuitive interface, it simplifies building robust Flutter applications. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..3104982 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,99 @@ +# Architecture: **upper_flutter_stores** + +The **upper_flutter_stores** package is designed with flexibility, modularity, and extensibility in mind. It provides a robust foundation for managing application state in Flutter projects, allowing developers to handle complex scenarios while maintaining simplicity. + +## Core Principles + +1. **Separation of Concerns**: + - The architecture separates core functionalities into distinct stores and components. + - Unified and separated stores ensure modularity and focus on specific responsibilities. + +2. **Scalability**: + - The package supports both small-scale and large-scale applications by allowing developers to pick only the features they need. + +3. **Reusability**: + - Components like middleware and snapshot mechanisms are designed to be reusable across multiple stores. + +4. **Developer Experience**: + - Minimal boilerplate and intuitive APIs make it easy for developers to integrate and use the package. + +--- + +## Key Components + +### 1. Unified Store +- **Purpose**: Combines multiple features (e.g., persistence, undo/redo, async operations) into a single store for convenience. +- **Features**: + - Supports state persistence using `PersistentStore`. + - Allows undo/redo actions via `UndoableStore`. + - Handles computed states with `ComputedStore`. + - Enables asynchronous operations through `AsyncStore`. + - Provides snapshot and replay functionality with `SnapshotStore`. +- **Usage**: Ideal for most scenarios where multiple features are required in a single store. + +### 2. Separated Stores +- **Purpose**: Provide feature-specific implementations for advanced control and specialization. +- **Examples**: + - `BaseStore`: Core state management. + - `UndoableStore`: Adds undo/redo functionality. + - `PersistentStore`: Manages state persistence. + - `AsyncStore`: Handles asynchronous updates. + - `ComputedStore`: Manages derived state calculations. + - `SnapshotStore`: Enables state snapshots and replay. +- **Usage**: Suitable for use cases requiring granular control over individual features. + +### 3. Store Providers +- **`StoreProvider`**: + - A generic provider that injects a store into the widget tree. + - Allows child widgets to access the store via the context. +- **`MultiStoreProvider`**: + - Enables injecting multiple stores into the widget tree simultaneously. + - Simplifies dependency management for widgets requiring multiple stores. + +### 4. Middleware +- **Purpose**: Intercepts state transitions, enabling custom logic (e.g., logging, validation). +- **Integration**: Middleware can be added to unified stores or specific separated stores. + +--- + +## Data Flow + +The package follows a unidirectional data flow pattern: + +1. **State Update**: + - State changes are triggered via `set` or feature-specific methods (e.g., `undo`, `takeSnapshot`). + +2. **Middleware Interception**: + - Middleware intercepts state transitions, allowing additional logic to be executed. + +3. **Reactivity**: + - Changes in state automatically notify listeners, ensuring the UI reflects the updated state. + +4. **Persistence** (if enabled): + - State changes are saved to local storage, ensuring data is retained across sessions. + +--- + +## Reactivity and Notifications + +- Stores inherit from `BaseStore`, which implements `ChangeNotifier` to provide reactivity. +- Changes to state trigger notifications to listeners, ensuring widgets depending on the state rebuild as needed. + +--- + +## Design Decisions + +### Modular Design +- Stores are designed as independent modules, allowing developers to use them individually or as part of the unified store. + +### Extensibility +- Developers can extend existing stores or create custom implementations by inheriting from `BaseStore` or other specialized stores. + +### Lightweight and Efficient +- The package avoids unnecessary dependencies, focusing on performance and simplicity. + +--- + +## Conclusion + +The **upper_flutter_stores** package provides a comprehensive and flexible state management solution tailored to the needs of modern Flutter applications. Its modular architecture and focus on scalability make it a powerful tool for developers, whether building small apps or enterprise-level solutions. diff --git a/docs/MULTISTORE_PROVIDER.md b/docs/MULTISTORE_PROVIDER.md new file mode 100644 index 0000000..62046d1 --- /dev/null +++ b/docs/MULTISTORE_PROVIDER.md @@ -0,0 +1,132 @@ +# MultiStoreProvider + +The **MultiStoreProvider** widget allows developers to efficiently manage multiple state stores in a Flutter application by wrapping them in a single widget. This approach simplifies dependency injection and state management when multiple stores are required in the widget tree. + +--- + +## Key Features + +- Simplifies managing multiple stores by combining them into a single widget. +- Reduces boilerplate for injecting multiple providers. +- Compatible with the **StoreProvider** for individual store injection. +- Supports strong typing for stores, ensuring type safety and flexibility. + +--- + +## Usage + +### Import the Package + +Before using the **MultiStoreProvider**, import the package: + +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; +``` + +### Example Implementation + +Below is a comprehensive example demonstrating how to use **MultiStoreProvider** to inject multiple stores into your Flutter application. + +```dart +import 'package:flutter/material.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class TodoStore extends StoreInterface> { + TodoStore() + : super( + {'tasks': [], 'categories': []}, + enableDebugging: true, + ); +} + +class SampleStore extends StoreInterface> { + SampleStore() + : super( + {'sampleData': 'Hello MultiStore!'}, + enableDebugging: true, + ); +} + +class MultiStoreExampleScreen extends StatelessWidget { + const MultiStoreExampleScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MultiStoreProvider( + definitions: [ + StoreDefinition(TodoStore()), + StoreDefinition(SampleStore()), + ], + child: Scaffold( + appBar: AppBar( + title: const Text('MultiStoreProvider Example'), + ), + body: Column( + children: [ + Expanded( + child: StoreConsumer( + builder: (context, todoStore) { + final tasks = todoStore.state['tasks'] ?? []; + return tasks.isEmpty + ? const Center(child: Text('No tasks available')) + : ListView.builder( + itemCount: tasks.length, + itemBuilder: (context, index) => ListTile( + title: Text('Task ${index + 1}'), + ), + ); + }, + ), + ), + Expanded( + child: StoreConsumer( + builder: (context, sampleStore) { + final sampleData = sampleStore.state['sampleData']; + return Center( + child: Text('Sample Data: $sampleData'), + ); + }, + ), + ), + ], + ), + ), + ); + } +} +``` + +--- + +## Implementation Details + +### MultiStoreProvider Constructor + +```dart +MultiStoreProvider({ + required List definitions, + required Widget child, +}) +``` + +- **`definitions`**: A list of `StoreDefinition` objects, each specifying a store type and its instance. +- **`child`**: The widget tree that the providers will wrap. + +### StoreDefinition + +A helper class used to pair a store with its corresponding **StoreProvider**. + +```dart +StoreDefinition(TodoStore()), +StoreDefinition(SampleStore()), +``` + +--- + +## When to Use + +Use the **MultiStoreProvider** when your application requires multiple stores for managing state. This widget reduces complexity by combining providers into a single, manageable structure. + +--- + +For further information, visit the full [documentation repository](https://github.com/upperdo/upper_flutter_stores). diff --git a/docs/PROVIDER_OVERVIEW.md b/docs/PROVIDER_OVERVIEW.md new file mode 100644 index 0000000..d607ac4 --- /dev/null +++ b/docs/PROVIDER_OVERVIEW.md @@ -0,0 +1,124 @@ +# Provider Overview: **upper\_flutter\_stores** + +The **upper\_flutter\_stores** package includes multiple types of providers designed to facilitate state management for your Flutter applications. Each provider offers distinct functionality tailored to specific use cases. This document explains when to use each provider and why it’s recommended. + +--- + +## Table of Contents + +1. [StoreProvider](#storeprovider) +2. [StoreConsumer](#storeconsumer) +3. [MultiStoreProvider](#multistoreprovider) + +--- + +## StoreProvider + +### Overview + +`StoreProvider` is the primary provider for injecting a single store into your widget tree. It allows child widgets to access and interact with the store via context. + +### When to Use + +- **Single Store Applications**: Use when your app manages a single store instance for its state. +- **Scoped State Management**: Use to limit the store’s visibility to a specific subtree in your widget hierarchy. + +### Why Use StoreProvider? + +- **Simplicity**: Provides a straightforward way to inject a single store. +- **Performance**: Reduces rebuilds by scoping state updates to specific areas of the widget tree. + +### Example + +```dart +StoreProvider( + store: TodoStore(), + child: MaterialApp( + home: TodoScreen(), + ), +); +``` + +--- + +## StoreConsumer + +### Overview + +`StoreConsumer` is a widget that listens to changes in a store and rebuilds its child widgets when the store’s state changes. It’s designed to work in tandem with `StoreProvider`. + +### When to Use + +- **Fine-Grained Updates**: Use when a specific widget needs to rebuild only when a store’s state changes. +- **Custom Rendering**: Use to build custom UIs based on the current state of a store. + +### Why Use StoreConsumer? + +- **Customizability**: Allows you to define how the UI should respond to state changes. +- **Ease of Use**: Automatically handles listening to state changes and rebuilding the widget. + +### Example + +```dart +StoreConsumer( + builder: (context, todoStore) { + final tasks = todoStore.tasks; + return ListView.builder( + itemCount: tasks.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(tasks[index]['title']), + ); + }, + ); + }, +); +``` + +--- + +## MultiStoreProvider + +### Overview + +`MultiStoreProvider` simplifies managing multiple stores by wrapping them in a single provider widget. It ensures all stores are accessible to the child widgets. + +### When to Use + +- **Applications with Multiple Stores**: Use when your app requires different stores for managing independent pieces of state. +- **Complex Widget Trees**: Use to avoid nesting multiple `StoreProvider` instances manually. + +### Why Use MultiStoreProvider? + +- **Cleaner Code**: Reduces boilerplate by managing multiple stores in a single widget. +- **Scalability**: Makes it easier to add or remove stores as your app grows. + +### Example + +```dart +MultiStoreProvider( + definitions: [ + StoreDefinition(TodoStore()), + StoreDefinition(SampleStore()), + ], + child: MaterialApp( + home: MultiStoreExampleScreen(), + ), +); +``` + +--- + +## Choosing the Right Provider + +| **Scenario** | **Recommended Provider** | **Reason** | +| ------------------------------------------- | ------------------------ | --------------------------------------------------------------------- | +| Managing a single store in a small app | StoreProvider | Simple and efficient for injecting a single store. | +| Fine-grained control over widget rebuilding | StoreConsumer | Allows you to define specific widget rebuild logic. | +| Managing multiple independent stores | MultiStoreProvider | Reduces boilerplate and ensures clean code for multiple stores. | +| Scoped state management | StoreProvider | Restricts the store’s visibility to a subtree, improving performance. | +| Custom rendering based on state | StoreConsumer | Gives full control over how the UI responds to store state changes. | + +--- + +Each provider in **upper\_flutter\_stores** is designed with specific use cases in mind, ensuring flexibility, performance, and developer convenience. Use this guide to make informed decisions about which provider to use in your application. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..c145ab9 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,98 @@ +# Documentation: **upper_flutter_stores** + +## Table of Contents + +1. [Introduction](#introduction) +2. [Installation](#installation) +3. [Features Overview](#features-overview) + - [Unified Store Overview](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/UNIFIED_STORE_OVERVIEW.md) + - [Provider Overview](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/PROVIDER_OVERVIEW.md) + - [StoreProvider](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/STOREPROVIDER.md) + - [StoreConsumer](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/STORECONSUMER.md) + - [MultiStoreProvider](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/MULTISTORE_PROVIDER.md) + - [Undo/Redo Functionality](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/UNIFIED_UNDO_REDO.md) + - [Snapshots and Replay](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/UNIFIED_SNAPSHOTS.md) + - [Persistence](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/UNIFIED_PERSISTENCE.md) + - [Middleware](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/UNIFIED_MIDDLEWARE.md) + - [Separated Overview Store](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/SEPARATED_OVERVIEW.md) +4. [Architecture](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/ARCHITECTURE.md) +5. [Suggested Architecture](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/SUGGESTED_ARCHITECTURE.md) +6. [Use Cases](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/USE_CASES.md) +7. [API Definition](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/API_DEFINITION.md) +8. [Comparison with Other Solutions](#comparison-with-other-solutions) +--- + +## Introduction + +The **upper_flutter_stores** package is a robust and user-friendly state management solution for Flutter applications. Inspired by simplicity and extensibility, it provides: + +- Unified state management for various types of stores (e.g., persistent, undoable, async). +- Support for snapshots and reactivity out of the box. +- Multi-store provider for seamless dependency injection. +- Middleware capabilities for intercepting and enhancing state transitions. +- A developer-friendly API for minimal boilerplate and maximum productivity. + +--- + +## Installation + +To use **upper_flutter_stores**, add the package to your `pubspec.yaml` file: + +```yaml +dependencies: + upper_flutter_stores: latest +``` + +Then run: + +```bash +flutter pub get +``` + +--- + +## Features Overview + +The **upper_flutter_stores** package offers: + +1. **[Unified Store Overview](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/UNIFIED_STORE_OVERVIEW.md)**: A single store encapsulating multiple functionalities such as persistence, undo/redo, snapshots, and more. +2. **[StoreProvider](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/STOREPROVIDER.md)**: Dependency injection for state management. +3. **[StoreConsumer](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/STORECONSUMER.md)**: Simplifies accessing the store in the widget tree. +4. **[MultiStoreProvider](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/MULTISTORE_PROVIDER.md)**: Manage multiple stores seamlessly. +5. **[Undo/Redo Functionality](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/UNIFIED_UNDO_REDO.md)**: Enables undoing and redoing state changes easily. +6. **[Snapshots and Replay](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/UNIFIED_SNAPSHOTS.md)**: Allows state snapshots and replay for debugging and more. +7. **[Persistence](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/UNIFIED_PERSISTENCE.md)**: Save and restore state across app sessions. +8. **[Middleware](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/UNIFIED_MIDDLEWARE.md)**: Intercept and enhance state transitions. +9. **[Separated Overview Store](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/SEPARATED_OVERVIEW.md)**: A modular approach for specific state management features. + +--- + +## Architecture + +Learn about the architectural philosophy and design principles behind **upper_flutter_stores** in the [Architecture Guide](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/ARCHITECTURE.md). + +--- + +## API Definition + +For a detailed overview of available classes, methods, and their usage, refer to the [API Definition](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/API_DEFINITION.md). + +--- + +## Comparison with Other Solutions + +| Feature | upper_flutter_stores | Provider/InheritedWidget | Riverpod | Bloc/Cubit | +|----------------------------------|--------------------------------|--------------------------|---------------------------|----------------------------| +| Unified Store | **Yes** | No | No | No | +| Multi-Store Support | **Yes** | Limited | **Yes** | Limited | +| Middleware | **Yes** | No | No | Limited | +| Undo/Redo | **Yes** | No | No | No | +| Persistence | **Yes** | No | Limited | No | +| Snapshots and Replay | **Yes** | No | No | No | +| Ease of Use | **High** | Moderate | High | Low | +| Boilerplate | **Minimal** | Minimal | Minimal | **High** | +| Extensibility | **High** | Limited | High | Limited | + +**Note**: This table highlights key differences and strengths of **upper_flutter_stores** compared to popular Flutter state management solutions. + +--- diff --git a/docs/SEPARATED_ASYNC.md b/docs/SEPARATED_ASYNC.md new file mode 100644 index 0000000..5b42b60 --- /dev/null +++ b/docs/SEPARATED_ASYNC.md @@ -0,0 +1,105 @@ +# Documentation: **upper_flutter_stores** + +### AsyncStore + +The **AsyncStore** provides built-in support for managing asynchronous tasks while maintaining state consistency. This store is useful for operations such as fetching data from a remote API or performing long-running calculations. + +#### Features of AsyncStore + +- Built-in support for asynchronous operations. +- Automatically updates state based on the outcome of asynchronous tasks. +- Provides a state object for monitoring the current status (`loading`, `data`, or `error`). + +#### Constructor + +```dart +AsyncStore({ + bool enableDebugging = false, + String? debugContext, +}) +``` + +- `enableDebugging`: Enables debug logs to provide insights into the state changes during asynchronous operations. +- `debugContext`: Adds contextual information for debugging. + +#### AsyncState Object + +The `AsyncState` object provides the current state of the asynchronous operation. It contains the following properties: + +- `loading`: A `bool` indicating whether the task is in progress. +- `data`: The result of the completed task (if successful). +- `error`: The error object (if the task fails). + +#### Methods + +- `run(Future Function() task)`: Executes an asynchronous task and updates the state based on its progress. + - `task`: A function that returns a `Future`. + +#### Usage Example + +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class WeatherStore extends AsyncStore> { + WeatherStore() : super(enableDebugging: true, debugContext: 'WeatherStore'); + + Future fetchWeather() async { + await run(() async { + // Simulate API call + await Future.delayed(Duration(seconds: 2)); + return { + 'temperature': '20Β°C', + 'condition': 'Cloudy', + }; + }); + } + + Map? get weatherData => state.data; + + bool get isLoading => state.loading; + + dynamic get error => state.error; +} + +void main() { + final weatherStore = WeatherStore(); + + weatherStore.fetchWeather(); +} +``` + +#### State Monitoring + +To observe state changes and update the UI: + +```dart +import 'package:flutter/material.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class WeatherScreen extends StatelessWidget { + final WeatherStore weatherStore = WeatherStore(); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: weatherStore, + builder: (context, _, __) { + if (weatherStore.isLoading) { + return Center(child: CircularProgressIndicator()); + } else if (weatherStore.error != null) { + return Center(child: Text('Error: ${weatherStore.error}')); + } else { + final data = weatherStore.weatherData; + return Center( + child: Text( + 'Temperature: ${data?['temperature']}, Condition: ${data?['condition']}', + ), + ); + } + }, + ); + } +} +``` + +The **AsyncStore** simplifies asynchronous operations by managing the loading, success, and error states transparently. This ensures a seamless developer experience for handling dynamic, data-driven applications. diff --git a/docs/SEPARATED_BASE.md b/docs/SEPARATED_BASE.md new file mode 100644 index 0000000..e298cb6 --- /dev/null +++ b/docs/SEPARATED_BASE.md @@ -0,0 +1,82 @@ +# Documentation: **upper_flutter_stores** + +### BaseStore + +The `BaseStore` is the core store implementation in the `upper_flutter_stores` package. It provides essential state management functionality and serves as the foundation for all other store types. It is lightweight, efficient, and designed to be extended or used directly in applications. + +#### Key Features +- **State Management**: Manage state in a reactive manner with `set` and `state`. +- **Reactivity**: Automatically notify listeners when the state is updated. +- **Debugging**: Enable debugging to log state transitions and changes. + +#### Usage + +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class CounterStore extends BaseStore { + CounterStore() : super(0); // Initial state is set to 0 + + void increment() { + set(state + 1); // Update state by incrementing the current value + } + + void decrement() { + set(state - 1); // Update state by decrementing the current value + } +} +``` + +#### Example + +```dart +import 'package:flutter/material.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class CounterScreen extends StatelessWidget { + final CounterStore store = CounterStore(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Counter Example'), + ), + body: Center( + child: ValueListenableBuilder( + valueListenable: store, + builder: (context, value, _) { + return Text( + 'Counter Value: $value', + style: const TextStyle(fontSize: 24), + ); + }, + ), + ), + floatingActionButton: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + FloatingActionButton( + onPressed: store.increment, + child: const Icon(Icons.add), + ), + FloatingActionButton( + onPressed: store.decrement, + child: const Icon(Icons.remove), + ), + ], + ), + ); + } +} +``` + +#### Debugging +Enable debugging by passing `enableDebugging: true` to the constructor: + +```dart +final store = BaseStore(0, enableDebugging: true); +``` +This will log state transitions in the console, aiding in tracing and debugging changes in the state. + +--- diff --git a/docs/SEPARATED_PERSISTENT.md b/docs/SEPARATED_PERSISTENT.md new file mode 100644 index 0000000..2cf6d2f --- /dev/null +++ b/docs/SEPARATED_PERSISTENT.md @@ -0,0 +1,101 @@ +# Documentation: **upper_flutter_stores** +### PersistentStore + +The `PersistentStore` is a specialized store that extends `BaseStore` to provide state persistence. It ensures that the store's state is saved and restored across app sessions, using mechanisms like `SharedPreferences` or other persistence solutions. + +#### Features +- Save the state persistently. +- Automatically restore the state on initialization. +- Debugging support to trace state changes and persistence events. + +#### Usage +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class PersistentCounterStore extends PersistentStore { + PersistentCounterStore() + : super( + 0, // Initial state + persistKey: 'counter_store', // Key used for persistence + fromJson: (json) => json as int, // Deserialization logic + toJson: (state) => state, // Serialization logic + enableDebugging: true, // Enable debugging + ); + + void increment() { + set(state + 1); + } + + void decrement() { + set(state - 1); + } +} +``` + +#### Explanation +- **Initialization:** The constructor takes the initial state, a `persistKey` to identify the store, and functions for serializing (`toJson`) and deserializing (`fromJson`) the state. +- **Persistence:** + - `initialize()` is called internally to load persisted state during app startup. + - `persist()` saves the current state to the persistence layer. +- **Debugging:** If `enableDebugging` is set to `true`, detailed logs about state changes and persistence events are printed. + +#### Example Integration +```dart +import 'package:flutter/material.dart'; +import 'path/to/persistent_counter_store.dart'; + +class PersistentCounterScreen extends StatelessWidget { + const PersistentCounterScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final store = PersistentCounterStore(); + + return StoreProvider( + store: store, + child: Scaffold( + appBar: AppBar( + title: const Text('Persistent Counter Example'), + ), + body: Center( + child: ValueListenableBuilder( + valueListenable: store, + builder: (context, value, _) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Counter: $value', + style: Theme.of(context).textTheme.headline4, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: const Icon(Icons.remove), + onPressed: store.decrement, + ), + IconButton( + icon: const Icon(Icons.add), + onPressed: store.increment, + ), + ], + ), + ], + ); + }, + ), + ), + ), + ); + } +} +``` + +#### Key Methods +- `initialize()`: Loads the persisted state and sets it in the store. +- `persist()`: Saves the current state to the persistence layer. + +#### Notes +- Ensure that `persistKey` is unique for each store to avoid collisions. +- Use lightweight serialization/deserialization functions for better performance. diff --git a/docs/SEPARATED_SNAPSHOTS.md b/docs/SEPARATED_SNAPSHOTS.md new file mode 100644 index 0000000..af1f91e --- /dev/null +++ b/docs/SEPARATED_SNAPSHOTS.md @@ -0,0 +1,87 @@ +# Documentation: **upper_flutter_stores** + +### SnapshotStore + +The `SnapshotStore` is a powerful extension of `BaseStore` that allows you to take snapshots of the store's state at any point in time and replay those snapshots when needed. This feature is useful for implementing time-travel debugging, undo/redo functionality, or for maintaining different application states during development or testing. + +#### Features +- Take snapshots of the current state. +- Replay snapshots by index. +- Maintain a history of snapshots for debugging and state tracking. + +#### Constructor +```dart +SnapshotStore( + T initialState, { + bool enableDebugging = false, + String? debugContext, +}) +``` + +##### Parameters +- `initialState`: The initial state of the store. +- `enableDebugging`: If `true`, enables debug logs for snapshot operations. +- `debugContext`: A string used to identify the store in debug logs. + +#### Methods + +##### `takeSnapshot` +Takes a snapshot of the current state and adds it to the history. + +```dart +void takeSnapshot() +``` + +##### `replay` +Replays a snapshot from the history by its index. + +```dart +void replay(int index) +``` + +##### `snapshotHistory` +Returns the list of all snapshots stored in the `SnapshotStore`. + +```dart +List get snapshotHistory +``` + +#### Example Usage + +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class CounterStore extends SnapshotStore { + CounterStore() : super(0, enableDebugging: true, debugContext: 'CounterStore'); + + void increment() { + set(state + 1); + takeSnapshot(); + } + + void decrement() { + set(state - 1); + takeSnapshot(); + } + + void resetToSnapshot(int index) { + replay(index); + } +} + +void main() { + final store = CounterStore(); + + store.increment(); // State: 1, Snapshot taken + store.increment(); // State: 2, Snapshot taken + store.decrement(); // State: 1, Snapshot taken + + print(store.snapshotHistory); // [0, 1, 2, 1] + + store.resetToSnapshot(1); // State reset to 1 (second snapshot) +} +``` + +#### Best Practices +- Use snapshots for debugging purposes and to keep track of state changes over time. +- Avoid storing excessive numbers of snapshots if memory usage is a concern. Consider limiting the size of the snapshot history. diff --git a/docs/SEPARATED_UNDOABLE.md b/docs/SEPARATED_UNDOABLE.md new file mode 100644 index 0000000..d44494e --- /dev/null +++ b/docs/SEPARATED_UNDOABLE.md @@ -0,0 +1,144 @@ +# Documentation: **upper_flutter_stores** + +## UndoableStore + +The `UndoableStore` is a specialized store designed to handle undo and redo functionality. It allows you to revert to a previous state or reapply a reverted change. This is particularly useful in applications where users may need to undo actions or retry actions they've undone. + +### Features +- **Undo and Redo:** Easily traverse state changes with undo and redo functionality. +- **Undo/Redo Stack Management:** Keeps track of state history to enable smooth transitions between states. +- **Debugging Support:** Logs state changes when debugging is enabled. + +### Usage +To use the `UndoableStore`, simply wrap it around your initial state and define how it should handle undo/redo operations. + +### Constructor +```dart +UndoableStore( + T initialState, { + bool enableDebugging = false, + String? debugContext, +}) +``` + +### Example +Here’s how to integrate the `UndoableStore` into your application: + +#### 1. Define an Undoable Store +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class CounterUndoableStore extends UndoableStore { + CounterUndoableStore() + : super(0, enableDebugging: true, debugContext: "CounterUndoableStore"); + + void increment() { + set(state + 1); + } + + void decrement() { + set(state - 1); + } +} +``` + +#### 2. Use the Undoable Store in a Widget +```dart +import 'package:flutter/material.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class UndoableCounterScreen extends StatelessWidget { + const UndoableCounterScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final counterStore = CounterUndoableStore(); + + return StoreProvider( + store: counterStore, + child: Scaffold( + appBar: AppBar( + title: const Text('Undoable Counter Example'), + ), + body: Center( + child: ValueListenableBuilder( + valueListenable: counterStore, + builder: (context, value, _) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Counter Value: $value', style: const TextStyle(fontSize: 24)), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: counterStore.increment, + ), + IconButton( + icon: const Icon(Icons.remove), + onPressed: counterStore.decrement, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: counterStore.canUndo ? counterStore.undo : null, + child: const Text('Undo'), + ), + const SizedBox(width: 10), + ElevatedButton( + onPressed: counterStore.canRedo ? counterStore.redo : null, + child: const Text('Redo'), + ), + ], + ), + ], + ); + }, + ), + ), + ), + ); + } +} +``` + +### Methods + +#### `void undo()` +Reverts the store to the previous state if available. +```dart +if (store.canUndo) { + store.undo(); +} +``` + +#### `void redo()` +Reapplies a reverted state if available. +```dart +if (store.canRedo) { + store.redo(); +} +``` + +#### `bool get canUndo` +Returns `true` if there is a previous state available for undo. + +#### `bool get canRedo` +Returns `true` if there is a reverted state available for redo. + +### Debugging Example +Enable debugging to trace undo/redo operations. +```dart +final store = UndoableStore(0, enableDebugging: true); +store.set(1); +store.undo(); // Logs: Undo performed. +store.redo(); // Logs: Redo performed. +``` + +### Notes +- Ensure state changes are meaningful to avoid cluttering the undo stack. +- Limit the size of the undo stack if necessary to save memory. diff --git a/docs/SEPARATED_UNDO_REDO.md b/docs/SEPARATED_UNDO_REDO.md new file mode 100644 index 0000000..d6efe76 --- /dev/null +++ b/docs/SEPARATED_UNDO_REDO.md @@ -0,0 +1,104 @@ +# Documentation: **upper_flutter_stores** + +## Undo/Redo Functionality + +The **undo/redo** functionality in `upper_flutter_stores` allows you to manage state changes in a reversible manner. This feature is particularly useful for applications that require the ability to revert or redo changes, such as text editors, form inputs, or task managers. + +### Enabling Undo/Redo +Undo/Redo is supported by the unified `Store` and can be enabled when initializing a `StoreInterface`. To enable this feature, set the `enableUndoRedo` parameter to `true` when creating your store. + +```dart +class TodoStore extends StoreInterface> { + TodoStore() + : super( + { + 'tasks': >[], + 'categories': ['General'], + }, + enableDebugging: true, + enableUndoRedo: true, + ); +} +``` + +### Using Undo/Redo + +#### Undo +To revert to the previous state, call the `undo` method on your store. Ensure that the `undo` method is only called when `canUndo` is `true`. + +```dart +void undoAction(TodoStore store) { + if (store.canUndo) { + store.undo(); + print('Undo performed. Current state: ${store.state}'); + } else { + print('No undo available.'); + } +} +``` + +#### Redo +To reapply a reverted state, call the `redo` method. Ensure that the `redo` method is only called when `canRedo` is `true`. + +```dart +void redoAction(TodoStore store) { + if (store.canRedo) { + store.redo(); + print('Redo performed. Current state: ${store.state}'); + } else { + print('No redo available.'); + } +} +``` + +### Example +Here is an example of how undo/redo functionality can be used in a Flutter application: + +```dart +import 'package:flutter/material.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; +import 'todo_store.dart'; + +class UndoRedoExample extends StatelessWidget { + const UndoRedoExample({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final store = StoreProvider.of(context); + + return Scaffold( + appBar: AppBar( + title: const Text('Undo/Redo Example'), + actions: [ + IconButton( + icon: const Icon(Icons.undo), + onPressed: store.canUndo ? store.undo : null, + ), + IconButton( + icon: const Icon(Icons.redo), + onPressed: store.canRedo ? store.redo : null, + ), + ], + ), + body: ValueListenableBuilder( + valueListenable: store, + builder: (context, value, _) { + final tasks = store.tasks; + return tasks.isEmpty + ? const Center(child: Text('No tasks available.')) + : ListView.builder( + itemCount: tasks.length, + itemBuilder: (context, index) { + final task = tasks[index]; + return ListTile( + title: Text(task['title'] ?? ''), + subtitle: Text(task['description'] ?? ''), + ); + }, + ); + }, + ), + ); + } +} +``` diff --git a/docs/SEPERATED_COMPUTED.md b/docs/SEPERATED_COMPUTED.md new file mode 100644 index 0000000..fa66e59 --- /dev/null +++ b/docs/SEPERATED_COMPUTED.md @@ -0,0 +1,63 @@ +# Documentation: **upper_flutter_stores** + +### ComputedStore + +The `ComputedStore` enables reactive computations based on other stores' states. This is useful for deriving new state values that depend on one or more independent stores, ensuring the computed state updates automatically whenever the dependencies change. + +#### Key Features +- Automatically computes derived state based on dependencies. +- Simplifies state management for computed values. +- Reduces redundant logic by centralizing derived state in the `ComputedStore`. + +#### Example Usage + +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class CounterStore extends BaseStore { + CounterStore() : super(0); + + void increment() => set(state + 1); + void decrement() => set(state - 1); +} + +class ComputedExampleStore extends ComputedStore { + ComputedExampleStore( + CounterStore counterStore, + ) : super( + initialState: 0, + compute: () => counterStore.state * 2, + dependencies: [counterStore], + enableDebugging: true, + ); +} + +void main() { + final counterStore = CounterStore(); + final computedStore = ComputedExampleStore(counterStore); + + computedStore.addListener(() { + print('Computed state: ${computedStore.state}'); + }); + + counterStore.increment(); // Output: Computed state: 2 + counterStore.increment(); // Output: Computed state: 4 +} +``` + +#### Constructor Parameters +- `initialState`: The initial state of the `ComputedStore`. +- `compute`: A function that derives the computed state. +- `dependencies`: A list of stores whose state changes trigger recomputation. +- `enableDebugging`: Optional. Enables debugging logs for state updates. + +#### Methods and Properties +- **`state`**: The computed state, derived automatically based on dependencies. +- **`addListener(VoidCallback listener)`**: Registers a listener for state changes. +- **`removeListener(VoidCallback listener)`**: Removes a registered listener. + +#### Notes +- ComputedStores are read-only; you cannot directly update their state. Instead, modify the state of their dependencies to trigger recomputation. +- The `compute` function is called whenever any dependency's state changes, ensuring the computed value remains up to date. + +The `ComputedStore` is ideal for scenarios where you need to derive reactive state based on multiple other stores, reducing complexity and promoting cleaner architecture. diff --git a/docs/SEPERATED_OVERVIEW.md b/docs/SEPERATED_OVERVIEW.md new file mode 100644 index 0000000..bcfeaf4 --- /dev/null +++ b/docs/SEPERATED_OVERVIEW.md @@ -0,0 +1,23 @@ +# Documentation: **upper_flutter_stores** + +# Separated Store Overview + +**Separated Stores** in the `upper_flutter_stores` package provide a modular and specialized approach to store management. These stores focus on individual features or functionalities, offering advanced capabilities tailored to specific needs. + +### Benefits of Using Separated Stores +- **Granular Control**: Each store is designed for a specific purpose, making it easier to focus on the feature at hand. +- **Reusability**: Feature-specific stores can be reused across projects. +- **Advanced Capabilities**: Gain access to specialized features like undo/redo, snapshots, and computed values without managing everything manually. +- **Scalability**: Adopt only the stores you need without including unnecessary overhead. + +### List of Separated Stores +Here is a list of the separated stores available in the `upper_flutter_stores` package: + +1. **[BaseStore](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/SEPARATED_BASE.md)**: The foundational store that provides core functionality for state management. +2. **[UndoableStore](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/SEPARATED_UNDOABLE.md)**: Adds undo/redo functionality to your state management. +3. **[PersistentStore](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/SEPARATED_PERSISTENT.md)**: Enables persistence of state across app sessions using local storage. +4. **[AsyncStore](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/SEPARATED_ASYNC.md)**: Handles asynchronous state updates, such as network requests or time-based updates. +5. **[ComputedStore](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/SEPARATED_COMPUTED.md)**: Computes derived state based on dependencies, automatically recalculating when dependencies change. +6. **[SnapshotStore](https://github.com/upperdo/upper_flutter_stores/blob/main/docs/SEPARATED_SNAPSHOTS.md)**: Allows taking and replaying snapshots of the state for debugging or temporal operations. + +Each store can be used independently or integrated into the `Store` class for a unified experience. diff --git a/docs/STORE_CONSUMER.md b/docs/STORE_CONSUMER.md new file mode 100644 index 0000000..35b8100 --- /dev/null +++ b/docs/STORE_CONSUMER.md @@ -0,0 +1,92 @@ +# StoreConsumer + +The **StoreConsumer** widget is a utility for consuming and rebuilding UI based on changes to a specific store. It simplifies accessing a store from the widget tree and reacting to its state changes. + +## Features + +- Automatically rebuilds the widget when the store's state changes. +- Provides a clean and intuitive API for accessing stores within the widget tree. +- Avoids unnecessary boilerplate when interacting with stores. + +--- + +## Usage + +To use **StoreConsumer**, wrap it around your widget and provide a `builder` function. The `builder` function gives you access to the store and the `BuildContext`. + +### Example + +```dart +import 'package:flutter/material.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; +import 'todo_store.dart'; + +class TaskListScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Task List'), + ), + body: StoreConsumer( + builder: (context, todoStore) { + final tasks = todoStore.tasks; + + return tasks.isEmpty + ? const Center(child: Text('No tasks available.')) + : ListView.builder( + itemCount: tasks.length, + itemBuilder: (context, index) { + final task = tasks[index]; + final isDone = task['done'] == true; + + return ListTile( + title: Text( + task['title'] ?? '', + style: TextStyle( + decoration: isDone ? TextDecoration.lineThrough : null, + ), + ), + subtitle: Text(task['description'] ?? ''), + trailing: IconButton( + icon: Icon( + isDone ? Icons.check_circle : Icons.radio_button_unchecked, + color: isDone ? Colors.green : Colors.grey, + ), + onPressed: () => todoStore.completeTask(index), + ), + ); + }, + ); + }, + ), + ); + } +} +``` + +--- + +## Parameters + +| Parameter | Type | Description | +| --------- | ---------------------------------------- | ---------------------------------------------------------------- | +| `builder` | `Widget Function(BuildContext, T store)` | A function that builds the widget tree using the provided store. | + +--- + +## Best Practices + +1. **Minimize Rebuilds**: Ensure that only the necessary parts of the widget tree are rebuilt by scoping your **StoreConsumer** usage appropriately. + +2. **Error Handling**: Include error and empty-state handling in your `builder` function to provide a smooth user experience. + +3. **Readability**: Use meaningful variable names and structure the `builder` function to maintain code clarity. + +--- + +## Conclusion + +The **StoreConsumer** widget is a powerful tool for interacting with your stores in a clean, responsive manner. It eliminates boilerplate and ensures your UI reacts seamlessly to state changes. Integrate it into your workflow to simplify state-driven UI updates. + +For more advanced usage and additional features, refer to the [documentation](https://github.com/upperdo/upper_flutter_stores). diff --git a/docs/STORE_PROVIDER.md b/docs/STORE_PROVIDER.md new file mode 100644 index 0000000..fefdbee --- /dev/null +++ b/docs/STORE_PROVIDER.md @@ -0,0 +1,124 @@ +# StoreProvider Documentation + +## Overview +The `StoreProvider` is a core component of the **upper_flutter_stores** package, enabling state management by providing a single store to a subtree of widgets. It utilizes Flutter’s `InheritedWidget` to make the store accessible to any descendant widget. + +## Features +- **Type-safe access**: Provides type-safe access to the store in the widget tree. +- **Reactivity**: Automatically rebuilds widgets when the store’s state changes. +- **Modularity**: Makes it easy to inject specific stores into distinct parts of the widget tree. + +## Usage + +### Import +First, ensure you import the necessary package: + +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; +``` + +### Basic Example +Wrap a part of your widget tree with `StoreProvider` to inject a specific store: + +```dart +import 'package:flutter/material.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class CounterStore extends StoreInterface { + CounterStore() : super(0); + + void increment() => set(state + 1); + void decrement() => set(state - 1); +} + +void main() { + runApp( + StoreProvider( + store: CounterStore(), + child: const MyApp(), + ), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final counterStore = StoreProvider.of(context); + + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('StoreProvider Example')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ValueListenableBuilder( + valueListenable: counterStore, + builder: (context, value, _) { + return Text('Counter: $value'); + }, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: const Icon(Icons.remove), + onPressed: counterStore.decrement, + ), + IconButton( + icon: const Icon(Icons.add), + onPressed: counterStore.increment, + ), + ], + ), + ], + ), + ), + ), + ); + } +} +``` + +### API Reference +#### Constructor +```dart +const StoreProvider({ + required T store, + required Widget child, + Key? key, +}) +``` +- **`store`**: The instance of the store to provide. +- **`child`**: The widget subtree that can access the provided store. + +#### Static Method +```dart +static T of(BuildContext context) +``` +- Retrieves the store instance of type `T` from the nearest ancestor `StoreProvider`. +- Throws an assertion if no matching `StoreProvider` is found in the widget tree. + +#### Update Behavior +The `StoreProvider` will notify its descendants only when the provided store changes. + +### Best Practices +1. **Avoid nesting providers unnecessarily**: + Use `MultiStoreProvider` when injecting multiple stores. +2. **Keep stores lightweight**: + Stores should focus on state management and avoid UI-related logic. +3. **Combine with consumers**: + Use `StoreConsumer` or `ValueListenableBuilder` for clean and reactive UI updates. + +### Common Pitfalls +1. **Forgetting to wrap with a provider**: + Ensure every widget accessing a store is wrapped by a `StoreProvider`. +2. **Overlapping providers**: + Be mindful of provider scopes to avoid unintentional overrides. + +## Next Steps +Explore [StoreConsumer](STORECONSUMER.md) to learn how to reactively consume stores in the widget tree. + +For more advanced usage, refer to the [Unified Store Overview](README.md). diff --git a/docs/SUGGESTED_ARCHITECTURED.md b/docs/SUGGESTED_ARCHITECTURED.md new file mode 100644 index 0000000..9644bce --- /dev/null +++ b/docs/SUGGESTED_ARCHITECTURED.md @@ -0,0 +1,187 @@ +# Suggested Architecture for **upper_flutter_stores** + +This document outlines three recommended architectures for using the **upper_flutter_stores** package. Each architecture balances simplicity, scalability, and flexibility while showcasing the strengths of **upper_flutter_stores**. These approaches cater to different project sizes and needs while leveraging the Unified and Separated store features effectively. + +--- + +## **1. Minimal Architecture (Best for Small Projects)** +- **Focus**: Keep it simple with direct use of stores, and limit abstraction layers. +- **When to Use**: Small projects or prototypes where simplicity and speed are key. + +### **Folder Structure** +```plaintext +lib/ +β”œβ”€β”€ main.dart +β”œβ”€β”€ screens/ +β”‚ β”œβ”€β”€ home_screen.dart +β”‚ β”œβ”€β”€ add_task_screen.dart +β”‚ β”œβ”€β”€ add_category_screen.dart +β”‚ β”œβ”€β”€ search_screen.dart +β”œβ”€β”€ store/ +β”‚ └── todo_store.dart +└── providers.dart +``` + +### **Key Points** +- **Unified Store Logic**: + - All state management logic resides in the store (e.g., `TodoStore`). +- **Business Logic**: + - Minimal separation, with business logic directly implemented in the store. +- **View Access**: + - Access the store directly via `StoreProvider.of(context)` or `StoreConsumer`. + +### **Advantages** +- Simple and fast to implement. +- Direct access to state makes debugging straightforward. + +### **Disadvantages** +- Not ideal for large projects (limited separation of concerns). + +--- + +## **2. Modularized Architecture (Best for Medium Projects)** +- **Focus**: Separate business logic into services for better organization and maintainability. +- **When to Use**: Medium-sized projects with a moderate feature set. + +### **Folder Structure** +```plaintext +lib/ +β”œβ”€β”€ main.dart +β”œβ”€β”€ screens/ +β”‚ β”œβ”€β”€ home_screen.dart +β”‚ β”œβ”€β”€ add_task_screen.dart +β”‚ β”œβ”€β”€ add_category_screen.dart +β”‚ β”œβ”€β”€ search_screen.dart +β”œβ”€β”€ store/ +β”‚ └── todo_store.dart +β”œβ”€β”€ services/ +β”‚ └── todo_service.dart +β”œβ”€β”€ models/ +β”‚ └── task_model.dart +└── providers.dart +``` + +### **Key Points** +- **Unified Store Logic**: + - The store manages state and integrates persistence, undo/redo, and snapshots. +- **Business Logic**: + - Services (e.g., `TodoService`) handle reusable business rules. +- **Models**: + - Use models (e.g., `TaskModel`) for type safety and consistency. +- **View Access**: + - Access the store via `StoreProvider.of(context)` or `StoreConsumer`. + +### **Advantages** +- Clear separation of concerns. +- Services simplify business logic and enable easy testing. +- Reusable and maintainable architecture. + +### **Disadvantages** +- Slightly more complexity compared to the minimal approach. + +--- + +## **3. Feature-First Architecture (Best for Large Projects)** +- **Focus**: Organize code by features, encapsulating all logic for each feature. +- **When to Use**: Large projects requiring scalability and modularity. + +### **Folder Structure** +```plaintext +lib/ +β”œβ”€β”€ main.dart +β”œβ”€β”€ features/ +β”‚ β”œβ”€β”€ todo/ +β”‚ β”‚ β”œβ”€β”€ screens/ +β”‚ β”‚ β”‚ β”œβ”€β”€ home_screen.dart +β”‚ β”‚ β”‚ β”œβ”€β”€ add_task_screen.dart +β”‚ β”‚ β”‚ β”œβ”€β”€ add_category_screen.dart +β”‚ β”‚ β”‚ β”œβ”€β”€ search_screen.dart +β”‚ β”‚ β”œβ”€β”€ store/ +β”‚ β”‚ β”‚ └── todo_store.dart +β”‚ β”‚ β”œβ”€β”€ services/ +β”‚ β”‚ β”‚ └── todo_service.dart +β”‚ β”‚ β”œβ”€β”€ models/ +β”‚ β”‚ β”‚ └── task_model.dart +β”‚ β”‚ └── todo_provider.dart +└── common/ + β”œβ”€β”€ widgets/ + β”œβ”€β”€ utils/ + └── themes/ +``` + +### **Key Points** +- **Feature Isolation**: + - Each feature encapsulates its screens, store, services, and models. +- **Shared Logic**: + - Reusable components like widgets and themes live in a `common/` folder. +- **Scalability**: + - Add new features by creating new folders under `features/`. + +### **Advantages** +- Highly scalable and modular. +- Feature isolation simplifies collaboration and onboarding. +- Encourages adherence to clean architecture principles. + +### **Disadvantages** +- Requires discipline to maintain feature boundaries. +- Slightly higher initial setup effort. + +--- + +## **4. Advanced Feature-First Architecture with Store Inheritance** +- **Focus**: Extend stores with advanced features like custom middleware, computed values, and async operations using inheritance. +- **When to Use**: Large-scale projects that require deep customization and high performance. + +### **Folder Structure** +```plaintext +lib/ +β”œβ”€β”€ main.dart +β”œβ”€β”€ features/ +β”‚ β”œβ”€β”€ todo/ +β”‚ β”‚ β”œβ”€β”€ screens/ +β”‚ β”‚ β”‚ β”œβ”€β”€ home_screen.dart +β”‚ β”‚ β”‚ β”œβ”€β”€ add_task_screen.dart +β”‚ β”‚ β”‚ β”œβ”€β”€ add_category_screen.dart +β”‚ β”‚ β”‚ β”œβ”€β”€ search_screen.dart +β”‚ β”‚ β”œβ”€β”€ store/ +β”‚ β”‚ β”‚ β”œβ”€β”€ todo_store.dart +β”‚ β”‚ β”‚ └── custom_store.dart +β”‚ β”‚ β”œβ”€β”€ services/ +β”‚ β”‚ β”‚ └── todo_service.dart +β”‚ β”‚ β”œβ”€β”€ models/ +β”‚ β”‚ β”‚ └── task_model.dart +β”‚ β”‚ └── todo_provider.dart +└── common/ + β”œβ”€β”€ widgets/ + β”œβ”€β”€ utils/ + └── themes/ +``` + +### **Key Points** +- **Custom Stores**: + - Extend the base store (e.g., `BaseStore`, `UndoableStore`) for specific requirements. +- **Middleware and Snapshots**: + - Leverage advanced features like middleware and snapshots to track state changes. +- **Service Layers**: + - Complex business rules remain in dedicated services. + +### **Advantages** +- Full flexibility with advanced store capabilities. +- Powerful debugging tools with snapshots and middleware. + +### **Disadvantages** +- Requires advanced knowledge of the package and architecture. + +--- + +## **Choosing the Best Architecture** +1. **Minimal Architecture**: + - Great for small projects, simple apps, or quick prototypes. +2. **Modularized Architecture**: + - Best for medium-sized apps requiring reusable business logic. +3. **Feature-First Architecture**: + - Ideal for large, scalable apps with multiple independent features. +4. **Advanced Feature-First Architecture**: + - Suitable for large projects requiring custom stores and advanced debugging tools. + +By selecting the architecture that aligns with your project’s size and complexity, you can maximize the benefits of **upper_flutter_stores** while maintaining a clean, maintainable codebase. diff --git a/docs/UNIFIED_MIDDLEWARE.md b/docs/UNIFIED_MIDDLEWARE.md new file mode 100644 index 0000000..3eaf275 --- /dev/null +++ b/docs/UNIFIED_MIDDLEWARE.md @@ -0,0 +1,130 @@ +# Middleware in **upper_flutter_stores** + +Middleware in the **upper_flutter_stores** package allows you to intercept state changes, enabling logging, validation, analytics, or any custom behavior before or after state transitions. Middleware can be applied to both unified and separated stores, providing flexibility and control over state management. + +--- + +## Using Middleware in Unified Stores + +Middleware can be added to unified stores to intercept and process state changes. This is useful for debugging, validation, or enhancing the state management flow. + +### Adding Middleware to a Unified Store + +Here is an example of adding middleware to a unified store: + +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class TodoStore extends StoreInterface> { + TodoStore() + : super( + { + 'tasks': >[], + 'categories': ['General'], + }, + enableDebugging: true, + ) { + setupMiddleware(); + } + + void setupMiddleware() { + store.addMiddleware((oldState, newState) { + print('Middleware: State changed from $oldState to $newState'); + }); + } + + void addTask(Map task) { + final updatedTasks = [...tasks, task]; + set({ + ...state, + 'tasks': updatedTasks, + }); + } + + List> get tasks => state['tasks'] ?? []; +} +``` + +### Middleware Signature + +Middleware functions in **upper_flutter_stores** have the following signature: + +```dart +typedef Middleware = void Function(T oldState, T newState); +``` + +- **`oldState`**: The state before the change. +- **`newState`**: The state after the change. + +### Adding Multiple Middleware + +You can add multiple middleware functions to the same store. Each middleware function is executed in the order it was added. + +```dart +store.addMiddleware((oldState, newState) { + print('First middleware: $oldState -> $newState'); +}); + +store.addMiddleware((oldState, newState) { + print('Second middleware: $oldState -> $newState'); +}); +``` + +--- + +## Removing Middleware + +If needed, you can remove middleware from a store. + +```dart +final middleware = (oldState, newState) { + print('Middleware logic here'); +}; + +store.addMiddleware(middleware); +store.removeMiddleware(middleware); +``` + +--- + +## Practical Use Cases + +### 1. Logging State Changes +Middleware is often used for logging state transitions to help with debugging. + +```dart +store.addMiddleware((oldState, newState) { + print('State changed from $oldState to $newState'); +}); +``` + +### 2. Validation +Validate the new state before applying it. + +```dart +store.addMiddleware((oldState, newState) { + if (newState['tasks'] == null) { + throw Exception('Tasks cannot be null'); + } +}); +``` + +### 3. Analytics +Track user interactions or state changes for analytics. + +```dart +store.addMiddleware((oldState, newState) { + analyticsService.track('StateChange', { + 'oldState': oldState, + 'newState': newState, + }); +}); +``` + +--- + +## Conclusion + +Middleware is a powerful feature in **upper_flutter_stores** that allows you to enhance and control the behavior of your state management. Whether you're debugging, validating, or integrating analytics, middleware provides a flexible way to intercept and process state transitions. + +For more advanced configurations, refer to the documentation on [Unified Store Overview](UNIFIED_STORE_OVERVIEW.md). diff --git a/docs/UNIFIED_PERSISTENT.md b/docs/UNIFIED_PERSISTENT.md new file mode 100644 index 0000000..800a22f --- /dev/null +++ b/docs/UNIFIED_PERSISTENT.md @@ -0,0 +1,81 @@ +# Documentation: **upper_flutter_stores** + +### Persistence + +The **`upper_flutter_stores`** package allows you to persist state effortlessly using the built-in **PersistentStore** feature. This functionality enables applications to maintain state across app restarts or page reloads. + +#### Enabling Persistence +To enable persistence for a store, configure the `StoreInterface` with: +- **`enablePersistence`** set to `true`. +- A unique **`persistKey`** to identify the store in persistent storage. +- `fromJson` and `toJson` methods to handle serialization and deserialization. + +#### Example: Enabling Persistence for a Store +Here is an example of a store with persistence enabled: + +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class TodoStore extends StoreInterface> { + TodoStore() + : super( + { + 'tasks': >[], + 'categories': ['General'], + }, + enablePersistence: true, // Enable persistence + persistKey: 'todo_store', // Unique key + fromJson: (json) { + final tasks = (json['tasks'] as List?) + ?.map((e) => Map.from(e as Map)) + .toList() ?? + []; + final categories = (json['categories'] as List?) + ?.map((e) => e as String) + .toList() ?? + []; + return { + 'tasks': tasks, + 'categories': categories, + }; + }, + toJson: (state) => state, + ) { + initializePersistence(); + } + + /// Add a task + void addTask(Map task) { + final updatedTasks = [...state['tasks'], task]; + set({ + ...state, + 'tasks': updatedTasks, + }); + } +} +``` + +In this example: +- **`enablePersistence: true`** activates persistence. +- **`persistKey: 'todo_store'`** ensures this store’s data is uniquely identified. +- **`fromJson`** and **`toJson`** handle the conversion between JSON and the internal state representation. + +#### Persistence Methods +- **`initializePersistence()`**: Loads the previously saved state, if available. +- **`persistState()`**: Explicitly saves the current state to persistent storage. + +#### Behavior +- When a store is initialized, it attempts to load the state from persistent storage automatically. +- State changes are automatically persisted when using the **`set`** method. + +#### Accessing Persistent Data +The persistent data is retrieved seamlessly as part of the store's state, so no additional code is required to handle it in your widgets. + +#### Example: Using a Persistent Store +```dart +final todoStore = TodoStore(); + +todoStore.addTask({'title': 'Task 1', 'done': false}); + +// State will persist across app restarts. +``` diff --git a/docs/UNIFIED_SNAPSHOTS.md b/docs/UNIFIED_SNAPSHOTS.md new file mode 100644 index 0000000..a198586 --- /dev/null +++ b/docs/UNIFIED_SNAPSHOTS.md @@ -0,0 +1,99 @@ +# Documentation: **upper_flutter_stores** + +## Snapshots and Replay + +The `upper_flutter_stores` package provides a **snapshot** and **replay** mechanism, which allows you to take snapshots of your store's state and replay them later. This feature is particularly useful for debugging and state management in complex applications. + +### Enabling Snapshots + +Snapshots are an optional feature that must be explicitly enabled when initializing the store. To enable snapshots, set `enableSnapshots: true` when creating the store. + +```dart +final todoStore = TodoStore( + enableSnapshots: true, +); +``` + +### Taking Snapshots + +To capture the current state of a store, call the `takeSnapshot` method: + +```dart +store.takeSnapshot(); +``` + +This method saves the current state of the store to the snapshot stack. Snapshots are stored in memory and can be replayed later to inspect previous states. + +### Replaying Snapshots + +You can replay a snapshot by its index using the `replaySnapshot` method: + +```dart +store.replaySnapshot(snapshotIndex); +``` + +Replaying a snapshot restores the state of the store to the captured snapshot at the specified index. + +### Example Usage + +Here is an example of taking snapshots and replaying them: + +```dart +// Take a snapshot of the current state +store.takeSnapshot(); + +// Modify the state +store.addTask({'title': 'Snapshot Example', 'done': false}); + +// Replay the first snapshot +store.replaySnapshot(0); +``` + +In this example: +- A snapshot is taken before modifying the store's state. +- The state is modified by adding a task. +- The store's state is restored to the snapshot taken earlier. + +### Snapshot Metadata + +Snapshots do not automatically include metadata such as timestamps. If you require metadata, you can implement it by adding custom logic in your application, for example: + +```dart +final snapshotWithMetadata = { + 'timestamp': DateTime.now(), + 'state': store.state, +}; +``` + +### Integration with the `StoreInterface` + +When using the `StoreInterface`, snapshots are seamlessly integrated: + +```dart +class TodoStore extends StoreInterface> { + TodoStore() + : super( + { + 'tasks': >[], + }, + enableSnapshots: true, + ); + + void takeTodoSnapshot() { + takeSnapshot(); + } + + void replayTodoSnapshot(int index) { + replaySnapshot(index); + } +} +``` + +This example demonstrates how to use the snapshot and replay functionality within a store class. + +### Notes + +- **Performance:** Snapshots are stored in memory, so consider memory usage when working with a large number of snapshots. +- **Replay Limitations:** Replaying snapshots resets the state of the store to the captured state but does not undo side effects such as API calls or UI changes. + +Snapshots and replay provide a powerful toolset for debugging and managing state in your application. diff --git a/docs/UNIFIED_STORE.md b/docs/UNIFIED_STORE.md new file mode 100644 index 0000000..4ef4374 --- /dev/null +++ b/docs/UNIFIED_STORE.md @@ -0,0 +1,71 @@ +# Documentation: **upper_flutter_stores** + +## Unified Store Implementation + +The **Unified Store** allows you to combine various store functionalities (e.g., undoable, persistent, async) into a single, cohesive store. + +### Creating a Unified Store + +Define your store by extending `StoreInterface` and initializing the desired features: + +```dart +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class TodoStore extends StoreInterface> { + TodoStore() + : super( + { + 'tasks': >[], + 'categories': ['General'], + }, + enableDebugging: true, + enableUndoRedo: true, + enablePersistence: true, + persistKey: 'todo_store', + fromJson: (json) => Map.from(json), + toJson: (state) => state, + ) { + initializePersistence(); + } + + List> get tasks => state['tasks'] ?? []; + + void addTask(Map task) { + set({ + ...state, + 'tasks': [...tasks, task], + }); + } + + void removeTask(int index) { + final updatedTasks = [...tasks]..removeAt(index); + set({ + ...state, + 'tasks': updatedTasks, + }); + } +} +``` + +### Key Parameters for StoreInterface + +| Parameter | Description | +|---------------------|-----------------------------------------------------------------------------| +| `initialState` | The initial state of the store. | +| `enableDebugging` | Enables debug logs for state changes. | +| `enableUndoRedo` | Enables undo/redo functionality. | +| `enablePersistence` | Persists the state to storage. Requires `persistKey`, `fromJson`, `toJson`. | +| `persistKey` | A unique key to identify the store in storage. | +| `fromJson` | A function to deserialize the state from JSON. | +| `toJson` | A function to serialize the state to JSON. | + +### Usage Example + +```dart +final todoStore = TodoStore(); + +todoStore.addTask({'title': 'Buy groceries', 'done': false}); +todoStore.removeTask(0); +todoStore.undo(); // Reverts the last state change. +todoStore.redo(); // Redoes the reverted change. +``` diff --git a/docs/USE_CASES.md b/docs/USE_CASES.md new file mode 100644 index 0000000..b7b09ab --- /dev/null +++ b/docs/USE_CASES.md @@ -0,0 +1,161 @@ +# Use Cases for **upper_flutter_stores** + +This document outlines various real-world use cases for **upper_flutter_stores** and demonstrates how the package simplifies state management, improves scalability, and enhances developer experience. Each use case highlights the features of the package that make it suitable for the given scenario. + +--- + +## **1. Task Management Application** +### Scenario +A task management app requires features such as: +- Adding, updating, and deleting tasks. +- Persisting tasks locally. +- Supporting undo/redo operations for user actions. +- Categorizing tasks. + +### Solution +- Use a **PersistentStore** to ensure tasks persist across app sessions. +- Utilize **UndoableStore** to enable undo/redo functionality. +- Manage task categories with a unified store or separate `CategoryStore`. + +### Benefits +- Simplified state persistence without boilerplate. +- Out-of-the-box support for undo/redo. +- Modular stores for tasks and categories to maintain clean separation. + +--- + +## **2. E-commerce Platform** +### Scenario +An e-commerce app needs: +- Managing a shopping cart with add/remove operations. +- Storing user preferences persistently. +- Fetching and caching product data asynchronously. + +### Solution +- Use **PersistentStore** for user preferences and cart data. +- Use **AsyncStore** to handle API calls for fetching product information. +- Leverage the unified store to integrate cart, user preferences, and product data management into a single architecture. + +### Benefits +- Unified store simplifies integration across features. +- Async support ensures efficient API calls and caching. +- Persistent storage enhances user experience by saving preferences. + +--- + +## **3. Collaborative Note-Taking Application** +### Scenario +A collaborative note-taking app requires: +- Real-time updates for shared notes. +- Tracking changes with snapshots. +- Undo/redo functionality for note edits. + +### Solution +- Use **SnapshotStore** to take snapshots of the note state and enable time-travel debugging. +- Leverage **UndoableStore** for edit operations. +- Integrate **StoreProvider** and **StoreConsumer** for reactive UI updates. + +### Benefits +- Real-time collaboration supported through reactive stores. +- Enhanced debugging with snapshots. +- User-friendly undo/redo functionality. + +--- + +## **4. Financial Management App** +### Scenario +A financial management app requires: +- Tracking transactions with historical snapshots. +- Categorizing expenses and income. +- Providing a scalable architecture for future features. + +### Solution +- Use **SnapshotStore** to track and replay transaction history. +- Utilize modular stores for transactions, categories, and user preferences. +- Adopt a feature-first architecture for scalability. + +### Benefits +- Easy tracking and visualization of financial data over time. +- Scalability for adding features like budgeting or reporting. + +--- + +## **5. Multi-User Social Platform** +### Scenario +A social platform requires: +- Managing user profiles and posts. +- Asynchronous fetching of user feeds. +- State persistence for user preferences. + +### Solution +- Use **AsyncStore** for fetching posts and user feeds. +- Manage user profiles with a unified store. +- Persist user settings using **PersistentStore**. +- Combine **MultiStoreProvider** for handling multiple stores effectively. + +### Benefits +- Efficient management of complex states for posts, profiles, and settings. +- Asynchronous support ensures responsive feed updates. +- Persistent user settings enhance the user experience. + +--- + +## **6. Data-Driven Dashboard Application** +### Scenario +A data visualization dashboard requires: +- Displaying charts and metrics based on real-time data. +- Managing filters and user preferences. +- Allowing snapshots of selected filters for sharing and debugging. + +### Solution +- Use **ComputedStore** to compute derived metrics from the raw data. +- Persist filter settings using **PersistentStore**. +- Take snapshots of the current dashboard state with **SnapshotStore**. + +### Benefits +- Simplified computation of derived states. +- Enhanced debugging with state snapshots. +- Modular design for filters and metrics. + +--- + +## **7. Educational Quiz Application** +### Scenario +An educational app for quizzes needs: +- Managing quiz questions and user answers. +- Supporting undo/redo for revisiting answers. +- Providing asynchronous updates for leaderboards. + +### Solution +- Use **UndoableStore** to enable users to revise their answers. +- Fetch leaderboard data with **AsyncStore**. +- Manage quiz data with a dedicated store and reactive UI updates using **StoreProvider**. + +### Benefits +- Improved user experience with undo/redo functionality. +- Real-time leaderboard updates. +- Modular design for quiz and leaderboard management. + +--- + +## **8. Large-Scale Enterprise Application** +### Scenario +An enterprise application requires: +- Scalable state management for multiple modules (e.g., HR, Finance, Inventory). +- Isolated features with dedicated state logic. +- Advanced debugging tools for development. + +### Solution +- Organize state management with the **Feature-First Architecture**. +- Use unified stores to integrate multiple modules efficiently. +- Take advantage of snapshots, undo/redo, and persistence as needed per feature. + +### Benefits +- Highly scalable and modular design. +- Easy debugging with built-in features like snapshots and middleware. +- Simplified management of interconnected modules. + +--- + +## Conclusion +The **upper_flutter_stores** package is versatile and caters to various use cases across industries. Its modular design, robust features, and developer-friendly API make it a powerful tool for managing state in Flutter applications. diff --git a/lib/src/independent/persistent_store.dart b/lib/src/independent/persistent_store.dart index edeee91..7533823 100644 --- a/lib/src/independent/persistent_store.dart +++ b/lib/src/independent/persistent_store.dart @@ -24,6 +24,15 @@ class PersistentStore extends BaseStore { } } + @override + void set(T newState) { + super.set(newState); + notifyListeners(); // Ensure changes in PersistentStore propagate + if (enableDebugging) { + print('PersistentStore [$debugContext]: State updated to: $newState'); + } + } + /// Asynchronously load persisted state Future initialize() async { if (enableDebugging) { @@ -42,6 +51,7 @@ class PersistentStore extends BaseStore { } final prefs = await SharedPreferences.getInstance(); final jsonString = jsonEncode(_toJson(state)); + print('Persisting state: $jsonString'); // Add this log to debug await prefs.setString(_key, jsonString); if (enableDebugging) { print('PersistentStore [$debugContext]: State persisted: $jsonString'); diff --git a/lib/src/store/multi_store_provider.dart b/lib/src/store/multi_store_provider.dart new file mode 100644 index 0000000..78232ab --- /dev/null +++ b/lib/src/store/multi_store_provider.dart @@ -0,0 +1,26 @@ +import 'package:flutter/widgets.dart'; +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +class MultiStoreProvider extends StatelessWidget { + /// A list of typed store definitions, each able to wrap a widget in its StoreProvider. + final List definitions; + + /// The child widget that will ultimately be wrapped by all stores. + final Widget child; + + const MultiStoreProvider({ + Key? key, + required this.definitions, + required this.child, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Widget tree = child; + // Wrap the child in each store's provider, in reverse order + for (final def in definitions.reversed) { + tree = def.wrap(tree); + } + return tree; + } +} diff --git a/lib/src/store/store_consumer.dart b/lib/src/store/store_consumer.dart new file mode 100644 index 0000000..3503595 --- /dev/null +++ b/lib/src/store/store_consumer.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:upper_flutter_stores/src/independent/base_store.dart'; +import 'package:upper_flutter_stores/src/store/store_provider.dart'; + +class StoreConsumer extends StatelessWidget { + final Widget Function(BuildContext context, T store) builder; + + const StoreConsumer({Key? key, required this.builder}) : super(key: key); + + @override + Widget build(BuildContext context) { + final store = StoreProvider.of(context); + return builder(context, store); + } +} diff --git a/lib/src/store/store_definition.dart b/lib/src/store/store_definition.dart new file mode 100644 index 0000000..663f4ad --- /dev/null +++ b/lib/src/store/store_definition.dart @@ -0,0 +1,25 @@ +import 'package:flutter/widgets.dart'; +import 'package:upper_flutter_stores/src/independent/base_store.dart'; +import 'package:upper_flutter_stores/src/store/store_provider.dart'; + +/// Abstract base so we can store different typed definitions in one list. +abstract class BaseStoreDefinition { + /// Wraps the given child widget in a typed StoreProvider. + Widget wrap(Widget child); +} + +/// A concrete definition that holds a typed store +/// and wraps the child with a matching StoreProvider. +class StoreDefinition extends BaseStoreDefinition { + final T store; + + StoreDefinition(this.store); + + @override + Widget wrap(Widget child) { + return StoreProvider( + store: store, + child: child, + ); + } +} diff --git a/lib/src/store/store_interface.dart b/lib/src/store/store_interface.dart new file mode 100644 index 0000000..a9bfa3b --- /dev/null +++ b/lib/src/store/store_interface.dart @@ -0,0 +1,129 @@ +import 'package:upper_flutter_stores/upper_flutter_stores.dart'; + +abstract class StoreInterface extends BaseStore { + final Store store; + bool isInitialized = false; + + StoreInterface( + T initialState, { + bool enableDebugging = false, + String? debugContext, + bool enableUndoRedo = false, + bool enablePersistence = false, + String? persistKey, + T Function(Map)? fromJson, + Map Function(T)? toJson, + bool enableAsync = false, + Future Function()? asyncTask, + bool enableComputed = false, + T Function()? compute, + List>? computedDependencies, + bool enableSnapshots = false, + }) : store = Store( + initialState, + enableDebugging: enableDebugging, + debugContext: debugContext, + enableUndoRedo: enableUndoRedo, + enablePersistence: enablePersistence, + persistKey: persistKey, + fromJson: fromJson, + toJson: toJson, + enableAsync: enableAsync, + asyncTask: asyncTask, + enableComputed: enableComputed, + compute: compute, + computedDependencies: computedDependencies, + enableSnapshots: enableSnapshots, + ), + super(initialState, enableDebugging: enableDebugging); + + @override + T get state => store.state; + + @override + void set(T newState) { + super.set(newState); + store.set(newState); + notifyListeners(); + + if (store.enablePersistence) { + store.persist(); + } + + if (enableDebugging) { + print( + '$runtimeType: State updated and persisted (if enabled): $newState'); + } + } + + Future initializePersistence() async { + if (store.enablePersistence) { + try { + await store.initializePersistence(); + set(store.state); + print('$runtimeType: Persistence initialized.'); + } catch (e) { + print('$runtimeType: Persistence initialization failed - $e'); + } + } + } + + Future persistState() async { + if (store.enablePersistence) { + try { + await store.persist(); + print('$runtimeType: State persisted.'); + } catch (e) { + print('$runtimeType: Failed to persist state - $e'); + } + } + } + + void setupMiddleware() { + store.addMiddleware((oldState, newState) { + print( + '$runtimeType: Middleware: State changed from $oldState to $newState'); + }); + } + + void undo() { + if (store.canUndo) { + store.undo(); + set(store.state); + print('$runtimeType: Undo performed. Current state: ${store.state}'); + } else { + print('$runtimeType: Undo not available.'); + } + } + + void redo() { + if (store.canRedo) { + store.redo(); + set(store.state); + print('$runtimeType: Redo performed. Current state: ${store.state}'); + } else { + print('$runtimeType: Redo not available.'); + } + } + + void takeSnapshot() { + if (store.enableSnapshots) { + store.takeSnapshot(); + set(store.state); + print('$runtimeType: Snapshot taken. Current state: ${store.state}'); + } else { + print('$runtimeType: Snapshots are not enabled.'); + } + } + + void replaySnapshot(int index) { + if (store.enableSnapshots) { + store.replaySnapshot(index); + set(store.state); + print( + '$runtimeType: Snapshot at index $index replayed. Current state: ${store.state}'); + } else { + print('$runtimeType: Snapshots are not enabled.'); + } + } +} diff --git a/lib/src/store/store_provider.dart b/lib/src/store/store_provider.dart new file mode 100644 index 0000000..f30ce3b --- /dev/null +++ b/lib/src/store/store_provider.dart @@ -0,0 +1,24 @@ +import 'package:flutter/widgets.dart'; +import 'package:upper_flutter_stores/src/independent/base_store.dart'; + +class StoreProvider extends InheritedWidget { + final T store; + + const StoreProvider({ + Key? key, + required Widget child, + required this.store, + }) : super(key: key, child: child); + + static T of(BuildContext context) { + final provider = + context.dependOnInheritedWidgetOfExactType>(); + assert(provider != null, 'No StoreProvider found for type $T'); + return provider!.store; + } + + @override + bool updateShouldNotify(covariant StoreProvider oldWidget) { + return oldWidget.store != store; + } +} diff --git a/lib/src/unified/store.dart b/lib/src/unified/store.dart index c84a702..7b10a5e 100644 --- a/lib/src/unified/store.dart +++ b/lib/src/unified/store.dart @@ -14,10 +14,14 @@ class Store extends BaseStore { final SnapshotStore? _snapshotFeature; final List> _middlewares = []; + final bool _enableUndoRedo; + final bool _enablePersistence; + final bool _enableSnapshots; + Store( T initialState, { bool enableDebugging = false, - String? debugContext, // Pass debugging context + String? debugContext, bool enableUndoRedo = false, bool enablePersistence = false, String? persistKey, @@ -29,7 +33,10 @@ class Store extends BaseStore { T Function()? compute, List>? computedDependencies, bool enableSnapshots = false, - }) : _undoableFeature = enableUndoRedo + }) : _enableUndoRedo = enableUndoRedo, + _enablePersistence = enablePersistence, + _enableSnapshots = enableSnapshots, + _undoableFeature = enableUndoRedo ? _initializeUndoableFeature( initialState, enableDebugging: enableDebugging, @@ -91,6 +98,15 @@ class Store extends BaseStore { } } + // Getter to check if undo/redo is enabled + bool get enableUndoRedo => _enableUndoRedo; + + // Getter to check if persistence is enabled + bool get enablePersistence => _enablePersistence; + + // Getter to check if snapshots are enabled + bool get enableSnapshots => _enableSnapshots; + void addMiddleware(Middleware middleware) { _middlewares.add(middleware); if (enableDebugging) { @@ -199,12 +215,17 @@ class Store extends BaseStore { if (_persistentFeature != null) { _persistentFeature!.set(state); } + + // Notify listeners after all features are updated + notifyListeners(); } Future initializePersistence() async { if (_persistentFeature != null) { await _persistentFeature!.initialize(); - super.set(_persistentFeature!.state); + final restoredState = _persistentFeature!.state; + super.set(restoredState); + notifyListeners(); // Ensure the UI and listeners react to restored state } else { throw UnsupportedError('Persistence is not enabled for this Store.'); } diff --git a/lib/upper_flutter_stores.dart b/lib/upper_flutter_stores.dart index bb56b1f..19058a7 100644 --- a/lib/upper_flutter_stores.dart +++ b/lib/upper_flutter_stores.dart @@ -15,3 +15,10 @@ export 'src/middleware/middleware_store.dart'; // Lifecycle Manager export 'src/lifecycle/store_lifecycle_manager.dart'; + +// Store Providers +export 'src/store/store_provider.dart'; +export 'src/store/store_consumer.dart'; +export 'src/store/multi_store_provider.dart'; +export 'src/store/store_interface.dart'; +export 'src/store/store_definition.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index ca2285d..c9c32c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,7 @@ name: upper_flutter_stores description: > - A lightweight, super-friendly, and non-verbose state management solution for Flutter inspired by Svelte stores. - Provides a unified API and modular stores for flexibility, performance, and scalability. -version: 0.1.11 + A lightweight, super-friendly, and non-verbose state management solution for Flutter. +version: 0.1.12 homepage: https://github.com/upperdo/upper_flutter_stores repository: https://github.com/upperdo/upper_flutter_stores issue_tracker: https://github.com/upperdo/upper_flutter_stores/issues diff --git a/test/store/store_provider_test.dart b/test/store/store_provider_test.dart new file mode 100644 index 0000000..a8cb498 --- /dev/null +++ b/test/store/store_provider_test.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:upper_flutter_stores/src/independent/base_store.dart'; +import 'package:upper_flutter_stores/src/store/store_provider.dart'; + +void main() { + group('StoreProvider', () { + testWidgets( + 'provides the store to child widgets', + (WidgetTester tester) async { + final store = BaseStore(0, enableDebugging: true); + + await tester.pumpWidget( + MaterialApp( + home: StoreProvider>( + store: store, + child: Builder( + builder: (context) { + final providedStore = + StoreProvider.of>(context); + return Text( + 'Store Value: ${providedStore.state}', + textDirection: TextDirection.ltr, + ); + }, + ), + ), + ), + ); + + expect(find.text('Store Value: 0'), findsOneWidget); + }, + ); + + testWidgets( + 'updates child widgets when store state changes', + (WidgetTester tester) async { + final store = BaseStore(0, enableDebugging: true); + + // Wrap the StoreProvider with a MaterialApp for context and use Builder to access the provider. + await tester.pumpWidget( + MaterialApp( + home: StoreProvider>( + store: store, + child: Builder( + builder: (context) { + final providedStore = + StoreProvider.of>(context); + return AnimatedBuilder( + animation: providedStore, + builder: (context, _) { + return Text( + 'Store Value: ${providedStore.state}', + textDirection: TextDirection.ltr, + ); + }, + ); + }, + ), + ), + ), + ); + + // Initial value check + expect(find.text('Store Value: 0'), findsOneWidget); + + // Update the store value + store.set(42); + await tester.pumpAndSettle(); // Ensure rebuilds are complete + + // Updated value check + expect(find.text('Store Value: 42'), findsOneWidget); + }, + ); + + testWidgets( + 'throws an assertion error if StoreProvider is not found', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (context) { + expect( + () => StoreProvider.of>(context), + throwsAssertionError, + ); + return const SizedBox(); + }, + ), + ), + ); + }, + ); + }); +} From 21d62a566b25e94226b3af984512c2dc61b2e23e Mon Sep 17 00:00:00 2001 From: Jeremias Nunez Date: Sat, 25 Jan 2025 23:37:47 -0400 Subject: [PATCH 3/5] docs: updated documentation link --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index c9c32c8..538a3e9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ version: 0.1.12 homepage: https://github.com/upperdo/upper_flutter_stores repository: https://github.com/upperdo/upper_flutter_stores issue_tracker: https://github.com/upperdo/upper_flutter_stores/issues -documentation: https://github.com/upperdo/upper_flutter_stores#readme +documentation: https://github.com/upperdo/upper_flutter_stores/blob/main/docs/README.md environment: sdk: ">=2.18.0 <4.0.0" From 3ff2d851cfde1f2f67198337a24b5c456859783f Mon Sep 17 00:00:00 2001 From: Jeremias Nunez Date: Sat, 25 Jan 2025 23:38:42 -0400 Subject: [PATCH 4/5] docs: updated documentation link --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 538a3e9..8323dc3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ version: 0.1.12 homepage: https://github.com/upperdo/upper_flutter_stores repository: https://github.com/upperdo/upper_flutter_stores issue_tracker: https://github.com/upperdo/upper_flutter_stores/issues -documentation: https://github.com/upperdo/upper_flutter_stores/blob/main/docs/README.md +documentation: https://github.com/upperdo/upper_flutter_stores/blob/main/docs environment: sdk: ">=2.18.0 <4.0.0" From 857c3422997772b750dec0cb6f0dba7708c3edf2 Mon Sep 17 00:00:00 2001 From: Jeremias Nunez Date: Sat, 25 Jan 2025 23:41:28 -0400 Subject: [PATCH 5/5] docs: updated titles --- docs/SEPARATED_ASYNC.md | 4 +--- docs/SEPARATED_BASE.md | 4 +--- docs/SEPARATED_PERSISTENT.md | 3 +-- docs/SEPARATED_SNAPSHOTS.md | 4 +--- docs/SEPARATED_UNDOABLE.md | 4 +--- docs/SEPARATED_UNDO_REDO.md | 4 +--- docs/SEPERATED_COMPUTED.md | 4 +--- docs/SEPERATED_OVERVIEW.md | 2 -- docs/UNIFIED_PERSISTENT.md | 4 +--- docs/UNIFIED_SNAPSHOTS.md | 4 +--- docs/UNIFIED_STORE.md | 4 +--- 11 files changed, 10 insertions(+), 31 deletions(-) diff --git a/docs/SEPARATED_ASYNC.md b/docs/SEPARATED_ASYNC.md index 5b42b60..ee9d95c 100644 --- a/docs/SEPARATED_ASYNC.md +++ b/docs/SEPARATED_ASYNC.md @@ -1,6 +1,4 @@ -# Documentation: **upper_flutter_stores** - -### AsyncStore +# AsyncStore The **AsyncStore** provides built-in support for managing asynchronous tasks while maintaining state consistency. This store is useful for operations such as fetching data from a remote API or performing long-running calculations. diff --git a/docs/SEPARATED_BASE.md b/docs/SEPARATED_BASE.md index e298cb6..6febf9e 100644 --- a/docs/SEPARATED_BASE.md +++ b/docs/SEPARATED_BASE.md @@ -1,6 +1,4 @@ -# Documentation: **upper_flutter_stores** - -### BaseStore +# BaseStore The `BaseStore` is the core store implementation in the `upper_flutter_stores` package. It provides essential state management functionality and serves as the foundation for all other store types. It is lightweight, efficient, and designed to be extended or used directly in applications. diff --git a/docs/SEPARATED_PERSISTENT.md b/docs/SEPARATED_PERSISTENT.md index 2cf6d2f..520ccb2 100644 --- a/docs/SEPARATED_PERSISTENT.md +++ b/docs/SEPARATED_PERSISTENT.md @@ -1,5 +1,4 @@ -# Documentation: **upper_flutter_stores** -### PersistentStore +# PersistentStore The `PersistentStore` is a specialized store that extends `BaseStore` to provide state persistence. It ensures that the store's state is saved and restored across app sessions, using mechanisms like `SharedPreferences` or other persistence solutions. diff --git a/docs/SEPARATED_SNAPSHOTS.md b/docs/SEPARATED_SNAPSHOTS.md index af1f91e..5427bce 100644 --- a/docs/SEPARATED_SNAPSHOTS.md +++ b/docs/SEPARATED_SNAPSHOTS.md @@ -1,6 +1,4 @@ -# Documentation: **upper_flutter_stores** - -### SnapshotStore +# SnapshotStore The `SnapshotStore` is a powerful extension of `BaseStore` that allows you to take snapshots of the store's state at any point in time and replay those snapshots when needed. This feature is useful for implementing time-travel debugging, undo/redo functionality, or for maintaining different application states during development or testing. diff --git a/docs/SEPARATED_UNDOABLE.md b/docs/SEPARATED_UNDOABLE.md index d44494e..dcfd5b5 100644 --- a/docs/SEPARATED_UNDOABLE.md +++ b/docs/SEPARATED_UNDOABLE.md @@ -1,6 +1,4 @@ -# Documentation: **upper_flutter_stores** - -## UndoableStore +# UndoableStore The `UndoableStore` is a specialized store designed to handle undo and redo functionality. It allows you to revert to a previous state or reapply a reverted change. This is particularly useful in applications where users may need to undo actions or retry actions they've undone. diff --git a/docs/SEPARATED_UNDO_REDO.md b/docs/SEPARATED_UNDO_REDO.md index d6efe76..1a10576 100644 --- a/docs/SEPARATED_UNDO_REDO.md +++ b/docs/SEPARATED_UNDO_REDO.md @@ -1,6 +1,4 @@ -# Documentation: **upper_flutter_stores** - -## Undo/Redo Functionality +# Undo/Redo Functionality The **undo/redo** functionality in `upper_flutter_stores` allows you to manage state changes in a reversible manner. This feature is particularly useful for applications that require the ability to revert or redo changes, such as text editors, form inputs, or task managers. diff --git a/docs/SEPERATED_COMPUTED.md b/docs/SEPERATED_COMPUTED.md index fa66e59..c26ea1e 100644 --- a/docs/SEPERATED_COMPUTED.md +++ b/docs/SEPERATED_COMPUTED.md @@ -1,6 +1,4 @@ -# Documentation: **upper_flutter_stores** - -### ComputedStore +# ComputedStore The `ComputedStore` enables reactive computations based on other stores' states. This is useful for deriving new state values that depend on one or more independent stores, ensuring the computed state updates automatically whenever the dependencies change. diff --git a/docs/SEPERATED_OVERVIEW.md b/docs/SEPERATED_OVERVIEW.md index bcfeaf4..02cc399 100644 --- a/docs/SEPERATED_OVERVIEW.md +++ b/docs/SEPERATED_OVERVIEW.md @@ -1,5 +1,3 @@ -# Documentation: **upper_flutter_stores** - # Separated Store Overview **Separated Stores** in the `upper_flutter_stores` package provide a modular and specialized approach to store management. These stores focus on individual features or functionalities, offering advanced capabilities tailored to specific needs. diff --git a/docs/UNIFIED_PERSISTENT.md b/docs/UNIFIED_PERSISTENT.md index 800a22f..580c4fb 100644 --- a/docs/UNIFIED_PERSISTENT.md +++ b/docs/UNIFIED_PERSISTENT.md @@ -1,6 +1,4 @@ -# Documentation: **upper_flutter_stores** - -### Persistence +# Persistence The **`upper_flutter_stores`** package allows you to persist state effortlessly using the built-in **PersistentStore** feature. This functionality enables applications to maintain state across app restarts or page reloads. diff --git a/docs/UNIFIED_SNAPSHOTS.md b/docs/UNIFIED_SNAPSHOTS.md index a198586..0432f8e 100644 --- a/docs/UNIFIED_SNAPSHOTS.md +++ b/docs/UNIFIED_SNAPSHOTS.md @@ -1,6 +1,4 @@ -# Documentation: **upper_flutter_stores** - -## Snapshots and Replay +# Snapshots and Replay The `upper_flutter_stores` package provides a **snapshot** and **replay** mechanism, which allows you to take snapshots of your store's state and replay them later. This feature is particularly useful for debugging and state management in complex applications. diff --git a/docs/UNIFIED_STORE.md b/docs/UNIFIED_STORE.md index 4ef4374..8601260 100644 --- a/docs/UNIFIED_STORE.md +++ b/docs/UNIFIED_STORE.md @@ -1,6 +1,4 @@ -# Documentation: **upper_flutter_stores** - -## Unified Store Implementation +# Unified Store Implementation The **Unified Store** allows you to combine various store functionalities (e.g., undoable, persistent, async) into a single, cohesive store.