Skip to content

Commit 9a13359

Browse files
authored
[deep link] display "missing scheme" and "missing domain" for null values. (#7559)
* add null scheme and domains * Update NEXT_RELEASE_NOTES.md * resolve comments * Update deep_links_model.dart * Update deep_links_screen_test.dart * lint
1 parent ce95784 commit 9a13359

File tree

5 files changed

+95
-34
lines changed

5 files changed

+95
-34
lines changed

packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_controller.dart

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import 'package:devtools_app_shared/utils.dart';
88
import 'package:devtools_shared/devtools_deeplink.dart';
99
import 'package:flutter/material.dart';
1010

11+
import '../../../devtools_app.dart';
1112
import '../../shared/analytics/analytics.dart' as ga;
1213
import '../../shared/analytics/constants.dart' as gac;
13-
import '../../shared/globals.dart';
1414
import '../../shared/server/server.dart' as server;
1515
import 'deep_link_list_view.dart';
1616
import 'deep_links_model.dart';
1717
import 'deep_links_services.dart';
1818

19-
typedef _DomainAndPath = ({String domain, String path});
19+
typedef _DomainAndPath = ({String? domain, String path});
2020

2121
const domainAssetLinksJsonFileErrors = {
2222
DomainError.existence,
@@ -160,6 +160,7 @@ class DeepLinksController extends DisposableController {
160160
linkDatasByPath[linkData.path] = LinkData(
161161
domain: linkData.domain,
162162
path: linkData.path,
163+
scheme: linkData.scheme.union(previousRecord?.scheme ?? {}),
163164
os: [
164165
if (previousRecord?.os.contains(PlatformOS.android) ??
165166
false || linkData.os.contains(PlatformOS.android))
@@ -170,7 +171,7 @@ class DeepLinksController extends DisposableController {
170171
],
171172
associatedDomains: [
172173
...previousRecord?.associatedDomains ?? [],
173-
linkData.domain,
174+
if (linkData.domain != null) linkData.domain!,
174175
],
175176
pathErrors: linkData.pathErrors,
176177
);
@@ -182,12 +183,15 @@ class DeepLinksController extends DisposableController {
182183
@visibleForTesting
183184
List<LinkData> linkDatasByDomain(List<LinkData> linkdatas) {
184185
final linkDatasByDomain = <String, LinkData>{};
185-
186186
for (var linkData in linkdatas) {
187+
if (linkData.domain.isNullOrEmpty) {
188+
continue;
189+
}
187190
final previousRecord = linkDatasByDomain[linkData.domain];
188-
linkDatasByDomain[linkData.domain] = LinkData(
191+
linkDatasByDomain[linkData.domain!] = LinkData(
189192
domain: linkData.domain,
190193
path: linkData.path,
194+
scheme: linkData.scheme.union(previousRecord?.scheme ?? {}),
191195
os: linkData.os,
192196
associatedPath: [
193197
...previousRecord?.associatedPath ?? [],
@@ -267,6 +271,7 @@ class DeepLinksController extends DisposableController {
267271
final domainPathToLinkData = <_DomainAndPath, LinkData>{};
268272
for (final appLink in appLinks) {
269273
final domainAndPath = (domain: appLink.host, path: appLink.path);
274+
final scheme = appLink.scheme;
270275

271276
if (domainPathToLinkData[domainAndPath] == null) {
272277
domainPathToLinkData[domainAndPath] = LinkData(
@@ -275,12 +280,12 @@ class DeepLinksController extends DisposableController {
275280
pathErrors:
276281
_getPathErrorsFromIntentFilterChecks(appLink.intentFilterChecks),
277282
os: [PlatformOS.android],
278-
scheme: [appLink.scheme],
283+
scheme: {if (scheme != null) scheme},
279284
);
280285
} else {
281286
final linkData = domainPathToLinkData[domainAndPath]!;
282-
if (!linkData.scheme.contains(appLink.scheme)) {
283-
linkData.scheme.add(appLink.scheme);
287+
if (scheme != null) {
288+
linkData.scheme.add(scheme);
284289
}
285290
final pathErrors = {
286291
...linkData.pathErrors,
@@ -341,20 +346,27 @@ class DeepLinksController extends DisposableController {
341346

342347
Future<void> _generateAssetLinks() async {
343348
generatedAssetLinksForSelectedLink.value = null;
344-
generatedAssetLinksForSelectedLink.value =
345-
await deepLinksServices.generateAssetLinks(
346-
domain: selectedLink.value!.domain,
347-
applicationId: applicationId,
348-
localFingerprint: localFingerprint.value,
349-
);
349+
final domain = selectedLink.value!.domain;
350+
if (domain != null) {
351+
generatedAssetLinksForSelectedLink.value =
352+
await deepLinksServices.generateAssetLinks(
353+
domain: domain,
354+
applicationId: applicationId,
355+
localFingerprint: localFingerprint.value,
356+
);
357+
}
350358
}
351359

352360
Future<List<LinkData>> _validateAndroidDomain(
353361
List<LinkData> linkdatas,
354362
) async {
355363
final domains = linkdatas
356-
.where((linkdata) => linkdata.os.contains(PlatformOS.android))
357-
.map((linkdata) => linkdata.domain)
364+
.where(
365+
(linkdata) =>
366+
linkdata.os.contains(PlatformOS.android) &&
367+
linkdata.domain != null,
368+
)
369+
.map((linkdata) => linkdata.domain!)
358370
.toSet()
359371
.toList();
360372

packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_model.dart

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const kDeeplinkTableCellDefaultWidth = 200.0;
1818
const kToolTipWidth = 344.0;
1919
const metaDataDeepLinkingFlagTag =
2020
'<meta-data android:name="flutter_deeplinking_enabled" android:value="true" />';
21+
const missingDomain = 'missing domain';
22+
const missingScheme = 'missing scheme';
2123

2224
enum PlatformOS {
2325
android('Android'),
@@ -193,17 +195,17 @@ class LinkData with SearchableDataMixin {
193195
required this.domain,
194196
required this.path,
195197
required this.os,
196-
this.scheme = const <String>['http://', 'https://'],
198+
this.scheme = const <String>{},
197199
this.domainErrors = const <DomainError>[],
198200
this.pathErrors = const <PathError>{},
199201
this.associatedPath = const <String>[],
200202
this.associatedDomains = const <String>[],
201203
});
202204

203205
final String path;
204-
final String domain;
206+
final String? domain;
205207
final List<PlatformOS> os;
206-
final List<String> scheme;
208+
final Set<String> scheme;
207209
final List<DomainError> domainErrors;
208210
Set<PathError> pathErrors;
209211

@@ -212,7 +214,7 @@ class LinkData with SearchableDataMixin {
212214

213215
@override
214216
bool matchesSearchToken(RegExp regExpSearch) {
215-
return domain.caseInsensitiveContains(regExpSearch) ||
217+
return (domain?.caseInsensitiveContains(regExpSearch) ?? false) ||
216218
path.caseInsensitiveContains(regExpSearch);
217219
}
218220

@@ -328,7 +330,7 @@ class DomainColumn extends ColumnData<LinkData>
328330
}
329331

330332
@override
331-
String getValue(LinkData dataObject) => dataObject.domain;
333+
String getValue(LinkData dataObject) => dataObject.domain ?? 'missing domain';
332334

333335
@override
334336
Widget build(
@@ -338,12 +340,14 @@ class DomainColumn extends ColumnData<LinkData>
338340
bool isRowHovered = false,
339341
VoidCallback? onPressed,
340342
}) {
341-
return _ErrorAwareText(
342-
isError: dataObject.domainErrors.isNotEmpty,
343-
controller: controller,
344-
text: dataObject.domain,
345-
link: dataObject,
346-
);
343+
return dataObject.domain == null
344+
? Text('missing domain', style: Theme.of(context).errorTextStyle)
345+
: _ErrorAwareText(
346+
isError: dataObject.domainErrors.isNotEmpty,
347+
controller: controller,
348+
text: dataObject.domain!,
349+
link: dataObject,
350+
);
347351
}
348352

349353
@override
@@ -474,7 +478,9 @@ class SchemeColumn extends ColumnData<LinkData>
474478
bool isRowHovered = false,
475479
VoidCallback? onPressed,
476480
}) {
477-
return Text(getValue(dataObject));
481+
return dataObject.scheme.isEmpty
482+
? Text(missingScheme, style: Theme.of(context).errorTextStyle)
483+
: Text(getValue(dataObject));
478484
}
479485

480486
@override
@@ -723,11 +729,11 @@ int _compareLinkData(
723729
}
724730
return 0;
725731
case SortingOption.aToZ:
726-
if (compareDomain) return a.domain.compareTo(b.domain);
732+
if (compareDomain) return (a.domain ?? '').compareTo(b.domain ?? '');
727733

728734
return a.path.compareTo(b.path);
729735
case SortingOption.zToA:
730-
if (compareDomain) return b.domain.compareTo(a.domain);
736+
if (compareDomain) return (b.domain ?? '').compareTo(a.domain ?? '');
731737

732738
return b.path.compareTo(a.path);
733739
}

packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ TODO: Remove this section if there are not any general updates.
4848
## Deep links tool updates
4949

5050
* Improve layout for narrow screens. - [#7524](https://github.com/flutter/devtools/pull/7524)
51+
* Add error handling for missing schemes and domains - [#7559](https://github.com/flutter/devtools/pull/7559)
5152

5253
## VS Code Sidebar updates
5354

packages/devtools_app/test/deep_link_vlidation/deep_links_screen_test.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,48 @@ void main() {
538538
},
539539
);
540540

541+
testWidgetsWithWindowSize(
542+
'show scheme or missing scheme',
543+
windowSize,
544+
(WidgetTester tester) async {
545+
final deepLinksController = DeepLinksTestController();
546+
547+
deepLinksController.selectedProject.value =
548+
FlutterProject(path: '/abc', androidVariants: ['debug', 'release']);
549+
550+
final linkDatas = [
551+
LinkData(
552+
domain: 'www.domain1.com',
553+
path: '/',
554+
os: [PlatformOS.android],
555+
),
556+
LinkData(
557+
domain: 'www.domain2.com',
558+
path: '/',
559+
os: [PlatformOS.ios],
560+
scheme: {'http'},
561+
),
562+
];
563+
564+
deepLinksController.validatedLinkDatas = ValidatedLinkDatas(
565+
all: linkDatas,
566+
byDomain: deepLinksController.linkDatasByDomain(linkDatas),
567+
byPath: deepLinksController.linkDatasByPath(linkDatas),
568+
);
569+
570+
await pumpDeepLinkScreen(
571+
tester,
572+
controller: deepLinksController,
573+
);
574+
575+
expect(find.text('www.domain1.com'), findsOneWidget);
576+
expect(find.text('www.domain2.com'), findsOneWidget);
577+
578+
expect(find.text('missing scheme'), findsOneWidget);
579+
expect(find.text('http'), findsOneWidget);
580+
},
581+
);
582+
541583
testWidgetsWithWindowSize(
542584
'path view',
543585
windowSize,

packages/devtools_shared/lib/src/deeplink/app_link_settings.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ class AndroidDeeplink {
6060

6161
factory AndroidDeeplink._fromJsonObject(Map<String, dynamic> json) {
6262
return AndroidDeeplink._(
63-
json[_kSchemeKey] as String,
64-
json[_kHostKey] as String,
63+
json[_kSchemeKey] as String?,
64+
json[_kHostKey] as String?,
6565
json[_kPathKey] as String,
6666
IntentFilterChecks._fromJsonObject(
6767
json[_kIntentFilterChecksKey] as Map<String, dynamic>,
@@ -75,10 +75,10 @@ class AndroidDeeplink {
7575
static const _kIntentFilterChecksKey = 'intentFilterCheck';
7676

7777
/// The scheme section of the deeplink.
78-
final String scheme;
78+
final String? scheme;
7979

8080
/// The host section of the deeplink.
81-
final String host;
81+
final String? host;
8282

8383
/// The path pattern section of the deeplink.
8484
final String path;

0 commit comments

Comments
 (0)