Skip to content

Conversation

@ShashwatXD
Copy link
Contributor

@ShashwatXD ShashwatXD commented Jan 28, 2026

Fixes #482

Describe the changes you have made in this PR -

Added a Deep Link Manager using the app_links dependency.

  • Now, when a user opens a supported (shareable) link and the app is already installed, it correctly opens inside the app instead of the browser.
  • To make this work completely, we need to host our SHA key on the original website as required for app link verification. Related issue: Add assetlinks.json for Android App Links support CircuitVerse#6720

Screenshots of the changes (If any) -
A demonstration video on how the links would work (in the video i have used adb for deeplinks but once assets is hosted, if we click on any link example: "https://circuitverse.org/simulator/embed/5010", it would open our app the same way as in video.

Screen.Recording.2026-01-28.at.21.18.25.mp4

Note: Please check Allow edits from maintainers. if you would like us to assist in the PR.

Summary by CodeRabbit

  • New Features

    • Adds deep linking so users can open projects and simulator views directly from circuitverse.org URLs (including edit and embed modes).
    • Deep links support navigating to project details pages from shared links.
  • Chores

    • Added runtime link handling dependency.
    • Updated CI/workflow Flutter versions.

✏️ Tip: You can customize this high-level summary in your review settings.

@netlify
Copy link

netlify bot commented Jan 28, 2026

Deploy Preview for cv-mobile-app-web ready!

Name Link
🔨 Latest commit 97d0d7e
🔍 Latest deploy log https://app.netlify.com/projects/cv-mobile-app-web/deploys/697a390fcd47b80008f511f7
😎 Deploy Preview https://deploy-preview-486--cv-mobile-app-web.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

Adds Android App Links deep-link support: updates AndroidManifest with intent-filter for circuitverse.org, adds app_links dependency, introduces DeepLinkManager service (init/dispose, link handling, route generation), registers and initializes it in the service locator and main, updates router to delegate route generation to DeepLinkManager, and extends simulator-related code (SimulatorView/SimulatorViewModel and Project model factory) to support navigation for /simulator/edit, /simulator/embed, and /users/:userId/projects/:projectId paths.

Possibly related PRs

  • Project re-editing feature #409: Changes simulator routing and SimulatorViewModel argument handling to accept and forward project and embed parameters.
  • Improve UX #433: Refactors router route-construction helper (_buildRoute → buildRoute) affecting CVRouter's route-building implementation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title "Deeplinks manager" is vague and generic, using non-descriptive terminology that doesn't clearly convey what was implemented or the specific changes made. Consider using a more specific title like "Add deep link manager for Android App Links" to clearly indicate the feature being added and its purpose.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR implements all core requirements from issue #482: Android App Links configuration, deep link handling for project URLs and simulator routes, navigation to appropriate screens, and preservation of web fallback behavior.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the deep link manager feature. Modifications to SimulatorView, routing, and service registration are necessary supporting changes for deep link functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@android/app/src/main/AndroidManifest.xml`:
- Around line 71-81: The intent-filter currently matches all paths on
circuitverse.org; restrict it by adding android:pathPrefix attributes for the
supported routes so only those URLs open the app. Update the <intent-filter>
data elements (for both schemes and both hosts currently declared) to include
android:pathPrefix="/simulator/edit/", android:pathPrefix="/simulator/embed/",
and android:pathPrefix="/users/" (to cover /users/*/projects/) on each relevant
<data> entry so DeepLinkManager only receives supported paths.

In `@lib/models/projects.dart`:
- Around line 37-55: The factory Project.idOnly currently accepts an empty id
which creates an unusable placeholder; update Project.idOnly to validate the id
at the start (e.g., assert or throw ArgumentError/StateError when id.isEmpty) so
callers cannot construct a Project with an empty id, and include a clear error
message referencing the invalid id to aid debugging; keep the rest of the
construction (attributes/relationships) unchanged.

In `@lib/services/deep_link_manager.dart`:
- Around line 26-33: The _checkInitialLink method currently swallows errors from
_appLinks.getInitialLink() with an empty catch; update the catch to log the
error instead of ignoring it—catch the exception (e.g., catch (e, st)) and call
the package's logger (or debugPrint/print) to emit a clear message including the
exception and stack trace, keeping the rest of _checkInitialLink (including
calling _handleLink(uri)) intact; reference _checkInitialLink,
_appLinks.getInitialLink, and _handleLink when making the change.
- Around line 108-116: generateRoute currently returns a Scaffold with only a
CircularProgressIndicator for the "/users/:userId/projects/:projectId" pattern,
leaving the app stuck in a perpetual loading state; fix by either removing that
pattern from generateRoute (so the link is handled exclusively by the
stream-based navigator in _handleLink and _fetchAndNavigateToProject) or
implement an async load path here that triggers the same behavior as _handleLink
(invoke the project fetch/navigation flow used by _fetchAndNavigateToProject and
then return an appropriate route or null). Locate the pattern match in
generateRoute and choose one of the two approaches: delete the block or replace
it so it delegates to the existing _fetchAndNavigateToProject logic (or returns
null to let standard routing proceed).
- Around line 73-83: In _fetchAndNavigateToProject, when
locator<ProjectsApi>().getProjectDetails(projectId) returns null you currently
do nothing; update the method to detect a null project and show user feedback
(e.g., call SnackBarUtils.showDark with a suitable title/message) and avoid
calling Get.offNamed(ProjectDetailsView.id) when project is null, mirroring the
existing catch-block behavior so the user is informed when a project is not
found.

In `@lib/ui/views/simulator/simulator_view.dart`:
- Around line 24-39: Get.arguments is being cast unsafely to Project and Map
entries are force-cast; change the extraction to validate types before casting
to avoid runtime errors: check the runtime type of Get.arguments (use "is
Project" and "is Map" checks on args and for map entries check "is Project" /
"is bool" or safely parse Map<String, dynamic>), assign to project only when the
value is actually a Project and set isEmbed only when the map value is a bool
(fallback to false otherwise), and then call model.onModelReady(project,
isEmbed: isEmbed) as before; refer to the symbols Get.arguments, project,
isEmbed, and BaseView<SimulatorViewModel>::onModelReady to locate where to apply
these defensive checks.

In `@pubspec.yaml`:
- Line 66: The pubspec dependency for app_links is incompatible with the
project's Dart SDK; replace the current dependency declaration "app_links:
^6.4.1" in pubspec.yaml with "app_links: 3.5.1" so the package resolves against
Flutter 3.7.x / Dart 2.19.x—locate the app_links entry in pubspec.yaml and
update its version constraint accordingly, then run flutter pub get to verify
resolution.
🧹 Nitpick comments (4)
lib/utils/router.dart (1)

107-107: Consider keeping buildRoute internal or documenting its public use.

Renaming _buildRoute to buildRoute makes this a public API. This is necessary for DeepLinkManager to use it, but consider adding documentation to clarify its intended usage, or using @visibleForTesting / @internal annotations if it should remain implementation-scoped.

lib/services/deep_link_manager.dart (3)

8-8: Unused import.

DialogService is imported but never used in this file.

-import 'package:mobile_app/services/dialog_service.dart';

18-24: Consider awaiting _checkInitialLink or handling potential race condition.

_checkInitialLink() is async but called without await in init(). If the initial link check completes after the stream subscription processes a duplicate URI emission, navigation could occur twice. While AppLinks typically doesn't emit the initial link on the stream, this behavior can vary by platform.

Option: Make init() async and await the initial check
-  void init() {
+  Future<void> init() async {
-    _checkInitialLink();
+    await _checkInitialLink();

     _linkSubscription = _appLinks.uriLinkStream.listen((uri) {
       _handleLink(uri);
     });
   }

Then update the caller in main.dart to await locator<DeepLinkManager>().init().


39-42: Consider case-insensitive host comparison.

Host names are case-insensitive per RFC. While browsers typically normalize to lowercase, it's safer to compare using toLowerCase() to handle edge cases.

-    if (uri.host != 'circuitverse.org' && uri.host != 'www.circuitverse.org') {
+    final host = uri.host.toLowerCase();
+    if (host != 'circuitverse.org' && host != 'www.circuitverse.org') {
       return;
     }

Comment on lines +71 to +81

<!-- Deep Linking -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="circuitverse.org" />
<data android:host="www.circuitverse.org" />
</intent-filter>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Android App Links pathPrefix pathPattern best practices scoping specific URL paths

💡 Result:

Best practices for scoping Android App Links to specific URL paths

1) Scope paths in the manifest, not in assetlinks.json

For Android App Links, the Digital Asset Links statement typically uses delegate_permission/common.handle_all_urls, which delegates all URLs the app can handle—so the real scoping lever is your app’s <intent-filter> path matching (and optionally dynamic rules on newer Android). [1][2]

2) Prefer the narrowest matcher that works

Android supports these path match types in <data>: path (exact), pathPrefix (starts-with), pathSuffix (ends-with), pathPattern (simple glob), and pathAdvancedPattern (more regex-like). [3]
Recommended order for safety/clarity:

  • Use android:path when the route is fixed (most precise). [3]
  • Use android:pathPrefix for “all pages under /products/”. [3]
  • Avoid android:pathPattern unless you must, because its wildcard rules are easy to misunderstand (e.g., * repeats the previous character, and .* is the “any string” form). [3][4]
  • Only use android:pathAdvancedPattern if you truly need richer matching (character classes, ranges, +, {m,n}, etc.). [3]

3) Don’t mix unrelated <data> entries in one <intent-filter>

Multiple <data> elements inside the same <intent-filter> are effectively merged, and attributes can combine in ways that are not intuitive (it’s not “paired rows”; it becomes a broader OR-set). This can accidentally widen what you match. Prefer separate intent-filters per host/scheme/path family. [3]

4) Use separate filters for distinct “jobs” (and keep exported explicit)

Android recommends separate filters for each unique job a component can do, which also maps well to separating link groups (e.g., /products/* vs /help/*). Also, for Android 12+, ensure android:exported is explicitly set on components with intent filters. [5]

5) Keep host/scheme tight; avoid catching more than you own

At minimum, include:

  • android:scheme="https"
  • the exact android:host="example.com" (and www variant if needed)
    Then add only the path scopes you actually support. (If you omit paths entirely, you’re effectively claiming the whole site for that host.) [3][4]

6) If multiple apps share one domain, split by path

A single website can be associated with multiple apps, and each app should claim only its relevant path space via intent filters (e.g., one app handles /articles, another handles /videos). [1]

Sources: Android Developers docs for Digital Asset Links / App Links and intent filter URI matching. [1][2][3][4][5]


🏁 Script executed:

# Search for deep link handling in the codebase
find . -type f -name "*.kt" -o -name "*.java" | head -20

Repository: CircuitVerse/mobile-app

Length of output: 140


🏁 Script executed:

# Look for DeepLinkManager or similar deep link handler
rg -i "deeplink|deep.?link|intent.?filter" --type kotlin --type java -l

Repository: CircuitVerse/mobile-app

Length of output: 49


🏁 Script executed:

# Check for other manifest or configuration files related to deep linking
git ls-files | grep -iE "(manifest|deeplink|route)" | head -20

Repository: CircuitVerse/mobile-app

Length of output: 268


🏁 Script executed:

# Examine MainActivity.kt for deep link handling
cat -n android/app/src/main/kotlin/org/circuitverse/mobile_app/MainActivity.kt

Repository: CircuitVerse/mobile-app

Length of output: 4896


🏁 Script executed:

# Check the router configuration to see what paths are actually supported
cat -n lib/utils/router.dart

Repository: CircuitVerse/mobile-app

Length of output: 6939


🏁 Script executed:

# Check the main AndroidManifest.xml to understand the current full structure
cat -n android/app/src/main/AndroidManifest.xml

Repository: CircuitVerse/mobile-app

Length of output: 5604


🏁 Script executed:

# Find DeepLinkManager file
find . -name "*deep*link*" -type f

Repository: CircuitVerse/mobile-app

Length of output: 104


🏁 Script executed:

# Search for DeepLinkManager class definition
rg -i "class.*deeplink" --type dart -A 30

Repository: CircuitVerse/mobile-app

Length of output: 1860


🏁 Script executed:

# Get full DeepLinkManager implementation
cat -n lib/services/deep_link_manager.dart

Repository: CircuitVerse/mobile-app

Length of output: 4512


Scope app links to supported paths to avoid dead-end navigation.

The intent-filter currently captures all circuitverse.org URLs without path restrictions. DeepLinkManager only handles /simulator/edit/, /simulator/embed/, and /users/*/projects/ paths; other URLs will route to the app and display an error instead of falling back to the browser. Add android:pathPrefix to limit the intent-filter to paths the app actually supports.

Example: scoping to supported paths
-            <intent-filter android:autoVerify="true">
+            <intent-filter android:autoVerify="true">
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="circuitverse.org" />
-                <data android:host="www.circuitverse.org" />
+                <data android:scheme="https"
+                      android:host="circuitverse.org"
+                      android:pathPrefix="/simulator/" />
+                <data android:scheme="https"
+                      android:host="circuitverse.org"
+                      android:pathPrefix="/users/" />
+                <data android:scheme="https"
+                      android:host="www.circuitverse.org"
+                      android:pathPrefix="/simulator/" />
+                <data android:scheme="https"
+                      android:host="www.circuitverse.org"
+                      android:pathPrefix="/users/" />
             </intent-filter>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<!-- Deep Linking -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="circuitverse.org" />
<data android:host="www.circuitverse.org" />
</intent-filter>
<!-- Deep Linking -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="circuitverse.org"
android:pathPrefix="/simulator/" />
<data android:scheme="https"
android:host="circuitverse.org"
android:pathPrefix="/users/" />
<data android:scheme="https"
android:host="www.circuitverse.org"
android:pathPrefix="/simulator/" />
<data android:scheme="https"
android:host="www.circuitverse.org"
android:pathPrefix="/users/" />
</intent-filter>
🤖 Prompt for AI Agents
In `@android/app/src/main/AndroidManifest.xml` around lines 71 - 81, The
intent-filter currently matches all paths on circuitverse.org; restrict it by
adding android:pathPrefix attributes for the supported routes so only those URLs
open the app. Update the <intent-filter> data elements (for both schemes and
both hosts currently declared) to include android:pathPrefix="/simulator/edit/",
android:pathPrefix="/simulator/embed/", and android:pathPrefix="/users/" (to
cover /users/*/projects/) on each relevant <data> entry so DeepLinkManager only
receives supported paths.

Comment on lines +37 to +55
factory Project.idOnly(String id) => Project(
id: id,
type: 'project',
attributes: ProjectAttributes(
name: '',
projectAccessType: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
imagePreview: ImagePreview(url: ''),
view: 0,
tags: [],
isStarred: false,
authorName: '',
starsCount: 0,
),
relationships: ProjectRelationships(
author: Author(data: AuthorData(id: '', type: 'user')),
),
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against empty IDs in Project.idOnly.

An empty id will produce a placeholder that can’t resolve to a real project and may cause confusing failures later. Consider asserting non-empty input.

Suggested safeguard
-  factory Project.idOnly(String id) => Project(
+  factory Project.idOnly(String id) {
+    assert(id.isNotEmpty, 'Project.idOnly requires a non-empty id');
+    return Project(
       id: id,
       type: 'project',
       attributes: ProjectAttributes(
         name: '',
         projectAccessType: '',
         createdAt: DateTime.now(),
         updatedAt: DateTime.now(),
         imagePreview: ImagePreview(url: ''),
         view: 0,
         tags: [],
         isStarred: false,
         authorName: '',
         starsCount: 0,
       ),
       relationships: ProjectRelationships(
         author: Author(data: AuthorData(id: '', type: 'user')),
       ),
-  );
+    );
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
factory Project.idOnly(String id) => Project(
id: id,
type: 'project',
attributes: ProjectAttributes(
name: '',
projectAccessType: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
imagePreview: ImagePreview(url: ''),
view: 0,
tags: [],
isStarred: false,
authorName: '',
starsCount: 0,
),
relationships: ProjectRelationships(
author: Author(data: AuthorData(id: '', type: 'user')),
),
);
factory Project.idOnly(String id) {
assert(id.isNotEmpty, 'Project.idOnly requires a non-empty id');
return Project(
id: id,
type: 'project',
attributes: ProjectAttributes(
name: '',
projectAccessType: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
imagePreview: ImagePreview(url: ''),
view: 0,
tags: [],
isStarred: false,
authorName: '',
starsCount: 0,
),
relationships: ProjectRelationships(
author: Author(data: AuthorData(id: '', type: 'user')),
),
);
}
🤖 Prompt for AI Agents
In `@lib/models/projects.dart` around lines 37 - 55, The factory Project.idOnly
currently accepts an empty id which creates an unusable placeholder; update
Project.idOnly to validate the id at the start (e.g., assert or throw
ArgumentError/StateError when id.isEmpty) so callers cannot construct a Project
with an empty id, and include a clear error message referencing the invalid id
to aid debugging; keep the rest of the construction (attributes/relationships)
unchanged.

Comment on lines +26 to +33
Future<void> _checkInitialLink() async {
try {
final uri = await _appLinks.getInitialLink();
if (uri != null) {
_handleLink(uri);
}
} catch (e) {}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid silent error swallowing.

The empty catch block silently discards any errors from getInitialLink(). At minimum, log the error for debugging purposes. This could hide issues during app initialization.

Proposed fix
   Future<void> _checkInitialLink() async {
     try {
       final uri = await _appLinks.getInitialLink();
       if (uri != null) {
         _handleLink(uri);
       }
-    } catch (e) {}
+    } catch (e) {
+      debugPrint('Error checking initial deep link: $e');
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future<void> _checkInitialLink() async {
try {
final uri = await _appLinks.getInitialLink();
if (uri != null) {
_handleLink(uri);
}
} catch (e) {}
}
Future<void> _checkInitialLink() async {
try {
final uri = await _appLinks.getInitialLink();
if (uri != null) {
_handleLink(uri);
}
} catch (e) {
debugPrint('Error checking initial deep link: $e');
}
}
🤖 Prompt for AI Agents
In `@lib/services/deep_link_manager.dart` around lines 26 - 33, The
_checkInitialLink method currently swallows errors from
_appLinks.getInitialLink() with an empty catch; update the catch to log the
error instead of ignoring it—catch the exception (e.g., catch (e, st)) and call
the package's logger (or debugPrint/print) to emit a clear message including the
exception and stack trace, keeping the rest of _checkInitialLink (including
calling _handleLink(uri)) intact; reference _checkInitialLink,
_appLinks.getInitialLink, and _handleLink when making the change.

Comment on lines +73 to +83
Future<void> _fetchAndNavigateToProject(String projectId) async {
try {
final project = await locator<ProjectsApi>().getProjectDetails(projectId);

if (project != null) {
Get.offNamed(ProjectDetailsView.id, arguments: project);
}
} catch (e) {
SnackBarUtils.showDark('Error', 'Could not load project details.');
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle null project response with user feedback.

When getProjectDetails returns null (project not found), the user receives no feedback. Consider showing an error message similar to the catch block.

Proposed fix
   Future<void> _fetchAndNavigateToProject(String projectId) async {
     try {
       final project = await locator<ProjectsApi>().getProjectDetails(projectId);

       if (project != null) {
         Get.offNamed(ProjectDetailsView.id, arguments: project);
+      } else {
+        SnackBarUtils.showDark('Error', 'Project not found.');
       }
     } catch (e) {
       SnackBarUtils.showDark('Error', 'Could not load project details.');
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future<void> _fetchAndNavigateToProject(String projectId) async {
try {
final project = await locator<ProjectsApi>().getProjectDetails(projectId);
if (project != null) {
Get.offNamed(ProjectDetailsView.id, arguments: project);
}
} catch (e) {
SnackBarUtils.showDark('Error', 'Could not load project details.');
}
}
Future<void> _fetchAndNavigateToProject(String projectId) async {
try {
final project = await locator<ProjectsApi>().getProjectDetails(projectId);
if (project != null) {
Get.offNamed(ProjectDetailsView.id, arguments: project);
} else {
SnackBarUtils.showDark('Error', 'Project not found.');
}
} catch (e) {
SnackBarUtils.showDark('Error', 'Could not load project details.');
}
}
🤖 Prompt for AI Agents
In `@lib/services/deep_link_manager.dart` around lines 73 - 83, In
_fetchAndNavigateToProject, when
locator<ProjectsApi>().getProjectDetails(projectId) returns null you currently
do nothing; update the method to detect a null project and show user feedback
(e.g., call SnackBarUtils.showDark with a suitable title/message) and avoid
calling Get.offNamed(ProjectDetailsView.id) when project is null, mirroring the
existing catch-block behavior so the user is informed when a project is not
found.

Comment on lines +108 to +116
// Pattern: /users/:userId/projects/:projectId
if (segments.length >= 4 &&
segments[0] == 'users' &&
segments[2] == 'projects') {
return CVRouter.buildRoute(
const Scaffold(body: Center(child: CircularProgressIndicator())),
settings: settings,
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Route generation for /users/:userId/projects/:projectId only shows a loading indicator.

This generateRoute case returns a scaffold with just a spinner but doesn't trigger actual project fetching or navigation. When the route is generated via CVRouter.generateRoute, the user will see a perpetual loading state.

The _handleLink method correctly calls _fetchAndNavigateToProject, but generateRoute (used for declarative routing) doesn't. Consider whether this code path is actually needed, or implement proper async loading similar to _handleLink.

Possible approaches
  1. Remove this case from generateRoute if it's only meant to be handled via _handleLink (stream-based navigation).

  2. Return null to let standard routing handle it, then rely on the stream listener:

       // Pattern: /users/:userId/projects/:projectId
       if (segments.length >= 4 &&
           segments[0] == 'users' &&
           segments[2] == 'projects') {
-        return CVRouter.buildRoute(
-          const Scaffold(body: Center(child: CircularProgressIndicator())),
-          settings: settings,
-        );
+        // Handled by _handleLink via stream; return null to avoid duplicate handling
+        return null;
       }
🤖 Prompt for AI Agents
In `@lib/services/deep_link_manager.dart` around lines 108 - 116, generateRoute
currently returns a Scaffold with only a CircularProgressIndicator for the
"/users/:userId/projects/:projectId" pattern, leaving the app stuck in a
perpetual loading state; fix by either removing that pattern from generateRoute
(so the link is handled exclusively by the stream-based navigator in _handleLink
and _fetchAndNavigateToProject) or implement an async load path here that
triggers the same behavior as _handleLink (invoke the project fetch/navigation
flow used by _fetchAndNavigateToProject and then return an appropriate route or
null). Locate the pattern match in generateRoute and choose one of the two
approaches: delete the block or replace it so it delegates to the existing
_fetchAndNavigateToProject logic (or returns null to let standard routing
proceed).

Comment on lines +24 to +39
final args = Get.arguments;
Project? project;
bool isEmbed = false;

if (args is Project) {
project = args;
} else if (args is Map) {
project = args['project'] as Project?;
isEmbed = args['isEmbed'] as bool? ?? false;
}

return Scaffold(
body: SafeArea(
child: BaseView<SimulatorViewModel>(
onModelReady: (model) => model.onModelReady(project),
onModelReady:
(model) => model.onModelReady(project, isEmbed: isEmbed),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid unchecked casts from route args.

args['project'] as Project? and args['isEmbed'] as bool? can throw if the payload types differ (e.g., from navigation or deep link parsing). Safer extraction avoids runtime crashes.

Safer argument parsing
-    if (args is Project) {
-      project = args;
-    } else if (args is Map) {
-      project = args['project'] as Project?;
-      isEmbed = args['isEmbed'] as bool? ?? false;
-    }
+    if (args is Project) {
+      project = args;
+    } else if (args is Map) {
+      final argProject = args['project'];
+      if (argProject is Project) {
+        project = argProject;
+      }
+      final argIsEmbed = args['isEmbed'];
+      isEmbed = argIsEmbed == true;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final args = Get.arguments;
Project? project;
bool isEmbed = false;
if (args is Project) {
project = args;
} else if (args is Map) {
project = args['project'] as Project?;
isEmbed = args['isEmbed'] as bool? ?? false;
}
return Scaffold(
body: SafeArea(
child: BaseView<SimulatorViewModel>(
onModelReady: (model) => model.onModelReady(project),
onModelReady:
(model) => model.onModelReady(project, isEmbed: isEmbed),
final args = Get.arguments;
Project? project;
bool isEmbed = false;
if (args is Project) {
project = args;
} else if (args is Map) {
final argProject = args['project'];
if (argProject is Project) {
project = argProject;
}
final argIsEmbed = args['isEmbed'];
isEmbed = argIsEmbed == true;
}
return Scaffold(
body: SafeArea(
child: BaseView<SimulatorViewModel>(
onModelReady:
(model) => model.onModelReady(project, isEmbed: isEmbed),
🤖 Prompt for AI Agents
In `@lib/ui/views/simulator/simulator_view.dart` around lines 24 - 39,
Get.arguments is being cast unsafely to Project and Map entries are force-cast;
change the extraction to validate types before casting to avoid runtime errors:
check the runtime type of Get.arguments (use "is Project" and "is Map" checks on
args and for map entries check "is Project" / "is bool" or safely parse
Map<String, dynamic>), assign to project only when the value is actually a
Project and set isEmbed only when the map value is a bool (fallback to false
otherwise), and then call model.onModelReady(project, isEmbed: isEmbed) as
before; refer to the symbols Get.arguments, project, isEmbed, and
BaseView<SimulatorViewModel>::onModelReady to locate where to apply these
defensive checks.

markdown: any
webview_flutter: 4.13.1
flutter_quill: ^11.4.2
app_links: ^6.4.1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

app_links flutter package 6.4.1 pubspec SDK constraint compatibility

💡 Result:

app_links 6.4.1 raised its minimum toolchain to Flutter 3.24.0 / Dart 3.5.0. [1]

So your app’s pubspec.yaml must allow at least those versions, e.g.:

environment:
  sdk: ">=3.5.0 <4.0.0"
  flutter: ">=3.24.0"

If your project is pinned below that (older Flutter/Dart), Pub will refuse to select app_links: 6.4.1 and you’ll need to use an older app_links version that still supports your SDKs. SDK constraints work via the environment: section as described in Dart’s pubspec docs. [2]

Sources: [1] [2]


🌐 Web query:

app_links latest version Flutter 3.7 compatibility pub.dev

💡 Result:

  • Latest app_links on pub.dev: 7.0.0 (min Dart 3.10) and it requires Flutter 3.38.1+ (per changelog), so it’s not compatible with Flutter 3.7. [1][2]
  • Flutter 3.7 uses Dart 2.19.x, so you need an app_links version that supports Dart 2.x. [3]
  • Latest app_links that fits Flutter 3.7 / Dart 2.19: 3.5.1 (min Dart 2.17). [1]

Use:

dependencies:
  app_links: 3.5.1

Sources:
[1] https://pub.dev/packages/app_links/versions
[2] https://pub.dev/packages/app_links/changelog
[3] https://stackoverflow.com/questions/78595756/cant-run-flutter-app-for-web-dependency-error


🌐 Web query:

app_links 6.4.1 supported platforms Android iOS web

💡 Result:

app_links v6.4.1 supports these platforms:

  • Android (pub.dev)
  • iOS (pub.dev)
  • Web (supported, but only the initial link is provided; no special setup) (pub.dev)

It also supports Windows, macOS, and Linux. (pub.dev)

Citations:


Replace app_links ^6.4.1 with app_links 3.5.1 due to Dart SDK incompatibility.

app_links 6.4.1 requires Dart 3.5.0, but Flutter 3.7.2 only includes Dart 2.19.x. This will cause dependency resolution failures. Use app_links 3.5.1 instead, which is the latest version compatible with Flutter 3.7.x and Dart 2.19.

🤖 Prompt for AI Agents
In `@pubspec.yaml` at line 66, The pubspec dependency for app_links is
incompatible with the project's Dart SDK; replace the current dependency
declaration "app_links: ^6.4.1" in pubspec.yaml with "app_links: 3.5.1" so the
package resolves against Flutter 3.7.x / Dart 2.19.x—locate the app_links entry
in pubspec.yaml and update its version constraint accordingly, then run flutter
pub get to verify resolution.

@ShashwatXD ShashwatXD closed this Jan 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Deeplink Manager

1 participant