loon 0.0.1 copy "loon: ^0.0.1" to clipboard
loon: ^0.0.1 copied to clipboard

Loon is a reactive, noSQL data store for Dart & Flutter.

Loon #



Loon is a reactive, noSQL data store for Dart & Flutter.

Note: Still early days, give it a try on hobbyist projects for now.

Features #

  • Synchronous reading, writing and querying of documents.
  • Streaming of changes to documents and queries.
  • Built-in file persistence with options for per-collection encryption and sharding.

➕ Creating documents #

Loon is based around collections of documents.

import 'package:loon/loon.dart';

Loon.collection('reviews').doc('The Book of Boba Fett').create({
  'rating': 3/10,
  'review': "A largely disappointing series that played it too safe and didn't live up to fan expectations for the character.",
});

Documents are stored under collections in a map structure that allows for synchronous reading/writing. To get type safety, you can define your own classes to represent collections and implement a serializer:

import 'package:loon/loon.dart';
import './models/reviews.dart';

Loon.collection<ReviewModel>(
  'reviews',
  fromJson: (Json json) => ReviewModel.fromJson(json),
  toJson: (review) => review.toJson(),
).doc('Obi-Wan Kenobi').create(
  ReviewModel(
    rating: 6/10,
    review: "The show had its moments, but they really should have de-aged Anakin.",
  )
);

To get reusable type safety and serialization, it can be helpful to define a collection index on your model:

import 'package:loon/loon.dart';

class ReviewModel {
  final double rating;
  final String review;

  ReviewModel({required this.rating, required this.review});

  static Collection<ReviewModel> get store {
    return Loon.collection<ReviewModel>(
      'reviews',
      fromJson: (Json json) => ReviewModel.fromJson(json),
      toJson: (review) => review.toJson(),
    )
  }
}

You can then read and write documents in your collection using the index:

import './models/reviews.dart';

ReviewModel.store.doc('Andor').create(
  ReviewModel(
    rating: 8/10,
    review: "Definitely a quality jump from Boba and Obi-Wan. Great writing, expect less action and more intrigue.",
  )
);

📚 Reading documents #

import './models/reviews.dart';

final snap = ReviewModel.store.doc('The Book of Boba Fett').get();

if (snap != null && snap.data.rating > 8/10) {
  print('Great show') // Unreachable
}

Reading documents returns a DocumentSnapshot? which exposes your document's data and ID:

print(snap.id) // Book of Boba Fett
print(snap.data) // ReviewModel(...)

To watch for changes to a document, you read it as a stream:

import './models/reviews.dart';

ReviewModel.store.doc('Obi-Wan').stream().listen((snap) {});

You can then use Flutter's built-in StreamBuilder or the provided DocumentStreamBuilder widget to then stream updates to documents data in your UI:

import './models/reviews.dart';
import 'package:loon/loon.dart';

class MyWidget extends StatelessWidget {
  @override
  build(context) {
    return DocumentStreamBuilder<ReviewModel>(
      doc: ReviewModel.store.doc('Andor'),
      builder: (context, snap) {
        final rating = snap.data.rating;

        return Text('A pretty good show, just look at the rating: ${rating}');
      }
    )
  }
}

You can read multiple documents using queries:

import './models/reviews.dart';

final snapshots = ReviewModel.store.where((snap) => snap.data.rating >= 5/10).get();
for (final snap in snapshots) {
  print(snap.id);
  // Obi-Wan
  // Andor
}

You can stream queries just like documents, with an option to use the QueryStreamBuilder:

import 'package:loon/loon.dart';
import './models/reviews.dart';

class MyWidget extends StatelessWidget {
  @override
  build(context) {
    return QueryStreamBuilder<ReviewModel>(
      doc: ReviewModel.store.where((snap) => snap.data.rating >= 5/10),
      builder: (context, snapshots) {
        return ListView.builder(
          itemCount: snapshots.length,
          builder: (context, snap) {
            return Text('I gave ${snap.id} a ${snap.data.rating}.');
          }
        )
      }
    )
  }
}

✏️ Updating documents #

Assuming our model has a copyWith function, we can then perform updates to documents like this:

import './models/reviews.dart';

final doc = ReviewModel.doc('The Book of Boba Fett');
final review = doc.get();

doc.update(
  review.copyWith(
    rating: 4/10,
    review: "If I take the Mando episodes out it actually feels even lower.",
  ),
);

If we don't want to read the document first, we can use the modify API:

import './models/reviews.dart';

ReviewModel.doc('The Book of Boba Fett').modify((review) {
  return review.copyWith(
    rating: 3/10,
    review: "They really did my boy dirty",
  );
})

❌ Deleting documents #

Short and sweet, just call delete:

import './models/reviews.dart';

ReviewModel.doc('The Book of Boba Fett').delete(); // Good riddance.

Persisting Data #

The library comes with two persistence options out of the box:

FilePersistor and EncryptedFilePersistor.

You can specify which one you want to use by default for all collections like this:

import 'package:loon/loon.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  Loon.configure(persistor: FilePersistor());

  runApp(const MyApp());
}

When changes occur to documents in the app, they are batched and written to a file per collection. In the case of the reviews collection, it would be: loon_reviews.json.

You can specify the frequency that batch updates should be written:

import 'package:loon/loon.dart';

FilePersistor(
  persistenceThrottle: Duration(milliseconds: 200),
)

as well as specify custom options per collection, like sharding documents:

import 'package:loon/loon.dart';

class ReviewModel {
  final double rating;
  final String review;

  ReviewModel({required this.rating, required this.review});

  static Collection<ReviewModel> get store {
    return Loon.collection<ReviewModel>(
      'reviews',
      persistorSettings: FilePersistorSettings(
        shardFn: (doc) {
          final snap = doc.get();
          final rating = snap.data.rating ?? 0;

          return rating >= 6/10 ? 'good' : 'bad';
        },
      ),
      fromJson: (Json json) => ReviewModel.fromJson(json),
      toJson: (review) => review.toJson(),
    )
  }
}

In this example, the documents in our reviews collection would be spread across multiple files:

  • reviews_good.json:
    • Obi-Wan
    • Andor
  • reviews_bad.json:
    • The Book of Boba Fett

If some of your collections contain sensitive data, you can choose to encrypt them by using the EncryptedFilePersistor instead (no web support yet), either globally:

import 'package:loon/loon.dart';

import 'package:loon/loon.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  Loon.configure(persistor: EncryptedFilePersistor());

  runApp(const MyApp());
}

or on a per-collection basis:

import 'package:loon/loon.dart';

class ReviewModel {
  final double rating;
  final String review;

  ReviewModel({required this.rating, required this.review});

  static Collection<ReviewModel> get store {
    return Loon.collection<ReviewModel>(
      'reviews',
      persistorSettings: EncryptedFilePersistorSettings(
        isEncrypted: true,
      ),
      fromJson: (Json json) => ReviewModel.fromJson(json),
      toJson: (review) => review.toJson(),
    )
  }
}

Encrypted files are stored similarly to default file persistence, in this case it would be: loon_reviews.encrypted.json.

Custom persistence #

If you don't want to use the provided persistence options, it's pretty straightforward to use your own, just implement the persistence interface:

import 'package:loon/loon.dart';

typedef DocumentDataStore = Map<String, Json>;
typedef CollectionDataStore = Map<String, DocumentDataStore>;

class MyPersistor extends Persistor {
  Future<void> persist(List<BroadcastDocument> docs);

  Future<CollectionDataStore> hydrate();

  Future<void> clear(String collection);
}

The base Persistor class implements batching and throttling, so you can just choose your storage mechanism and format.

Loon time coming #

I've been wanting to play around with building a data store library for a while, incorporating some reflections from working with web libraries like Redux, ApolloClient and Flutter libraries like cloud_firestore (the collection/document pattern most notably).

The library is really new and I'm still thinking about the streaming and persistence models so feel free to give feedback.

Happy coding!

14
likes
0
pub points
42%
popularity

Publisher

unverified uploader

Loon is a reactive, noSQL data store for Dart & Flutter.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

encrypt, flutter, flutter_secure_storage, path, path_provider

More

Packages that depend on loon