From 76b43e1cc2b1ef9639a10ae6344a6301b0896557 Mon Sep 17 00:00:00 2001 From: mjtalbot Date: Wed, 31 May 2023 11:56:54 +0000 Subject: [PATCH] Add setting custom headers adding pr from the community https://github.com/rive-app/rive-flutter/pull/314, including an update to how we make sure credits for contributions are kept when our mono scripts merge changes upstream. Some companies don't let the rive file URLs without authentication, that's why they need to send headers in the network request. Example of how to use: ``` RiveAnimation.network( 'https://cdn.rive.app/animations/vehicles.riv', headers: {'Authorization': '{token}'}, ) ``` The same idea is in the [flutter_svg package](https://pub.dev/documentation/flutter_svg/latest/svg/SvgPicture/SvgPicture.network.html) Diffs= c163c1a7f Add setting custom headers (#5327) Co-authored-by: Ahmed Wahba Co-authored-by: Maxwell Talbot --- .rive_head | 2 +- CHANGELOG.md | 4 ++ lib/src/rive_file.dart | 4 +- lib/src/widgets/rive_animation.dart | 9 +++- pubspec.yaml | 2 +- test/rive_network_test.dart | 80 +++++++++++++++++++++++++++++ 6 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 test/rive_network_test.dart diff --git a/.rive_head b/.rive_head index 2551d4c..0bf90c1 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -cae6fa5ccfb6029614e6306d443a2039d1f0657c +c163c1a7fe5a8d26506c439e686716b4131f6ffe diff --git a/CHANGELOG.md b/CHANGELOG.md index e228ffe..93fb749 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.11.2 + +- Add parameter to specifcy headers on RiveAnimation.network widget + ## 0.11.1 - Joysticks with custom handle sources. diff --git a/lib/src/rive_file.dart b/lib/src/rive_file.dart index d158f33..f1e60db 100644 --- a/lib/src/rive_file.dart +++ b/lib/src/rive_file.dart @@ -277,8 +277,8 @@ class RiveFile { /// your file contains images that needed to be loaded with separate network /// requests. static Future network(String url, - {FileAssetResolver? assetResolver}) async { - final res = await http.get(Uri.parse(url)); + {FileAssetResolver? assetResolver, Map? headers}) async { + final res = await http.get(Uri.parse(url), headers: headers); final bytes = ByteData.view(res.bodyBytes.buffer); return RiveFile.import(bytes, assetResolver: assetResolver); } diff --git a/lib/src/widgets/rive_animation.dart b/lib/src/widgets/rive_animation.dart index 1fc0416..7cc0477 100644 --- a/lib/src/widgets/rive_animation.dart +++ b/lib/src/widgets/rive_animation.dart @@ -55,6 +55,9 @@ class RiveAnimation extends StatefulWidget { /// Callback fired when [RiveAnimation] has initialized final OnInitCallback? onInit; + /// Headers for network requests + final Map? headers; + /// Creates a new [RiveAnimation] from an asset bundle. /// /// *Example:* @@ -75,6 +78,7 @@ class RiveAnimation extends StatefulWidget { Key? key, }) : name = asset, file = null, + headers = null, src = _Source.asset, super(key: key); @@ -95,6 +99,7 @@ class RiveAnimation extends StatefulWidget { this.antialiasing = true, this.controllers = const [], this.onInit, + this.headers, Key? key, }) : name = url, file = null, @@ -121,6 +126,7 @@ class RiveAnimation extends StatefulWidget { Key? key, }) : name = path, file = null, + headers = null, src = _Source.file, super(key: key); @@ -145,6 +151,7 @@ class RiveAnimation extends StatefulWidget { this.onInit, Key? key, }) : name = null, + headers = null, src = _Source.direct, super(key: key); @@ -182,7 +189,7 @@ class RiveAnimationState extends State { case _Source.asset: return RiveFile.asset(widget.name!); case _Source.network: - return RiveFile.network(widget.name!); + return RiveFile.network(widget.name!, headers: widget.headers); case _Source.file: return RiveFile.file(widget.name!); case _Source.direct: diff --git a/pubspec.yaml b/pubspec.yaml index acebc90..39856ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: rive -version: 0.11.1 +version: 0.11.2 homepage: https://rive.app description: Rive 2 Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app. repository: https://github.com/rive-app/rive-flutter diff --git a/test/rive_network_test.dart b/test/rive_network_test.dart new file mode 100644 index 0000000..fa72ad4 --- /dev/null +++ b/test/rive_network_test.dart @@ -0,0 +1,80 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rive/rive.dart'; + +import 'mocks/mocks.dart'; +import 'src/utils.dart'; + +class MockHttpClient extends Mock implements HttpClient {} + +class MockHttpClientRequest extends Mock implements HttpClientRequest {} + +class MockHttpClientResponse extends Mock implements HttpClientResponse {} + +class MockHttpHeaders extends Mock implements HttpHeaders {} + +void main() { + late MockHttpClient mockHttpClient; + late MockHttpClientRequest request; + setUpAll(() { + registerFallbackValue(ArtboardFake()); + registerFallbackValue(Uri()); + registerFallbackValue(Stream.value([])); + // Build our app and trigger a frame. + final riveBytes = loadFile('assets/rive-flutter-test-asset.riv'); + final body = riveBytes.buffer.asUint8List(); + mockHttpClient = MockHttpClient(); + request = MockHttpClientRequest(); + + when(() => request.headers).thenReturn(MockHttpHeaders()); + + when(() => mockHttpClient.openUrl(any(), any())).thenAnswer((invocation) { + final response = MockHttpClientResponse(); + when(request.close).thenAnswer((_) => Future.value(response)); + when(() => request.addStream(any())).thenAnswer((_) async => null); + when(() => response.headers).thenReturn(MockHttpHeaders()); + when(() => response.handleError(any(), test: any(named: 'test'))) + .thenAnswer((_) => Stream.value(body)); + when(() => response.statusCode).thenReturn(200); + when(() => response.reasonPhrase).thenReturn('OK'); + when(() => response.contentLength).thenReturn(body.length); + when(() => response.isRedirect).thenReturn(false); + when(() => response.persistentConnection).thenReturn(false); + return Future.value(request); + }); + }); + + testWidgets('Using the network, calls the http client without headers', + (WidgetTester tester) async { + await HttpOverrides.runZoned(() async { + await tester.pumpWidget( + const MaterialApp( + home: RiveAnimation.network('https://some.fake.url'), + ), + ); + }, createHttpClient: (_) => mockHttpClient); + + verify(() => mockHttpClient.openUrl(any(), any())).called(1); + verifyNever(() => request.headers.set(any(), any())); + }); + + testWidgets('Using the network, calls the http client with headers', + (WidgetTester tester) async { + await HttpOverrides.runZoned(() async { + await tester.pumpWidget( + const MaterialApp( + home: RiveAnimation.network('https://some.fake.url', headers: { + 'first': 'header', + 'second': 'header', + }), + ), + ); + }, createHttpClient: (_) => mockHttpClient); + + verify(() => mockHttpClient.openUrl(any(), any())).called(1); + verify(() => request.headers.set(any(), any())).called(2); + }); +}