Added ServiceList

This commit is contained in:
Tobe O 2017-12-10 00:13:31 -05:00
parent b30e615a7d
commit cd199f2c8f
5 changed files with 151 additions and 1 deletions

2
CHANGELOG.md Normal file
View file

@ -0,0 +1,2 @@
# 1.1.0+1
Added `ServiceList`.

View file

@ -89,3 +89,18 @@ Logout:
```dart
await app.logout();
```
# Live Updates
Oftentimes, you will want to update a collection based on updates from a service.
Use `ServiceList` for this case:
```dart
build(BuildContext context) async {
var list = new ServiceList(app.service('api/todos'));
return new StreamBuilder(
stream: list.onChange,
builder: _yourBuildFunction,
);
}
```

View file

@ -3,6 +3,7 @@ library angel_client;
import 'dart:async';
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:http/src/response.dart' as http;
export 'package:angel_http_exception/angel_http_exception.dart';
@ -132,3 +133,80 @@ abstract class Service {
/// Removes the given resource.
Future remove(id, [Map params]);
}
/// A [List] that automatically updates itself whenever the referenced [service] fires an event.
class ServiceList extends DelegatingList {
/// A field name used to compare [Map] by ID.
final String idField;
/// If `true` (default: `false`), then `index` events will be handled as a [Map] containing a `data` field.
///
/// See https://github.com/angel-dart/paginate.
final bool asPaginated;
/// A function used to compare two items for equality.
final bool Function(dynamic, dynamic) compare;
final Service service;
final StreamController<ServiceList> _onChange = new StreamController();
final List<StreamSubscription> _subs = [];
ServiceList(this.service, {this.idField, this.asPaginated: false, this.compare}) : super([]) {
// Index
_subs.add(service.onIndexed.listen((data) {
var items = asPaginated == true ? data['data'] : data;
this..clear()..addAll(items);
}));
// Created
_subs.add(service.onCreated.listen((item) {
add(item);
_onChange.add(this);
}));
// Modified/Updated
handleModified(item) {
var indices = <int>[];
for (int i = 0; i < length; i++) {
if (compareItems(item, this[i]))
indices.add(i);
}
if (indices.isNotEmpty) {
for (var i in indices)
this[i] = item;
_onChange.add(this);
}
}
_subs.addAll([
service.onModified.listen(handleModified),
service.onUpdated.listen(handleModified),
]);
// Removed
_subs.add(service.onRemoved.listen((item) {
removeWhere((x) => compareItems(item, x));
_onChange.add(this);
}));
}
/// Fires whenever the underlying [service] fires a change event.
Stream<ServiceList> get onChange => _onChange.stream;
Future close() async {
_onChange.close();
}
bool compareItems(a, b) {
if (compare != null)
return compare(a, b);
if (a is Map)
return a[idField ?? 'id'] == b[idField ?? 'id'];
else
return a == b;
}
}

View file

@ -1,5 +1,5 @@
name: angel_client
version: 1.1.0
version: 1.1.0+1
description: Client library for the Angel framework.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_client
@ -7,6 +7,7 @@ environment:
sdk: ">=1.21.0"
dependencies:
angel_http_exception: ^1.0.0
collection: ^1.0.0
http: ">= 0.11.3 < 0.12.0"
json_god: ">=2.0.0-beta <3.0.0"
merge_map: ">=1.0.0 <2.0.0"

54
test/list_test.dart Normal file
View file

@ -0,0 +1,54 @@
import 'package:async/async.dart';
import 'dart:io';
import 'package:angel_client/io.dart' as c;
import 'package:angel_framework/angel_framework.dart' as s;
import 'package:test/test.dart';
main() {
HttpServer server;
c.Angel app;
c.ServiceList list;
StreamQueue queue;
setUp(() async {
var serverApp = new s.Angel();
serverApp.use('/api/todos', new s.MapService(autoIdAndDateFields: false));
server = await serverApp.startServer();
var uri = 'http://${server.address.address}:${server.port}';
app = new c.Rest(uri);
list = new c.ServiceList(app.service('api/todos'));
queue = new StreamQueue(list.onChange);
});
tearDown(() async {
await server.close(force: true);
await list.close();
await list.service.close();
await app.close();
});
test('listens on create', () async {
list.service.create({'foo': 'bar'});
await list.onChange.first;
expect(list, [{'foo': 'bar'}]);
});
test('listens on modify', () async {
list.service.create({'id': 1, 'foo': 'bar'});
await queue.next;
await list.service.update(1, {'id': 1, 'bar': 'baz'});
await queue.next;
expect(list, [{'id': 1, 'bar': 'baz'}]);
});
test('listens on remove', () async {
list.service.create({'id': '1', 'foo': 'bar'});
await queue.next;
await list.service.remove('1');
await queue.next;
expect(list, isEmpty);
});
}