Enjoyable & customizable offline-first REST API client

Pub Version GitHub

Unruffled is lightweight and customizable http client that allows you to create offline-first easily.

Features

  • 🧘 Stay safe by automatically managing offline storage & connectivity errors
  • 🚀 Very customizable
  • 💻 All platforms supported
  • 🤖 Intuitive & simple API
  • 🔒 Strong encryption with Hive

Getting Started

Check out the Quick Start documentation to get started.

Quick start

1. Add to pubspec.yaml

dependencies:
  json_annotation: ^4.5.0
  unruffled: ^1.0.0

dev_dependencies:
  build_runner: any
  json_serializable: ^6.2.0
  unruffled_generator: ^1.0.0

NOTE : Unruffled relies to json_annotation and json_serializable to work as expected, please ensure to add theme in your dependencies.

2. Declare models

Declare models used by your remote service and generate Unruffled adapters.

@UnruffledData()
@JsonSerializable()
class User extends DataModel<User> {
  @Id()
  int? id;
  
  String name;
  
  String surname;
  
  int age;

  User({super.unruffledKey, this.id, required this.name, required this.surname, required this.age});
}

Build your flutter directory to generate a UserRepository()

flutter pub run build_runner build

NOTE: Your class must call super.unruffledKey

  • unruffledKey refers to your local object ID generated by unruffled

3. Register adapters

For all platforms except web, use path_provider to generate a directory path.

final dir = await getApplicationSupportDirectory(); // path_provider package
var unruffled = Unruffled(
      baseDirectory: dir,
      defaultBaseUrl: 'http://example.com',
      dio: dio,
  )
  .registerRepository(UserRepository());

4. Initialize Unruffled

Before using Unruffled, ensure to initialize it.

await unruffled.init();

🚀 That's it, you're ready to go !

Usage

1. Create a user

Define a user and call User repository.

final testUser = User(name: 'John', surname: 'Doe');
final repository = unruffled.repository<User>();

Create your user by calling post() method.

var user = await repository.post(model: testUser);

2. Get a user

var user = await repository.get(key: user.key);

3. Delete a user

var user = await repository.delete(key: user.key);

4. Query users

QueryBuilder lets you apply filters on your queries.

Filters will be applied automatically on local queries.
To enable filters on remote queries, you must implement following method :

Note : For a complete working example, check unruffled_feathersjs

mixin CustomRemoteRepository<T extends DataModel<T>> on RemoteRepository<T> {
  @override
  Map<String, dynamic> parseLimit(int limit) => {};

  @override
  Map<String, dynamic> parsePage(int page) => {};

  @override
  Map<String, dynamic> parseSort(SortCondition<R> sort) => {};
  
  @override
  Map<String, dynamic> parseEqual(FilterCondition<T> condition) => {};

  @override
  Map<String, dynamic> parseNotEqual(FilterCondition<T> condition) => {};

  @override
  Map<String, dynamic> parseGreaterThan(FilterCondition<T> condition) => {};

  @override
  Map<String, dynamic> parseLessThan(FilterCondition<T> condition) => {};

  @override
  Map<String, dynamic> parseInValues(FilterCondition<T> condition) => {};

  @override
  Map<String, dynamic> parseOrCondition(List<FilterOperation<T>> operations) => {};

  @override
  Map<String, dynamic> parseAndCondition(List<FilterOperation<T>> operations) => {};
}

QueryBuilder usage :

List<User> users = await repository.getAll(
  queryBuilder: QueryBuilder(
    filterGroup: FilterGroup.or(
      filters: [
        FilterCondition.equal(property: UserField.surname(), value: 'Doe'),
        FilterCondition.greaterThan(property: UserField.age(), value: 18, include: true),
      ],
    ),
    limit: 10,
    page: 1,
    sort: SortCondition(property: UserField.age(), sort: SortType.desc),
  ),
);

5. Manage connectivity isues

If connectivity errors occur, Unruffled stores automatically requests to retry them later, just call the global method :

final operations = unruffled.offlineOperations;
for (var remoteRepository in operations.keys) {
  await operations[remoteRepository].retry(remoteRepository);
}

Or call for each remote repository :

final operations = repository.offlineOperations;
for (var operation in operations) {
  await operation.retry(repository);
}

Libraries

unruffled
Support for doing something awesome.