Skip to content

Commit ee9bc1b

Browse files
authored
feat: create abstract layer for deep_link_client to simplify other implementations (#1158)
1 parent 5354d19 commit ee9bc1b

22 files changed

+298
-185
lines changed

.github/workflows/deep_link_client.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ concurrency:
77
on:
88
pull_request:
99
paths:
10-
- "flutter_news_example/packages/deep_link_client/**"
10+
- "flutter_news_example/packages/deep_link_client/deep_link_client/**"
1111
- ".github/workflows/deep_link_client.yaml"
1212
branches:
1313
- main
1414

1515
jobs:
1616
build:
17-
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
17+
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1
1818
with:
19-
flutter_version: 3.24.2
20-
working_directory: flutter_news_example/packages/deep_link_client
19+
dart_sdk: 3.4.3
20+
working_directory: flutter_news_example/packages/deep_link_client/deep_link_client
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: firebase_deep_link_client
2+
3+
concurrency:
4+
group: ${{ github.workflow }}-${{ github.ref }}
5+
cancel-in-progress: true
6+
7+
on:
8+
pull_request:
9+
paths:
10+
- "flutter_news_example/packages/deep_link_client/firebase_deep_link_client/**"
11+
- ".github/workflows/firebase_deep_link_client.yaml"
12+
branches:
13+
- main
14+
15+
jobs:
16+
build:
17+
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
18+
with:
19+
flutter_version: 3.22.2
20+
working_directory: flutter_news_example/packages/deep_link_client/firebase_deep_link_client

flutter_news_example/lib/main/main_development.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:ads_consent_client/ads_consent_client.dart';
22
import 'package:article_repository/article_repository.dart';
33
import 'package:deep_link_client/deep_link_client.dart';
44
import 'package:firebase_authentication_client/firebase_authentication_client.dart';
5+
import 'package:firebase_deep_link_client/firebase_deep_link_client.dart';
56
import 'package:firebase_notifications_client/firebase_notifications_client.dart';
67
import 'package:flutter_news_example/app/app.dart';
78
import 'package:flutter_news_example/main/bootstrap/bootstrap.dart';
@@ -43,8 +44,10 @@ void main() {
4344
packageVersion: packageVersion,
4445
);
4546

46-
final deepLinkClient = DeepLinkClient(
47-
firebaseDynamicLinks: firebaseDynamicLinks,
47+
final deepLinkService = DeepLinkService(
48+
deepLinkClient: FirebaseDeepLinkClient(
49+
firebaseDynamicLinks: firebaseDynamicLinks,
50+
),
4851
);
4952

5053
final userStorage = UserStorage(storage: persistentStorage);
@@ -61,7 +64,7 @@ void main() {
6164
apiClient: apiClient,
6265
authenticationClient: authenticationClient,
6366
packageInfoClient: packageInfoClient,
64-
deepLinkClient: deepLinkClient,
67+
deepLinkService: deepLinkService,
6568
storage: userStorage,
6669
);
6770

flutter_news_example/lib/main/main_production.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:ads_consent_client/ads_consent_client.dart';
22
import 'package:article_repository/article_repository.dart';
33
import 'package:deep_link_client/deep_link_client.dart';
44
import 'package:firebase_authentication_client/firebase_authentication_client.dart';
5+
import 'package:firebase_deep_link_client/firebase_deep_link_client.dart';
56
import 'package:firebase_notifications_client/firebase_notifications_client.dart';
67
import 'package:flutter_news_example/app/app.dart';
78
import 'package:flutter_news_example/main/bootstrap/bootstrap.dart';
@@ -43,8 +44,10 @@ void main() {
4344
packageVersion: packageVersion,
4445
);
4546

46-
final deepLinkClient = DeepLinkClient(
47-
firebaseDynamicLinks: firebaseDynamicLinks,
47+
final deepLinkService = DeepLinkService(
48+
deepLinkClient: FirebaseDeepLinkClient(
49+
firebaseDynamicLinks: firebaseDynamicLinks,
50+
),
4851
);
4952

5053
final userStorage = UserStorage(storage: persistentStorage);
@@ -61,7 +64,7 @@ void main() {
6164
apiClient: apiClient,
6265
authenticationClient: authenticationClient,
6366
packageInfoClient: packageInfoClient,
64-
deepLinkClient: deepLinkClient,
67+
deepLinkService: deepLinkService,
6568
storage: userStorage,
6669
);
6770

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include: package:very_good_analysis/analysis_options.5.1.0.yaml
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export 'src/deep_link_client.dart';
2+
export 'src/deep_link_service.dart';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// {@template deep_link_client}
2+
/// A generic DeepLinkClient interface.
3+
/// {@endtemplate}
4+
abstract class DeepLinkClient {
5+
/// Provides a stream of URIs intercepted by the app. Will emit the latest
6+
/// received value (if any) as first.
7+
Stream<Uri> get deepLinkStream;
8+
9+
/// Retrieves the initial deep link if present.
10+
Future<Uri?> getInitialLink();
11+
}

flutter_news_example/packages/deep_link_client/lib/src/deep_link_client.dart renamed to flutter_news_example/packages/deep_link_client/deep_link_client/lib/src/deep_link_service.dart

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
import 'dart:async';
44

5+
import 'package:deep_link_client/deep_link_client.dart';
56
import 'package:equatable/equatable.dart';
6-
import 'package:firebase_dynamic_links/firebase_dynamic_links.dart';
7-
87
import 'package:rxdart/rxdart.dart';
98

109
/// {@template deep_link_client_failure}
@@ -21,21 +20,20 @@ class DeepLinkClientFailure with EquatableMixin implements Exception {
2120
List<Object> get props => [error];
2221
}
2322

24-
/// {@template deep_link_client}
25-
/// A client that exposes a stream of deep link URIs redirected to the app.
23+
/// {@template deep_link_service}
24+
/// A DeepLinkService that provides access to deep links intercepted by the app.
2625
/// {@endtemplate}
27-
class DeepLinkClient {
28-
/// {@macro deep_link_client}
29-
DeepLinkClient({FirebaseDynamicLinks? firebaseDynamicLinks})
30-
: _deepLinkSubject = BehaviorSubject<Uri>() {
31-
_firebaseDynamicLinks =
32-
firebaseDynamicLinks ?? FirebaseDynamicLinks.instance;
33-
26+
class DeepLinkService {
27+
/// {@macro deep_link_service}
28+
DeepLinkService({
29+
required DeepLinkClient deepLinkClient,
30+
}) : _deepLinkClient = deepLinkClient,
31+
_deepLinkSubject = BehaviorSubject<Uri>() {
3432
unawaited(_getInitialLink());
35-
_firebaseDynamicLinks.onLink.listen(_onAppLink).onError(_handleError);
33+
_deepLinkClient.deepLinkStream.listen(_onAppLink).onError(_handleError);
3634
}
3735

38-
late final FirebaseDynamicLinks _firebaseDynamicLinks;
36+
final DeepLinkClient _deepLinkClient;
3937
final BehaviorSubject<Uri> _deepLinkSubject;
4038

4139
/// Provides a stream of URIs intercepted by the app. Will emit the latest
@@ -44,7 +42,7 @@ class DeepLinkClient {
4442

4543
Future<void> _getInitialLink() async {
4644
try {
47-
final deepLink = await _firebaseDynamicLinks.getInitialLink();
45+
final deepLink = await _deepLinkClient.getInitialLink();
4846
if (deepLink != null) {
4947
_onAppLink(deepLink);
5048
}
@@ -53,8 +51,8 @@ class DeepLinkClient {
5351
}
5452
}
5553

56-
void _onAppLink(PendingDynamicLinkData dynamicLinkData) {
57-
_deepLinkSubject.add(dynamicLinkData.link);
54+
void _onAppLink(Uri deepLink) {
55+
_deepLinkSubject.add(deepLink);
5856
}
5957

6058
void _handleError(Object error, StackTrace stackTrace) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: deep_link_client
2+
description: A deep link client interface
3+
publish_to: none
4+
5+
environment:
6+
sdk: ">=3.0.0 <4.0.0"
7+
8+
dependencies:
9+
equatable: ^2.0.5
10+
rxdart: ^0.27.5
11+
12+
dev_dependencies:
13+
mocktail: ^1.0.4
14+
test: ^1.25.8
15+
very_good_analysis: ^6.0.0
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import 'dart:async';
2+
3+
import 'package:deep_link_client/deep_link_client.dart';
4+
import 'package:mocktail/mocktail.dart';
5+
import 'package:test/test.dart';
6+
7+
typedef OnAppLinkFunction = void Function(Uri uri, String stringUri);
8+
9+
class MockDeepLinkClient extends Mock implements DeepLinkClient {}
10+
11+
void main() {
12+
late DeepLinkClient deepLinkClient;
13+
late StreamController<Uri> onDeepLinkStreamController;
14+
15+
setUp(() {
16+
deepLinkClient = MockDeepLinkClient();
17+
onDeepLinkStreamController = StreamController<Uri>();
18+
when(() => deepLinkClient.deepLinkStream)
19+
.thenAnswer((_) => onDeepLinkStreamController.stream);
20+
});
21+
22+
tearDown(() {
23+
onDeepLinkStreamController.close();
24+
});
25+
26+
group('DeepLinkService', () {
27+
test('retrieves and publishes latest link if present', () {
28+
final expectedUri = Uri.https('ham.app.test', '/test/path');
29+
when(deepLinkClient.getInitialLink).thenAnswer(
30+
(_) => Future.value(expectedUri),
31+
);
32+
33+
final service = DeepLinkService(deepLinkClient: deepLinkClient);
34+
expect(service.deepLinkStream, emits(expectedUri));
35+
36+
// Testing also the replay of the latest value.
37+
expect(service.deepLinkStream, emits(expectedUri));
38+
});
39+
40+
test('publishes DeepLinkClientFailure to stream if upstream throws', () {
41+
final expectedError = Error();
42+
final expectedStackTrace = StackTrace.current;
43+
44+
when(deepLinkClient.getInitialLink).thenAnswer((_) {
45+
return Future.error(expectedError, expectedStackTrace);
46+
});
47+
48+
final deepLinkService = DeepLinkService(deepLinkClient: deepLinkClient);
49+
expect(
50+
deepLinkService.deepLinkStream,
51+
emitsError(
52+
isA<DeepLinkClientFailure>()
53+
.having((failure) => failure.error, 'error', expectedError),
54+
),
55+
);
56+
});
57+
58+
test('publishes values received through onAppLink callback', () {
59+
final expectedUri1 = Uri.https('ham.app.test', '/test/1');
60+
final expectedUri2 = Uri.https('ham.app.test', '/test/2');
61+
62+
when(deepLinkClient.getInitialLink).thenAnswer((_) async => null);
63+
64+
final deepLinkService = DeepLinkService(deepLinkClient: deepLinkClient);
65+
66+
expect(
67+
deepLinkService.deepLinkStream,
68+
emitsInOrder(
69+
<Uri>[expectedUri1, expectedUri1, expectedUri2, expectedUri1],
70+
),
71+
);
72+
73+
onDeepLinkStreamController
74+
..add(expectedUri1)
75+
..add(expectedUri1)
76+
..add(expectedUri2)
77+
..add(expectedUri1);
78+
});
79+
});
80+
81+
group('DeepLinkClientFailure', () {
82+
final error = Exception('errorMessage');
83+
84+
test('has correct props', () {
85+
expect(
86+
DeepLinkClientFailure(error).props,
87+
[error],
88+
);
89+
});
90+
});
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export 'src/firebase_deep_link_client.dart';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// ignore_for_file: deprecated_member_use
2+
3+
import 'dart:async';
4+
5+
import 'package:deep_link_client/deep_link_client.dart';
6+
import 'package:firebase_dynamic_links/firebase_dynamic_links.dart';
7+
8+
/// {@template firebase_deep_link_client}
9+
/// A FirebaseDynamicLinks implementation of [DeepLinkClient].
10+
/// {@endtemplate}
11+
class FirebaseDeepLinkClient implements DeepLinkClient {
12+
/// {@macro firebase_deep_link_client}
13+
FirebaseDeepLinkClient({required FirebaseDynamicLinks firebaseDynamicLinks})
14+
: _firebaseDynamicLinks = firebaseDynamicLinks;
15+
16+
final FirebaseDynamicLinks _firebaseDynamicLinks;
17+
18+
@override
19+
Stream<Uri> get deepLinkStream =>
20+
_firebaseDynamicLinks.onLink.map((event) => event.link);
21+
22+
@override
23+
Future<Uri?> getInitialLink() async {
24+
final deepLink = await _firebaseDynamicLinks.getInitialLink();
25+
return deepLink?.link;
26+
}
27+
}

flutter_news_example/packages/deep_link_client/pubspec.yaml renamed to flutter_news_example/packages/deep_link_client/firebase_deep_link_client/pubspec.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
name: deep_link_client
1+
name: firebase_deep_link_client
22
description: A Dart package which provides a deep link stream
33
publish_to: none
44

55
environment:
66
sdk: ">=3.0.0 <4.0.0"
77

88
dependencies:
9+
deep_link_client:
10+
path: ../deep_link_client
911
equatable: ^2.0.3
1012
firebase_core: ^3.4.1
1113
firebase_dynamic_links: ^6.0.6
1214
plugin_platform_interface: ^2.1.3
13-
rxdart: ^0.27.3
1415

1516
dev_dependencies:
1617
firebase_core_platform_interface: ^5.2.1

0 commit comments

Comments
 (0)