Aus öffentlichen Paketdaten erstellte Orientierung.
Loon ist ein reaktiver Sammlungsdatenspeicher für Dart- und Flutter-Anwendungen, der persistenten Speicher mit NoSQL- und SQL-Funktionen bietet. Er ermöglicht Entwicklern die reaktive Verwaltung von Datensammlungen, wobei die Benutzeroberfläche automatisch aktualisiert wird, wenn sich die Daten ändern. Für den plattformübergreifenden Einsatz entwickelt, unterstützt Loon Android, iOS, Web und macOS und ist ideal für Apps mit Offline-first-Funktionalität und Echtzeit-Daten-Synchronisation. Sein leichtgewichtiges Design lässt sich nahtlos in Flutter-Arbeitsabläufe integrieren.
Anwendungsfälle
Offline-first-Mobile-Anwendungen
Echtzeit-Daten-Synchronisierung
Plattformübergreifende Datenpersistenz
Dynamische Benutzeroberflächen mit reaktiven Daten
Lokale Datenverwaltung in Web-Anwendungen
Hauptmerkmale
reaktive Datenbindung
Persistenter NoSQL- und SQL-Speicher
Plattformübergreifende Kompatibilität
Automatische UI-Updates bei Datenänderung
Leichtgewichtig und einfache Integration
Geeignet für
Flutter-Entwickler, die lokale Datenpersistenz suchen
Apps, die Offline-Funktionen erfordern
Projekte, die reaktive Zustandsverwaltung benötigen
Teams, die Multiplattform-Apps entwickeln
Entwickler, die einfache aber leistungsstarke Datenspeicher wollen
Hinweise
Kann Lernkurve für SQL-Funktionen erfordern
Eingeschränkte Dokumentation im Vergleich zu etablierten Alternativen
Die Leistung hängt von der Datenmenge und Komplexität ab
Nicht geeignet für große Server-Datenbanken
Am besten geeignet für Client-seitige Daten anstelle von Backend-Diensten
Original-README
Originaler README-Schnappschuss aus dem verknüpften Quell-Repository. Aktuelle Inhalte finden Sie im Repository.
Loon
Android
iOS
Web
macOS
✅
✅
✅
✅
Loon is a reactive document data store for Flutter.
Features
Synchronous reading, writing and querying of documents.
Streaming of changes to documents and queries.
Out of the box persistence and encryption.
Install
flutter pub add loon
➕ Creating documents
Loon makes it easy to work with collections of documents.
If persistence is enabled, then a typed collection needs to specify a fromJson/toJson serialization pair. In order to avoid having to specify types or serializers whenever a collection is accessed, it can be helpful to store the collection in a variable or as an index on the data model:
You can then use Flutter's built-in StreamBuilder or the library's DocumentStreamBuilder widget to access data from widgets:
classMyWidgetextendsStatelessWidget{
@overridebuild(context) {
returnDocumentStreamBuilder(
doc: UserModel.store.doc('1'),
builder: (context, snap) {
final user = snap?.data;
if (user == null) {
returnText('Missing user');
}
returnText('Found user ${user.name}');
}
)
}
}
𖢞 Subcollections
Documents can be nested under subcollections. Documents in subcollections are uniquely identified by the path to their collection and
their document ID.
final friendsCollection = UserModel.store.doc('1').subcollection('friends');
friendsCollection.doc('2').create(UserModel(name: 'Jack', age: 17));
friendsCollection.doc('3').create(UserModel(name: 'Brenda', age: 40));
friendsCollection.doc('4').create(UserModel(name: 'Bill', age: 70));
final snaps = friendsCollection.get();
for (final snap in snaps) {
print("${snap.data.name}: ${snap.path}");
// Jack: users__1__friends__2// Brenda: users__1__friends__3// Bill: users__1__friends__4
}
🔎 Queries
Documents can be filtered using queries:
final snapshots = friendsCollection.where((snap) => snap.data.name.startsWith('B')).get();
for (final snap in snapshots) {
print(snap.data.name);
// Brenda// Bill
}
Queries can also be streamed, optionally using the QueryStreamBuilder:
Deleting a document removes it and all of its subcollections from the store.
UserModel.store.doc('1').delete();
🌊 Streaming changes
Documents and queries can be streamed for changes which provides the previous and current document data as well as the event type of the change:
UserModel.store.streamChanges().listen((changes) {
for (final changeSnap in changes) {
switch(changeSnap.event) {
caseBroadcastEvents.added:
print('New document ${changeSnap.id} was added to the collection.');
break;
caseBroadcastEvents.modified:
print('The document ${changeSnap.id} was modified from ${changeSnap.prevData} to ${changeSnap.data}.');
break;
caseBroadcastEvents.removed:
print('${changeSnap.id} was removed from the collection.');
break;
caseBroadcastEvents.hydrated:
print('${changeSnap.id} was hydrated from the persisted data.');
break;
}
}
});
🔁 Data Dependencies
Data relationships in the store can be established using the data dependencies builder.
In this example, whenever a post's associated user is updated, the post will also be rebroadcast to its active listeners.
Additionally, whenever a document is updated, it will rebuild its set of dependencies, allowing documents to support dynamic dependencies
that can change in response to updated document data.
🪴 Root collection
Not all documents necessarily make sense to be grouped together under any particular collection. In this scenario, any one-off documents can be stored
on the root collection:
Loon.doc('current_user_id').create('1');
🗄️ Data Persistence
Persistence is supported on both web and native platforms. It works out of the box and can be configured on app start.
Native platforms (iOS, Android, macOS) use a default file-based persistence implementation, while web persists data to IndexedDB.
The currently available persistence options are broken down by platform:
Native
SqlitePersistor: A SQLite persistence implementation using sqflite. Documents are distributed in rows based on their persistence configuration.
FilePersistor: A file-based persistence implementation for native platforms. Documents are stored in one or more files based on the persistence configuration.
Web
IndexedDBPersistor: The default persistence implementation for web platforms. Documents are stored in IndexedDB, a low-level API for web-based client-side storage.
Note: Encryption on web is experimental through flutter_secure_storage. It is important to enable the correct headers in order to ensure security of encryption on web.
⚙️ Configuration
Specify your preferred persistor on startup or use Persistor.current() to dynamically select the default persistence implementation for the current platform (FilePersistor on Android/iOS, IndexedDBPersistor on web).
No persisted data is available on startup until it is manually requested by calling hydrate.
The call to hydrate returns a Future that resolves when the data has been hydrated from the persistence layer. By default, calling hydrate() will hydrate all persisted data. If only certain data should be hydrated, then it can be called with a list of documents and collections to hydrate. All subcollections of the specified paths are also hydrated.
Persistence options can be specified both globally as well as on a per-collection basis.
// main.dartvoidmain() {
// Globally enable encryption.Loon.configure(persistor: FilePersistor(settings: PersistorSettings(encrypted: true));
Loon.hydrate().then(() {
print('Hydration complete');
});
}
// models/user.dartclassUserModel{
finalString name;
finalint age;
UserModel({
requiredthis.name,
requiredthis.age,
});
staticCollection<UserModel> get store {
returnLoon.collection(
'users',
fromJson: UserModel.fromJson,
toJson: (user) => user.toJson(),
// Disable encryption specifically for this collection and its subcollections.
settings: PersistorSettings(encrypted: false),
)
}
}
In this example, file encryption is enabled globally for all collections, but disabled
specifically for the users collection and its subcollections in the store.
By default all data in a single __store__.json persistence file.
loon>
__store__.json
If data needs to be persisted differently, either by merging data across collections into a single store or by breaking down a collection
into multiple stores, then a custom persistence key can be specified on the collection:
Now instead of storing all users in the users.json file, they will be distributed across multiple files based on the user's age. The key is recalculated
whenever a document's data changes and if its associated key is updated, then the document is moved from its previous file to its updated location.
🎨 Custom persistence
If you would prefer to persist data using an alternative implementation than the default persistors, you can implement the persistence interface:
import'package:loon/loon.dart';
classMyPersistorextendsPersistor{
/// Initialization function called when the persistor is instantiated to execute and setup work.Future<void> init();
/// Persist function called with the bath of documents that have changed (including been deleted) within the last throttle window/// specified by the [Persistor.persistenceThrottle] duration.Future<void> persist(Set<Document> docs);
/// Hydration function called to read data from persistence. If no references are specified,/// then it hydrates all persisted data. if refs are specified, it hydrates only the data at/// and under those paths.Future<Json> hydrate([Set<StoreReference>? refs]);
/// Clear function used to clear all documents under the given store paths.Future<void> clear(Set<StoreReference> refs);
/// Clears all documents and removes all persisted data.Future<void> clearAll();
}