-
Notifications
You must be signed in to change notification settings - Fork 131
Deeplinks manager #486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deeplinks manager #486
Conversation
✅ Deploy Preview for cv-mobile-app-web ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Caution Review failedThe pull request is closed. WalkthroughAdds 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
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 keepingbuildRouteinternal or documenting its public use.Renaming
_buildRoutetobuildRoutemakes this a public API. This is necessary forDeepLinkManagerto use it, but consider adding documentation to clarify its intended usage, or using@visibleForTesting/@internalannotations if it should remain implementation-scoped.lib/services/deep_link_manager.dart (3)
8-8: Unused import.
DialogServiceis imported but never used in this file.-import 'package:mobile_app/services/dialog_service.dart';
18-24: Consider awaiting_checkInitialLinkor handling potential race condition.
_checkInitialLink()isasyncbut called withoutawaitininit(). If the initial link check completes after the stream subscription processes a duplicate URI emission, navigation could occur twice. WhileAppLinkstypically 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.darttoawait 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; }
|
|
||
| <!-- 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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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:pathwhen the route is fixed (most precise). [3] - Use
android:pathPrefixfor “all pages under/products/”. [3] - Avoid
android:pathPatternunless 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:pathAdvancedPatternif 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"(andwwwvariant 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 -20Repository: 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 -lRepository: 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 -20Repository: 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.ktRepository: 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.dartRepository: 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.xmlRepository: CircuitVerse/mobile-app
Length of output: 5604
🏁 Script executed:
# Find DeepLinkManager file
find . -name "*deep*link*" -type fRepository: CircuitVerse/mobile-app
Length of output: 104
🏁 Script executed:
# Search for DeepLinkManager class definition
rg -i "class.*deeplink" --type dart -A 30Repository: CircuitVerse/mobile-app
Length of output: 1860
🏁 Script executed:
# Get full DeepLinkManager implementation
cat -n lib/services/deep_link_manager.dartRepository: 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.
| <!-- 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.
| 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')), | ||
| ), | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| Future<void> _checkInitialLink() async { | ||
| try { | ||
| final uri = await _appLinks.getInitialLink(); | ||
| if (uri != null) { | ||
| _handleLink(uri); | ||
| } | ||
| } catch (e) {} | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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.'); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| // 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, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
-
Remove this case from
generateRouteif it's only meant to be handled via_handleLink(stream-based navigation). -
Return
nullto 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).
| 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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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_linkson 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_linksversion that supports Dart 2.x. [3] - Latest
app_linksthat fits Flutter 3.7 / Dart 2.19:3.5.1(min Dart 2.17). [1]
Use:
dependencies:
app_links: 3.5.1Sources:
[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:
- 1: https://pub.dev/packages/app_links/versions/6.4.1?utm_source=openai
- 2: https://pub.dev/packages/app_links/versions/6.4.1?utm_source=openai
- 3: https://pub.dev/packages/app_links/versions/6.4.1?utm_source=openai
- 4: https://pub.dev/packages/app_links/versions/6.4.1?utm_source=openai
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.
Fixes #482
Describe the changes you have made in this PR -
Added a Deep Link Manager using the app_links dependency.
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
Chores
✏️ Tip: You can customize this high-level summary in your review settings.