Saverly
Saverly is a Dart library designed to simplify the process of saving and loading key-value pairs persistently in Flutter applications. It provides a reusable mixin to add JSON-based serialization and persistence capabilities to any class.
Features
• Persistent Storage: Save and load data effortlessly using JSON files backed by shared_preferences.
• Automatic File Name Handling: Each class gets a default file name based on its type.
• JSON Serialization: Convert objects to/from JSON seamlessly.
• Reactive State Management: Supports ValueNotifier for automatic save operations when values change.
• UI Integration: Works well with Flutter’s reactive widgets like ValueListenableBuilder.
How to Use
Step 1: Mix in Saverly
To use Saverly, mix it into your class and implement the required methods:
• toJson: Convert the class to a JSON Map.
• copyFromJson: Populate the class’s fields from a JSON Map.
Step 2: Use ValueNotifier for Reactive State Management
Declare your class properties as ValueNotifier to enable reactive state changes and automatic saving.
Example: Managing User Preferences
Here’s an example demonstrating how to use Saverly with ValueNotifier for automatic save operations:
Define the Class
import 'package:flutter/foundation.dart';
import 'package:saverly/saverly.dart';
class UserPreferences with Saverly {
/// Use ValueNotifier for reactive updates
late final ValueNotifier<String?> languageNotifier =
ValueNotifier<String?>(null)..addListener(save);
late final ValueNotifier<bool?> isDarkModeNotifier =
ValueNotifier<bool?>(null)..addListener(save);
UserPreferences() {
load();
}
@override
Map<String, dynamic> toJson() => {
'language': languageNotifier.value,
'isDarkMode': isDarkModeNotifier.value,
};
@override
void copyFromJson(Map<String, dynamic> data) {
// Update ValueNotifiers when data is loaded
languageNotifier.value = data['language'] as String?;
isDarkModeNotifier.value = data['isDarkMode'] as bool?;
}
}
Use the Class
void main() async {
final prefs = UserPreferences();
print('Initial Preferences:');
print('Language: ${prefs.languageNotifier.value}');
print('Dark Mode: ${prefs.isDarkModeNotifier.value}');
// Update preferences
prefs.languageNotifier.value = 'en'; // Automatically saves
prefs.isDarkModeNotifier.value = true; // Automatically saves
print('Preferences updated and saved!');
}
File Name
The default file name for each class is derived from the class name in uppercase, followed by .json. For example:
• A class UserPreferences will have the file name USERPREFERENCES.json.
If you want to use a custom file name, override the fileName property in your class:
@override
String get fileName => 'custom_prefs.json';
UI Integration Example
import 'package:flutter/material.dart';
class SettingsScreen extends StatelessWidget {
final UserPreferences prefs;
SettingsScreen(this.prefs);
@override
Widget build(BuildContext context) {
return Column(
children: [
// Language Dropdown
ValueListenableBuilder<String?>(
valueListenable: prefs.languageNotifier,
builder: (context, language, _) {
return DropdownButton<String>(
value: language,
items: ['en', 'es', 'fr'].map((lang) {
return DropdownMenuItem(value: lang, child: Text(lang));
}).toList(),
onChanged: (newLang) => prefs.languageNotifier.value = newLang,
);
},
),
// Dark Mode Switch
ValueListenableBuilder<bool?>(
valueListenable: prefs.isDarkModeNotifier,
builder: (context, isDarkMode, _) {
return SwitchListTile(
title: Text('Dark Mode'),
value: isDarkMode ?? false,
onChanged: (value) => prefs.isDarkModeNotifier.value = value,
);
},
),
],
);
}
}
Best Practices
- Avoid Blocking the UI:
Since load and save are asynchronous, always use await to avoid blocking the UI.
- Use Reactive Programming:
Bind ValueNotifier to widgets for real-time updates.
- Secure Sensitive Data:
If you’re storing sensitive information, consider encrypting the JSON before saving it.
Contributions
We welcome contributions to improve Saverly! Feel free to submit issues or pull requests on the GitHub repository.
This README provides a complete overview of the library and its usage. Let me know if you’d like any further customizations!