[deep link] Add more domain error types  (#7156)

* 1

* 2

* 1

* lint

* expansiontile

* string update

* resolve comments

* Update deep_links_screen_test.dart

* resolve comments

* UI update

* lint

* Update validation_details_view.dart

* lint
diff --git a/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_controller.dart b/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_controller.dart
index 7c990f4..d6dfd10 100644
--- a/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_controller.dart
+++ b/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_controller.dart
@@ -15,6 +15,17 @@
 import 'deep_links_services.dart';
 
 typedef _DomainAndPath = ({String domain, String path});
+const domainErrorsThatCanBeFixedByGeneratedJson = {
+  DomainError.existence,
+  DomainError.appIdentifier,
+  DomainError.fingerprints,
+  DomainError.contentType,
+};
+const domainErrorsThatCanNotBeFixedByGeneratedJson = {
+  DomainError.httpsAccessibility,
+  DomainError.nonRedirect,
+  DomainError.hostForm,
+};
 
 /// The phase of the deep link page.
 enum PagePhase {
@@ -135,6 +146,8 @@
   }
 
   DisplayOptions get displayOptions => displayOptionsNotifier.value;
+  String get applicationId =>
+      _androidAppLinks[selectedVariantIndex.value]?.applicationId ?? '';
 
   List<LinkData> get getLinkDatasByPath {
     final linkDatasByPath = <String, LinkData>{};
@@ -240,7 +253,8 @@
 
   List<LinkData>? allValidatedLinkDatas;
   final displayLinkDatasNotifier = ValueNotifier<List<LinkData>?>(null);
-  final generatedAssetLinksForSelectedLink = ValueNotifier<String?>(null);
+  final generatedAssetLinksForSelectedLink =
+      ValueNotifier<GenerateAssetLinksResult?>(null);
 
   final displayOptionsNotifier =
       ValueNotifier<DisplayOptions>(DisplayOptions());
@@ -250,9 +264,7 @@
   final deepLinksServices = DeepLinksServices();
 
   Future<void> _generateAssetLinks() async {
-    final applicationId =
-        _androidAppLinks[selectedVariantIndex.value]?.applicationId ?? '';
-
+    generatedAssetLinksForSelectedLink.value = null;
     generatedAssetLinksForSelectedLink.value =
         await deepLinksServices.generateAssetLinks(
       domain: selectedLink.value!.domain,
@@ -272,9 +284,6 @@
         .toSet()
         .toList();
 
-    final applicationId =
-        _androidAppLinks[selectedVariantIndex.value]?.applicationId ?? '';
-
     late final Map<String, List<DomainError>> domainErrors;
 
     try {
@@ -289,10 +298,11 @@
     }
 
     return linkdatas.map((linkdata) {
-      if (domainErrors[linkdata.domain]?.isNotEmpty ?? false) {
+      final errors = domainErrors[linkdata.domain];
+      if (errors != null && errors.isNotEmpty) {
         return LinkData(
           domain: linkdata.domain,
-          domainErrors: domainErrors[linkdata.domain]!,
+          domainErrors: errors,
           path: linkdata.path,
           pathError: linkdata.pathError,
           os: linkdata.os,
diff --git a/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_model.dart b/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_model.dart
index bffca5c..68f92eb 100644
--- a/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_model.dart
+++ b/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_model.dart
@@ -24,13 +24,82 @@
   final String description;
 }
 
-// TODO(hangyujin): Handle more domain error cases.
 enum DomainError {
-  existence('Domain doesn\'t exist'),
-  fingerprints('Fingerprints unavailable');
+  // Existence of an asset link file.
+  existence(
+    'Digital Asset Links JSON file existence failed',
+    'This test checks whether the assetlinks.json file, '
+        'which is used to verify the association between the app and the '
+        'domain name, exists under your domain.',
+    'Add a Digital Asset Links JSON file to all of the '
+        'failed website domains at the following location: '
+        'https://[domain.name]/.well-known/assetlinks.json. See the following recommended asset link json file. ',
+  ),
+  // Asset link file should define a link to this app.
+  appIdentifier(
+    'Package name failed',
+    'The test checks your Digital Asset Links JSON file '
+        'for package name validation, which the mobile device '
+        'uses to verify ownership of the app.',
+    'Ensure your Digital Asset Links JSON file declares the '
+        'correct package name with the "android_app" namespace for '
+        'all of the failed website domains. Also, confirm that the '
+        'app is available in the Google Play store. See the following recommended asset link json file. ',
+  ),
+  // Asset link file should contain the correct fingerprint.
+  fingerprints(
+    'Fingerprint validation failed',
+    'This test checks your Digital Asset Links JSON file for '
+        'sha256 fingerprint validation, which the mobile device uses '
+        'to verify ownership of the app.',
+    'Add sha256_cert_fingerprints to the Digital Asset Links JSON '
+        'file for all of the failed website domains. If the fingerprint '
+        'has already been added, make sure it\'s correct and that the '
+        '"android_app" namespace is declared on it. See the following recommended asset link json file. ',
+  ),
+  // Asset link file should be served with the correct content type.
+  contentType(
+    'JSON content type failed',
+    'This test checks your Digital Asset Links JSON file for content type '
+        'validation, which defines the format of the JSON file. This allows '
+        'the mobile device to verify ownership of the app.',
+    'Ensure the content-type is "application/json" for all of the failed website domains. See the following recommended asset link json file. ',
+  ),
+  // Asset link file should be accessible via https.
+  httpsAccessibility(
+    'HTTPS accessibility failed',
+    'This test tries to access your Digital Asset Links '
+        'JSON file over an HTTPS connection, which must be '
+        'accessible to verify ownership of the app.',
+    'Ensure your Digital Asset Links JSON file is accessible '
+        'over an HTTPS connection for all of the failed website domains (even if '
+        'the app\'s intent filter declares HTTP as the data scheme).',
+  ),
 
-  const DomainError(this.description);
-  final String description;
+  // Asset link file should be accessible with no redirects.
+  nonRedirect(
+    'Domain non-redirect failed',
+    'This test checks that your domain is accessible without '
+        'redirects. This domain must be directly accessible '
+        'to verify ownership of the app.',
+    'Ensure your domain is accessible without any redirects ',
+  ),
+
+  // Asset link domain should be valid/not malformed.
+  hostForm(
+    'Host attribute formed properly failed',
+    'This test checks that your android:host attribute has a valid domain URL pattern.',
+    'Make sure the host is a properly formed web address such '
+        'as google.com or www.google.com, without "http://" or "https://".',
+  ),
+  // Issues that are not covered by other checks. An example that may be in this
+  // category is Android validation API failures.
+  other('Check failed', '', '');
+
+  const DomainError(this.title, this.explanation, this.fixDetails);
+  final String title;
+  final String explanation;
+  final String fixDetails;
 }
 
 /// Contains all data relevant to a deep link.
diff --git a/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_services.dart b/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_services.dart
index 99e6ee1..3edf4eb 100644
--- a/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_services.dart
+++ b/packages/devtools_app/lib/src/screens/deep_link_validation/deep_links_services.dart
@@ -24,8 +24,23 @@
 const String _checkNameKey = 'checkName';
 const String _failedChecksKey = 'failedChecks';
 const String _generatedContentKey = 'generatedContent';
-const String _existenceCheckKey = 'EXISTENCE';
-const String _fingerPrintChecktKey = 'FINGERPRINT';
+
+const Map<String, DomainError> checkNameToDomainError = {
+  'EXISTENCE': DomainError.existence,
+  'APP_IDENTIFIER': DomainError.appIdentifier,
+  'FINGERPRINT': DomainError.fingerprints,
+  'CONTENT_TYPE': DomainError.contentType,
+  'HTTPS_ACCESSIBILITY': DomainError.httpsAccessibility,
+  'NON_REDIRECT': DomainError.nonRedirect,
+  'HOST_FORMED_PROPERLY': DomainError.hostForm,
+  'OTHER_CHECKS': DomainError.other,
+};
+
+class GenerateAssetLinksResult {
+  GenerateAssetLinksResult(this.errorCode, this.generatedString);
+  String errorCode;
+  String generatedString;
+}
 
 class DeepLinksServices {
   Future<Map<String, List<DomainError>>> validateAndroidDomain({
@@ -54,11 +69,10 @@
       final List? failedChecks = domainResult[_failedChecksKey];
       if (failedChecks != null) {
         for (final Map<String, dynamic> failedCheck in failedChecks) {
-          switch (failedCheck[_checkNameKey]) {
-            case _existenceCheckKey:
-              domainErrors[domainName]!.add(DomainError.existence);
-            case _fingerPrintChecktKey:
-              domainErrors[domainName]!.add(DomainError.fingerprints);
+          final checkName = failedCheck[_checkNameKey];
+          final domainError = checkNameToDomainError[checkName];
+          if (domainError != null) {
+            domainErrors[domainName]!.add(domainError);
           }
         }
       }
@@ -66,7 +80,7 @@
     return domainErrors;
   }
 
-  Future<String> generateAssetLinks({
+  Future<GenerateAssetLinksResult> generateAssetLinks({
     required String applicationId,
     required String domain,
   }) async {
@@ -77,22 +91,19 @@
         {
           _packageNameKey: applicationId,
           _domainsKey: [domain],
-          // TODO(hangyujin): Handle the error case when user doesn't have play console project set up.
         },
       ),
     );
     final Map<String, dynamic> result =
         json.decode(response.body) as Map<String, dynamic>;
+    final String errorCode = result[_errorCodeKey] ?? '';
+    String generatedContent = '';
 
-    if (result[_errorCodeKey] != null) {
-      return 'Content generation failed.\n Reason: ${result[_errorCodeKey]}';
-    }
     if (result[_domainsKey] != null) {
-      final String generatedContent = (((result[_domainsKey] as List).first)
+      generatedContent = (((result[_domainsKey] as List).first)
           as Map<String, dynamic>)[_generatedContentKey];
-
-      return generatedContent;
     }
-    return '';
+
+    return GenerateAssetLinksResult(errorCode, generatedContent);
   }
 }
diff --git a/packages/devtools_app/lib/src/screens/deep_link_validation/validation_details_view.dart b/packages/devtools_app/lib/src/screens/deep_link_validation/validation_details_view.dart
index 0656718..13f4518 100644
--- a/packages/devtools_app/lib/src/screens/deep_link_validation/validation_details_view.dart
+++ b/packages/devtools_app/lib/src/screens/deep_link_validation/validation_details_view.dart
@@ -2,16 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
 import 'package:devtools_app_shared/ui.dart';
+import 'package:devtools_app_shared/utils.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
 import '../../shared/common_widgets.dart';
+import '../../shared/config_specific/launch_url/launch_url.dart';
 import '../../shared/table/table.dart';
 import '../../shared/ui/colors.dart';
 import 'deep_link_list_view.dart';
 import 'deep_links_controller.dart';
 import 'deep_links_model.dart';
+import 'deep_links_services.dart';
 
 class ValidationDetailView extends StatelessWidget {
   const ValidationDetailView({
@@ -39,10 +44,10 @@
             crossAxisAlignment: CrossAxisAlignment.start,
             children: [
               Text(
-                'This tool assistants helps you diagnose Universal Links, App Links,'
-                ' and Custom Schemes in your app. Web check are done for the web association'
+                'This tool helps you diagnose issues with App Links in your application.'
+                'Web checks are done for the web association'
                 ' file on your website. App checks are done for the intent filters in'
-                ' the manifest and info.plist file, routing issues, URL format, etc.',
+                ' the manifest and info.plist files, routing issues, URL format, etc.',
                 style: Theme.of(context).subtleTextStyle,
               ),
               if (viewType == TableViewType.domainView ||
@@ -54,13 +59,14 @@
                   viewType == TableViewType.singleUrlView)
                 _PathCheckTable(),
               const SizedBox(height: largeSpacing),
-              Align(
-                alignment: Alignment.bottomRight,
-                child: FilledButton(
-                  onPressed: () async => await controller.validateLinks(),
-                  child: const Text('Recheck all'),
+              if (linkData.domainErrors.isNotEmpty)
+                Align(
+                  alignment: Alignment.bottomRight,
+                  child: FilledButton(
+                    onPressed: () async => await controller.validateLinks(),
+                    child: const Text('Recheck all'),
+                  ),
                 ),
-              ),
               if (viewType == TableViewType.domainView)
                 _DomainAssociatedLinksPanel(controller: controller),
             ],
@@ -119,18 +125,19 @@
   @override
   Widget build(BuildContext context) {
     final linkData = controller.selectedLink.value!;
+    final theme = Theme.of(context);
     return Column(
       crossAxisAlignment: CrossAxisAlignment.stretch,
       children: [
         const SizedBox(height: intermediateSpacing),
-        Text('Domain check', style: Theme.of(context).textTheme.titleSmall),
+        Text('Web check', style: theme.textTheme.titleSmall),
         const SizedBox(height: denseSpacing),
         DataTable(
           headingRowColor: MaterialStateProperty.all(
-            Theme.of(context).colorScheme.deeplinkTableHeaderColor,
+            theme.colorScheme.deeplinkTableHeaderColor,
           ),
           dataRowColor: MaterialStateProperty.all(
-            Theme.of(context).colorScheme.alternatingBackgroundColor2,
+            theme.colorScheme.alternatingBackgroundColor2,
           ),
           columns: const [
             DataColumn(label: Text('OS')),
@@ -149,15 +156,16 @@
                   DataCell(
                     linkData.domainErrors.isNotEmpty
                         ? Text(
-                            'Check failed',
+                            '${linkData.domainErrors.length} '
+                            '${pluralize('Check', linkData.domainErrors.length)} failed',
                             style: TextStyle(
-                              color: Theme.of(context).colorScheme.error,
+                              color: theme.colorScheme.error,
                             ),
                           )
                         : Text(
                             'No issues found',
                             style: TextStyle(
-                              color: Theme.of(context).colorScheme.green,
+                              color: theme.colorScheme.green,
                             ),
                           ),
                   ),
@@ -171,8 +179,7 @@
                   DataCell(
                     Text(
                       'No issues found',
-                      style:
-                          TextStyle(color: Theme.of(context).colorScheme.green),
+                      style: TextStyle(color: theme.colorScheme.green),
                     ),
                   ),
                 ],
@@ -206,62 +213,28 @@
         child: Column(
           crossAxisAlignment: CrossAxisAlignment.stretch,
           children: [
-            const Text('How to fix:'),
-            Text(
-              'Add the new recommended Digital Asset Links JSON file to the failed website domain at the correct location.\n'
-              'Update and publish recommend Digital Asset Links JSON file below to this location: ',
-              style: Theme.of(context).subtleTextStyle,
-            ),
+            _FailureDetails(linkData: linkData),
+            if (linkData.domainErrors.any(
+              (error) =>
+                  domainErrorsThatCanBeFixedByGeneratedJson.contains(error),
+            ))
+              _GenerateAssetLinksPanel(controller: controller),
             Align(
               alignment: Alignment.centerLeft,
-              child: Card(
-                shape: const RoundedRectangleBorder(
-                  borderRadius: BorderRadius.all(Radius.circular(4.0)),
-                ),
-                color: Theme.of(context).colorScheme.outline,
-                child: Padding(
-                  padding: const EdgeInsets.symmetric(horizontal: denseSpacing),
-                  child: SelectionArea(
-                    child: Text(
-                      'https://${linkData.domain}/.well-known/assetlinks.json',
-                      style: Theme.of(context).regularTextStyle.copyWith(
-                            color: Colors.black,
-                            fontWeight: FontWeight.w500,
-                          ),
+              child: TextButton(
+                onPressed: () {
+                  unawaited(
+                    launchUrl(
+                      'https://developer.android.com/training/app-links/verify-android-applinks',
                     ),
-                  ),
+                  );
+                },
+                style: const ButtonStyle().copyWith(
+                  textStyle: MaterialStateProperty.resolveWith<TextStyle>((_) {
+                    return Theme.of(context).textTheme.bodySmall!;
+                  }),
                 ),
-              ),
-            ),
-            Card(
-              color: Theme.of(context).colorScheme.surface,
-              child: Padding(
-                padding: const EdgeInsets.all(denseSpacing),
-                child: ValueListenableBuilder(
-                  valueListenable:
-                      controller.generatedAssetLinksForSelectedLink,
-                  builder: (_, String? generatedAssetLinks, __) =>
-                      generatedAssetLinks != null
-                          ? Row(
-                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                              crossAxisAlignment: CrossAxisAlignment.start,
-                              children: [
-                                Flexible(
-                                  child: SelectionArea(
-                                    child: Text(generatedAssetLinks),
-                                  ),
-                                ),
-                                IconButton(
-                                  onPressed: () async =>
-                                      await Clipboard.setData(
-                                    ClipboardData(text: generatedAssetLinks),
-                                  ),
-                                  icon: const Icon(Icons.copy_rounded),
-                                ),
-                              ],
-                            )
-                          : const CenteredCircularProgressIndicator(),
-                ),
+                child: const Text('View developer guide'),
               ),
             ),
           ],
@@ -271,6 +244,158 @@
   }
 }
 
+class _GenerateAssetLinksPanel extends StatelessWidget {
+  const _GenerateAssetLinksPanel({
+    required this.controller,
+  });
+
+  final DeepLinksController controller;
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = Theme.of(context);
+    return ValueListenableBuilder(
+      valueListenable: controller.generatedAssetLinksForSelectedLink,
+      builder: (
+        _,
+        GenerateAssetLinksResult? generatedAssetLinks,
+        __,
+      ) {
+        return Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            const Divider(),
+            const Text('Recommended Asset Links Json file :'),
+            const SizedBox(height: denseSpacing),
+            (generatedAssetLinks != null &&
+                    generatedAssetLinks.errorCode.isNotEmpty)
+                ? Text(
+                    'Not able to generate assetlinks.json, because the app ${controller.applicationId} is not uploaded to Google Play.',
+                    style: theme.subtleTextStyle,
+                  )
+                : Column(
+                    children: [
+                      Card(
+                        color: theme.colorScheme.alternatingBackgroundColor1,
+                        surfaceTintColor: Colors.transparent,
+                        elevation: 0.0,
+                        child: Padding(
+                          padding: const EdgeInsets.all(denseSpacing),
+                          child: generatedAssetLinks != null
+                              ? Row(
+                                  mainAxisAlignment:
+                                      MainAxisAlignment.spaceBetween,
+                                  crossAxisAlignment: CrossAxisAlignment.start,
+                                  children: [
+                                    Flexible(
+                                      child: SelectionArea(
+                                        child: Text(
+                                          generatedAssetLinks.generatedString,
+                                        ),
+                                      ),
+                                    ),
+                                    IconButton(
+                                      onPressed: () async =>
+                                          await Clipboard.setData(
+                                        ClipboardData(
+                                          text: generatedAssetLinks
+                                              .generatedString,
+                                        ),
+                                      ),
+                                      icon: const Icon(Icons.copy_rounded),
+                                    ),
+                                  ],
+                                )
+                              : const CenteredCircularProgressIndicator(),
+                        ),
+                      ),
+                      const SizedBox(height: denseSpacing),
+                      Text(
+                        'Update and publish this new recommended Digital Asset Links JSON file below at this location:',
+                        style: theme.subtleTextStyle,
+                      ),
+                      const SizedBox(height: denseSpacing),
+                      Align(
+                        alignment: Alignment.centerLeft,
+                        child: Card(
+                          shape: const RoundedRectangleBorder(
+                            borderRadius:
+                                BorderRadius.all(Radius.circular(4.0)),
+                          ),
+                          color: theme.colorScheme.outline,
+                          child: Padding(
+                            padding: const EdgeInsets.symmetric(
+                              horizontal: denseSpacing,
+                            ),
+                            child: SelectionArea(
+                              child: Text(
+                                'https://${controller.selectedLink.value!.domain}/.well-known/assetlinks.json',
+                                style: theme.regularTextStyle.copyWith(
+                                  color: Colors.black,
+                                  fontWeight: FontWeight.w500,
+                                ),
+                              ),
+                            ),
+                          ),
+                        ),
+                      ),
+                      const SizedBox(height: denseSpacing),
+                    ],
+                  ),
+          ],
+        );
+      },
+    );
+  }
+}
+
+class _FailureDetails extends StatelessWidget {
+  const _FailureDetails({
+    required this.linkData,
+  });
+
+  final LinkData linkData;
+
+  @override
+  Widget build(BuildContext context) {
+    final errorCount = linkData.domainErrors.length;
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        for (var i = 0; i < errorCount; i++)
+          Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              const SizedBox(height: densePadding),
+              Row(
+                children: [
+                  Icon(
+                    Icons.error,
+                    color: Theme.of(context).colorScheme.error,
+                    size: defaultIconSize,
+                  ),
+                  const SizedBox(width: denseSpacing),
+                  Text('Issue ${i + 1} : ${linkData.domainErrors[i].title}'),
+                ],
+              ),
+              const SizedBox(height: densePadding),
+              Padding(
+                padding: EdgeInsets.only(
+                  left: defaultIconSize + denseSpacing,
+                ),
+                child: Text(
+                  linkData.domainErrors[i].explanation +
+                      linkData.domainErrors[i].fixDetails,
+                  style: Theme.of(context).subtleTextStyle,
+                ),
+              ),
+            ],
+          ),
+      ],
+    );
+  }
+}
+
 class _DomainAssociatedLinksPanel extends StatelessWidget {
   const _DomainAssociatedLinksPanel({
     required this.controller,
@@ -280,16 +405,17 @@
 
   @override
   Widget build(BuildContext context) {
+    final theme = Theme.of(context);
     final linkData = controller.selectedLink.value!;
     return Column(
       crossAxisAlignment: CrossAxisAlignment.stretch,
       children: [
         Text(
           'Associated deep link URL',
-          style: Theme.of(context).textTheme.titleSmall,
+          style: theme.textTheme.titleSmall,
         ),
         Card(
-          color: Theme.of(context).colorScheme.surface,
+          color: theme.colorScheme.surface,
           shape: const RoundedRectangleBorder(),
           child: Padding(
             padding: const EdgeInsets.all(denseSpacing),
@@ -306,7 +432,7 @@
                           if (linkData.domainErrors.isNotEmpty)
                             Icon(
                               Icons.error,
-                              color: Theme.of(context).colorScheme.error,
+                              color: theme.colorScheme.error,
                               size: defaultIconSize,
                             ),
                           const SizedBox(width: denseSpacing),
@@ -327,11 +453,12 @@
 class _PathCheckTable extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
+    final theme = Theme.of(context);
     final notAvailableCell = DataCell(
       Text(
         'Not available',
         style: TextStyle(
-          color: Theme.of(context).colorScheme.deeplinkUnavailableColor,
+          color: theme.colorScheme.deeplinkUnavailableColor,
         ),
       ),
     );
@@ -341,7 +468,7 @@
         const SizedBox(height: intermediateSpacing),
         Text(
           'Path check (coming soon)',
-          style: Theme.of(context).textTheme.titleSmall,
+          style: theme.textTheme.titleSmall,
         ),
         Opacity(
           opacity: 0.5,
@@ -350,10 +477,10 @@
             dataRowMinHeight: defaultRowHeight,
             dataRowMaxHeight: defaultRowHeight,
             headingRowColor: MaterialStateProperty.all(
-              Theme.of(context).colorScheme.deeplinkTableHeaderColor,
+              theme.colorScheme.deeplinkTableHeaderColor,
             ),
             dataRowColor: MaterialStateProperty.all(
-              Theme.of(context).colorScheme.alternatingBackgroundColor2,
+              theme.colorScheme.alternatingBackgroundColor2,
             ),
             columns: const [
               DataColumn(label: Text('OS')),
diff --git a/packages/devtools_app/test/deep_link_vlidation/deep_links_screen_test.dart b/packages/devtools_app/test/deep_link_vlidation/deep_links_screen_test.dart
index 3f1bd71..208c432 100644
--- a/packages/devtools_app/test/deep_link_vlidation/deep_links_screen_test.dart
+++ b/packages/devtools_app/test/deep_link_vlidation/deep_links_screen_test.dart
@@ -5,6 +5,7 @@
 import 'package:devtools_app/devtools_app.dart';
 import 'package:devtools_app/src/screens/deep_link_validation/deep_link_list_view.dart';
 import 'package:devtools_app/src/screens/deep_link_validation/deep_links_model.dart';
+import 'package:devtools_app/src/screens/deep_link_validation/deep_links_services.dart';
 import 'package:devtools_app/src/screens/deep_link_validation/validation_details_view.dart';
 import 'package:devtools_app/src/shared/directory_picker.dart';
 import 'package:devtools_app_shared/ui.dart';
@@ -491,7 +492,10 @@
   void selectLink(LinkData linkdata) async {
     selectedLink.value = linkdata;
     if (linkdata.domainErrors.isNotEmpty) {
-      generatedAssetLinksForSelectedLink.value = 'fake generated content';
+      generatedAssetLinksForSelectedLink.value = GenerateAssetLinksResult(
+        '',
+        'fake generated content',
+      );
     }
   }
 }