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 <a.tarek360@gmail.com>
Co-authored-by: Maxwell Talbot <talbot.maxwell@gmail.com>
This commit is contained in:
mjtalbot
2023-05-31 11:56:54 +00:00
parent 58c998986a
commit 76b43e1cc2
6 changed files with 96 additions and 5 deletions

View File

@ -1 +1 @@
cae6fa5ccfb6029614e6306d443a2039d1f0657c c163c1a7fe5a8d26506c439e686716b4131f6ffe

View File

@ -1,3 +1,7 @@
## 0.11.2
- Add parameter to specifcy headers on RiveAnimation.network widget
## 0.11.1 ## 0.11.1
- Joysticks with custom handle sources. - Joysticks with custom handle sources.

View File

@ -277,8 +277,8 @@ class RiveFile {
/// your file contains images that needed to be loaded with separate network /// your file contains images that needed to be loaded with separate network
/// requests. /// requests.
static Future<RiveFile> network(String url, static Future<RiveFile> network(String url,
{FileAssetResolver? assetResolver}) async { {FileAssetResolver? assetResolver, Map<String, String>? headers}) async {
final res = await http.get(Uri.parse(url)); final res = await http.get(Uri.parse(url), headers: headers);
final bytes = ByteData.view(res.bodyBytes.buffer); final bytes = ByteData.view(res.bodyBytes.buffer);
return RiveFile.import(bytes, assetResolver: assetResolver); return RiveFile.import(bytes, assetResolver: assetResolver);
} }

View File

@ -55,6 +55,9 @@ class RiveAnimation extends StatefulWidget {
/// Callback fired when [RiveAnimation] has initialized /// Callback fired when [RiveAnimation] has initialized
final OnInitCallback? onInit; final OnInitCallback? onInit;
/// Headers for network requests
final Map<String, String>? headers;
/// Creates a new [RiveAnimation] from an asset bundle. /// Creates a new [RiveAnimation] from an asset bundle.
/// ///
/// *Example:* /// *Example:*
@ -75,6 +78,7 @@ class RiveAnimation extends StatefulWidget {
Key? key, Key? key,
}) : name = asset, }) : name = asset,
file = null, file = null,
headers = null,
src = _Source.asset, src = _Source.asset,
super(key: key); super(key: key);
@ -95,6 +99,7 @@ class RiveAnimation extends StatefulWidget {
this.antialiasing = true, this.antialiasing = true,
this.controllers = const [], this.controllers = const [],
this.onInit, this.onInit,
this.headers,
Key? key, Key? key,
}) : name = url, }) : name = url,
file = null, file = null,
@ -121,6 +126,7 @@ class RiveAnimation extends StatefulWidget {
Key? key, Key? key,
}) : name = path, }) : name = path,
file = null, file = null,
headers = null,
src = _Source.file, src = _Source.file,
super(key: key); super(key: key);
@ -145,6 +151,7 @@ class RiveAnimation extends StatefulWidget {
this.onInit, this.onInit,
Key? key, Key? key,
}) : name = null, }) : name = null,
headers = null,
src = _Source.direct, src = _Source.direct,
super(key: key); super(key: key);
@ -182,7 +189,7 @@ class RiveAnimationState extends State<RiveAnimation> {
case _Source.asset: case _Source.asset:
return RiveFile.asset(widget.name!); return RiveFile.asset(widget.name!);
case _Source.network: case _Source.network:
return RiveFile.network(widget.name!); return RiveFile.network(widget.name!, headers: widget.headers);
case _Source.file: case _Source.file:
return RiveFile.file(widget.name!); return RiveFile.file(widget.name!);
case _Source.direct: case _Source.direct:

View File

@ -1,5 +1,5 @@
name: rive name: rive
version: 0.11.1 version: 0.11.2
homepage: https://rive.app 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. 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 repository: https://github.com/rive-app/rive-flutter

View File

@ -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(<int>[]));
// 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);
});
}