Added ServiceList
This commit is contained in:
parent
b30e615a7d
commit
cd199f2c8f
5 changed files with 151 additions and 1 deletions
2
CHANGELOG.md
Normal file
2
CHANGELOG.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# 1.1.0+1
|
||||
Added `ServiceList`.
|
15
README.md
15
README.md
|
@ -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,
|
||||
);
|
||||
}
|
||||
```
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
54
test/list_test.dart
Normal 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);
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue