diff --git a/packages/neon_framework/lib/src/utils/request_manager.dart b/packages/neon_framework/lib/src/utils/request_manager.dart index df72b9c0a18..95006646c6d 100644 --- a/packages/neon_framework/lib/src/utils/request_manager.dart +++ b/packages/neon_framework/lib/src/utils/request_manager.dart @@ -11,6 +11,7 @@ import 'package:neon_framework/src/bloc/result.dart'; import 'package:neon_framework/storage.dart'; import 'package:neon_http_client/neon_http_client.dart'; import 'package:nextcloud/utils.dart'; +import 'package:queue/queue.dart'; import 'package:rxdart/rxdart.dart'; import 'package:timezone/timezone.dart' as tz; @@ -67,7 +68,12 @@ class RequestManager { @visibleForTesting http.Client? httpClient; - /// Executes a generic [http.Request]. + /// This allows us to limit the amount of parallel requests to the server. + /// This is especially important when opening a gallery with a lot of images which require previews. + final _requestQueue = Queue(parallel: 10); + + /// Executes a generic [http.Request] in a queued manner. + /// Only actual requests that are not cached will be executed against the server and queued. Future wrap({ required Account account, required BehaviorSubject> subject, @@ -79,11 +85,12 @@ class RequestManager { if (subject.isClosed) { return; } + if (!subject.hasValue) { subject.add(Result.loading()); } - var request = getRequest(); + final request = getRequest(); final cachedResponse = await _cache?.get(account, request); if (subject.isClosed) { @@ -160,6 +167,29 @@ class RequestManager { subject.add(subject.value.asLoading()); } + await _requestQueue.add( + () => _requestFromServer( + request: request, + account: account, + subject: subject, + getRequest: getRequest, + converter: converter, + unwrap: unwrap, + getCacheHeaders: getCacheHeaders, + ), + ); + } + + /// Performs [http.Request] against the server. + Future _requestFromServer({ + required http.Request request, + required Account account, + required BehaviorSubject> subject, + required http.Request Function() getRequest, + required Converter converter, + required UnwrapCallback unwrap, + AsyncValueGetter>? getCacheHeaders, + }) async { final client = httpClient ?? account.client; for (var i = 0; i < kMaxTries; i++) { diff --git a/packages/neon_framework/packages/talk_app/test/room_bloc_test.dart b/packages/neon_framework/packages/talk_app/test/room_bloc_test.dart index b6bbdb38f93..1f7bc6226d4 100644 --- a/packages/neon_framework/packages/talk_app/test/room_bloc_test.dart +++ b/packages/neon_framework/packages/talk_app/test/room_bloc_test.dart @@ -320,37 +320,66 @@ void main() { test('refresh', () async { expect( - roomBloc.room.transformResult((e) => e.token), + roomBloc.lastCommonRead, + emitsInOrder([0, 0]), + ); + + final initialRoom = expectLater( + roomBloc.room.transformResult((room) => room.token), emitsInOrder([ Result.success('abcd').asLoading(), Result.success('abcd'), Result.success('abcd'), + ]), + ); + + final initialMessages = expectLater( + roomBloc.messages.transformResult( + (messages) => BuiltList(messages.map((message) => message.id)), + ), + emitsInOrder([ + Result>.loading(), + Result.success(BuiltList([2, 1, 0])), + ]), + ); + + await Future.wait([ + initialRoom, + initialMessages, + ]); + + final refreshedRoom = expectLater( + roomBloc.room + .skip(1) // we are skipping the initial refresh + .transformResult((room) => room.token), + emitsInOrder([ Result.success('abcd').asLoading(), Result.success('abcd'), Result.success('abcd'), ]), ); - expect( - roomBloc.messages.transformResult((e) => BuiltList(e.map((m) => m.id))), + final refreshedMessages = expectLater( + roomBloc.messages + .skip(1) // we are skipping the initial refresh + .transformResult( + (messages) => BuiltList(messages.map((message) => message.id)), + ), emitsInOrder([ - Result>.loading(), - Result.success(BuiltList([2, 1, 0])), Result.success(BuiltList([2, 1, 0])).asLoading(), Result.success(BuiltList([2, 1, 0])), ]), ); - expect( - roomBloc.lastCommonRead, - emitsInOrder([0, 0]), - ); - - // The delay is necessary to avoid a race condition with loading twice at the same time - await Future.delayed(const Duration(milliseconds: 1)); await roomBloc.refresh(); - verify(() => talkBloc.updateRoom(any())).called(4); + await Future.wait([ + refreshedRoom, + refreshedMessages, + ]); + + // expecting 5 events: initial seed + 2 from constructor refresh + 2 from test refresh + verify(() => talkBloc.updateRoom(any())).called(5); }); test('sendMessage', () async { diff --git a/packages/neon_framework/pubspec.yaml b/packages/neon_framework/pubspec.yaml index d2b931b89f2..9e7682a089d 100644 --- a/packages/neon_framework/pubspec.yaml +++ b/packages/neon_framework/pubspec.yaml @@ -57,6 +57,7 @@ dependencies: path_provider: ^2.1.0 permission_handler: ^12.0.0 provider: ^6.0.0 + queue: ^3.0.0 quick_actions: ^1.0.0 rxdart: ^0.28.0 scrollable_positioned_list: ^0.3.0 diff --git a/packages/neon_lints/pubspec.yaml b/packages/neon_lints/pubspec.yaml index 536c4786aaf..e45b713d2e6 100644 --- a/packages/neon_lints/pubspec.yaml +++ b/packages/neon_lints/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: analyzer: ^7.3.0 - analyzer_plugin: ^0.13.0 + analyzer_plugin: ^0.13.1 custom_lint_builder: ^0.7.0 dev_dependencies: