Skip to content

Feat/deeplinkshare#387

Draft
dhakar66 wants to merge 4 commits into
developfrom
feat/deeplinkshare
Draft

Feat/deeplinkshare#387
dhakar66 wants to merge 4 commits into
developfrom
feat/deeplinkshare

Conversation

@dhakar66

@dhakar66 dhakar66 commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Deep Link Implementation Documentation

Overview

This document describes the deep link functionality implemented for the WeBuddhist app, which enables users to share the app and have recipients directed to the app (if installed) or the appropriate app store (if not installed) via a universal link.

Purpose

The deep link implementation serves the following purposes:

  1. App Sharing: Allow users to share the WeBuddhist app with others through the home screen share prompt
  2. Smart Redirection: Direct recipients to the app if installed, or to the relevant app store if not
  3. Seamless User Experience: Provide a unified entry point (https://webuddhist.com/open) that works across platforms

Architecture

Components

The implementation consists of several interconnected components:

  1. UI Layer - Home screen share widget
  2. Service Layer - App share service and deep link service
  3. Routing Layer - GoRouter configuration with /open route
  4. Platform Configuration - iOS and Android manifest/entitlements
  5. Web Infrastructure - Landing page at https://webuddhist.com/open

Implementation Details

1. User Interface Components

Home Share Prompt Widget

Location: lib/features/home/presentation/widgets/home_share_prompt.dart

A prominent call-to-action displayed at the bottom of the home screen feed that invites users to share the app.

Features:

  • Displays a localized message: "Help others discover [WeBuddhist]. Share it today!"
  • Styled share button with export icon
  • Triggers the app share service when tapped

Placement:

  • Rendered at the bottom of the home screen's scrollable content using SliverToBoxAdapter
  • Located in home_screen.dart at line 359

2. Service Layer

App Share Service

Location: lib/core/services/app_share/app_share_service.dart

Handles the generation and sharing of the app invitation message.

Key Configuration:

static const String _deepLinkUrl = 'https://webuddhist.com/open';

Share Message Format:

I'm using WeBuddhist to learn and practice Buddhism. Join me!

👉 https://webuddhist.com/open

Functionality:

  • generateShareMessage(): Creates a localized share message with the deep link
  • shareApp(): Triggers the native share sheet with the generated message
  • Uses share_plus package for cross-platform sharing
  • Includes error handling and logging

Provider:

final appShareServiceProvider = Provider<AppShareService>((ref) {
  return AppShareService();
});

Deep Link Service

Location: lib/core/deep_linking/deep_link_service.dart

A singleton service that listens for and handles incoming Universal Links (iOS) and App Links (Android).

Entry Points:

  1. Cold Start: App was closed when the link was tapped
  2. Warm Start: App was already running (foreground/background)

Key Methods:

  1. initialize() - Called in main() before runApp()
  • Captures cold-start links via AppLinks.getInitialLink()
  • Sets up stream listener for warm-start links
  • Stores pending URI if router isn't ready yet
  1. setRouter(GoRouter) - Called from MyApp.build() after router initialization
  • Provides router instance to the service
  • Processes any pending cold-start links
  1. _handleDeepLink(Uri) - Internal routing logic
  • Matches incoming URIs against known patterns
  • Routes to appropriate screens
  • Falls back to /home for unhandled links

Supported Link Formats:

  • HTTPS Universal Link: https://webuddhist.com/open
  • Custom Scheme (fallback): webuddhist://open

Current Routes:

  • /open → Navigates to /home (main entry point)
  • Future expansion ready for plan invites, reader deep links, etc.

3. Routing Configuration

App Router

Location: lib/core/config/router/app_router.dart

The /open route is registered in the main GoRouter configuration:

GoRoute(
  path: '/open',
  name: 'open',
  redirect: (_, __) => AppRoutes.home,
),

Purpose:

  • Provides a warm-start fallback if GoRouter intercepts the URI directly
  • Primary handling is done by DeepLinkService for cold-start scenarios
  • Ensures consistent behavior across both entry scenarios

Route Categories

Location: lib/core/config/router/app_routes.dart

The /open route is categorized as a public route:

static const Set<String> publicRoutes = {splash, login, open};

This means:

  • No authentication required
  • Accessible to all users (logged in, guest, or anonymous)
  • Will not trigger login flow

4. Platform Configuration

iOS Configuration

Universal Links Setup:

  1. Development Entitlements (Runner-dev.entitlements):
 <key>com.apple.developer.associated-domains</key>
 <array>
     <string>webcredentials:dev-vz6o17motc18g45h.us.auth0.com</string>
     <string>applinks:webuddhist.com</string>
 </array>
  1. Production Entitlements (Runner.entitlements):
 <key>com.apple.developer.associated-domains</key>
 <array>
     <string>webcredentials:we-buddhist-prod.us.auth0.com</string>
 </array>

⚠️ Note: Production entitlements are currently missing the applinks:webuddhist.com entry. This should be added for production deep links to work properly.

Required for Universal Links:

  • Apple App Site Association (AASA) file must be hosted at:
    https://webuddhist.com/.well-known/apple-app-site-association
  • File must be JSON format without file extension
  • Must be served with HTTPS
  • Content-Type: application/json

Android Configuration

App Links Setup:

Location: android/app/src/main/AndroidManifest.xml

<!-- App Link: https://webuddhist.com/open (home share deep link) -->
<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="webuddhist.com"
        android:path="/open" />
</intent-filter>

Key Attributes:

  • android:autoVerify="true": Enables automatic verification of the App Link
  • android:launchMode="singleTask": Ensures only one instance of the activity
  • Deep linking enabled via meta-data:
    <meta-data
        android:name="flutter_deeplinking_enabled"
        android:value="true" />

Required for App Links:

  • Digital Asset Links file must be hosted at:
    https://webuddhist.com/.well-known/assetlinks.json
  • File must include app package name and SHA-256 certificate fingerprints
  • Must be served with HTTPS
  • Content-Type: application/json

5. Initialization Flow

Application Startup Sequence

Location: lib/main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // ... other initializations (Firebase, cache, etc.) ...
  
  // Initialize deep link listener early (line 125)
  await DeepLinkService.instance.initialize();
  
  runApp(UncontrolledProviderScope(container: container, child: const MyApp()));
}

In MyApp Widget:

@override
Widget build(BuildContext context) {
  final router = ref.watch(appRouterProvider);
  
  // ... other service initializations ...
  
  // Provide router to deep link service (line 182)
  DeepLinkService.instance.setRouter(router);
  
  return MaterialApp.router(
    routerConfig: router,
    // ... other configuration ...
  );
}

Initialization Timeline:

  1. Before runApp(): DeepLinkService.initialize() captures cold-start links
  2. During MyApp.build(): Router is created and provided to the service
  3. On Router Ready: Any pending cold-start link is processed
  4. Runtime: Stream listener handles warm-start links as they arrive

User Flow

Complete User Journey

Scenario A: Sharing the App

  1. User A scrolls to bottom of home feed
  2. Sees the "Share WeBuddhist" prompt
  3. Taps the share button
  4. Native share sheet appears with message:
 I'm using WeBuddhist to learn and practice Buddhism. Join me!

 👉 https://webuddhist.com/open
  1. Shares via SMS, WhatsApp, email, etc.

Scenario B: Recipient Has App Installed

  1. User B receives the message with link
  2. Taps on https://webuddhist.com/open
  3. iOS: Universal Link is recognized, app launches directly
  4. Android: App Link is verified, app launches directly
  5. App opens and DeepLinkService intercepts the URI
  6. User is navigated to /home screen
  7. User sees the WeBuddhist home feed

Scenario C: Recipient Without App

  1. User C receives the message with link
  2. Taps on https://webuddhist.com/open
  3. Link opens in mobile web browser
  4. Web page at webuddhist.com/open is loaded
  5. Page detects platform (iOS/Android) via user agent
  6. Displays appropriate call-to-action:
  • iOS: "Download on the App Store" button → Apple App Store
  • Android: "Get it on Google Play" button → Google Play Store
  1. User can install the app from the store

Technical Dependencies

Flutter Packages

  1. app_links (^6.3.4)
  • Handles Universal Links (iOS) and App Links (Android)
  • Provides stream of incoming URIs
  • Captures cold-start and warm-start links
  1. share_plus (^11.4.1)
  • Cross-platform sharing functionality
  • Native share sheet integration
  • Supports text and file sharing
  1. go_router (^14.8.1)
  • Declarative routing
  • Deep link handling
  • Protected route management
  1. flutter_riverpod (^2.7.2)
  • State management
  • Service providers
  • Dependency injection

Error Handling

Deep Link Service Error Handling

  1. Initialization Errors:
  • Caught and logged via AppLogger
  • App continues to function normally
  • Deep links will be unavailable but won't crash app
  1. Routing Errors:
  • Unrecognized URIs fall back to /home
  • Logged as warnings for debugging
  • User always lands on a valid screen
  1. Stream Errors:
  • Error handler on URI stream listener
  • Prevents stream from closing
  • Logs error details for investigation

Share Service Error Handling

  1. Share Cancellation:
  • User canceling share sheet is not treated as error
  • Share package handles this gracefully
  1. Platform Errors:
  • Caught via try-catch in shareApp()
  • Error logged and re-thrown
  • UI layer can handle with user feedback

Future Enhancements

The deep link system is designed to be extensible. Future link types could include:

Planned Routes

  1. Plan Invitations:
  • Format: https://webuddhist.com/plans/{planId}/invite
  • Direct user to specific plan preview or enrollment
  1. Reader Deep Links:
  • Format: https://webuddhist.com/reader/{textId}?segment={segmentId}
  • Open specific text at specific segment
  1. Group Invitations:
  • Format: https://webuddhist.com/groups/{groupId}/join
  • Direct user to group profile with join prompt
  1. Shared Content:
  • Format: https://webuddhist.com/verses/{verseId}
  • View a specific verse of the day

Implementation Pattern

To add a new deep link route:

  1. Update DeepLinkService:
 // In _handleDeepLink method
 if (_isPlanInviteLink(uri)) {
   final planId = uri.pathSegments[1];
   _router!.go('/home/plans/$planId/preview');
   return;
 }
  1. Add Matcher Method:
 bool _isPlanInviteLink(Uri uri) {
   return uri.path.startsWith('/plans/') && 
          uri.path.endsWith('/invite');
 }
  1. Update Platform Configuration:
  • Add path pattern to iOS AASA file
  • Add path to Android intent filter
  1. Test Both Cold and Warm Starts

Known Issues & Limitations

iOS Production Configuration

Issue: The production entitlements file (ios/Runner/Runner.entitlements) is missing the applinks:webuddhist.com entry.

Impact: Deep links will not work in production iOS builds.

Fix Required:

<key>com.apple.developer.associated-domains</key>
<array>
    <string>webcredentials:we-buddhist-prod.us.auth0.com</string>
    <string>applinks:webuddhist.com</string>  <!-- ADD THIS LINE -->
</array>

Universal Link Debugging

Challenge: Universal Links can be difficult to test and debug on iOS simulators.

Best Practices:

Custom Scheme Fallback

Limitation: Custom scheme (webuddhist://open) requires the app to be installed.

Behavior:

  • iOS: Prompts user to open app if installed, shows error if not
  • Android: Similar behavior, may show "Choose an app" dialog

Recommendation: Always use HTTPS universal/app links as primary method.


Related Files

Core Implementation

  • lib/core/deep_linking/deep_link_service.dart - Deep link service
  • lib/core/services/app_share/app_share_service.dart - App share service
  • lib/core/config/router/app_router.dart - Router configuration
  • lib/core/config/router/app_routes.dart - Route definitions
  • lib/main.dart - App initialization

UI Components

  • lib/features/home/presentation/widgets/home_share_prompt.dart - Share prompt widget
  • lib/features/home/presentation/widgets/verse_share_sheet.dart - Verse share bottom sheet
  • lib/features/home/presentation/screens/home_screen.dart - Home screen integration

Platform Configuration

  • android/app/src/main/AndroidManifest.xml - Android deep link config
  • ios/Runner/Runner.entitlements - iOS production entitlements
  • ios/Runner/Runner-dev.entitlements - iOS development entitlements

Infrastructure Requirements (External)

  • https://webuddhist.com/open - Web landing page
  • https://webuddhist.com/.well-known/apple-app-site-association - iOS AASA file
  • https://webuddhist.com/.well-known/assetlinks.json - Android Digital Asset Links

Conclusion

This deep link implementation provides a seamless sharing and acquisition flow for WeBuddhist. Users can easily share the app with friends, and recipients are intelligently routed to either the app (if installed) or the appropriate app store (if not).

The architecture is extensible and ready for additional deep link routes as the app grows. Key areas for attention are:

  1. Fixing the iOS production entitlements
  2. Ensuring web infrastructure is properly configured
  3. Testing thoroughly on physical devices across platforms

@dhakar66 dhakar66 requested a review from tentamdin June 17, 2026 07:14
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown

Confidence Score: 3/5

Not safe to merge as-is: Universal Links will be broken in production iOS builds, and cold-start deep links can cause a double navigation for unauthenticated users.

Two concrete defects are introduced: applinks:webuddhist.com is missing from Runner.entitlements (used by all non-dev Xcode configurations), so the feature ships broken for every iOS user on a production or staging build. Additionally, enabling FlutterDeepLinkingEnabled while also using DeepLinkService causes the same cold-start URI to be processed twice, producing a double navigation that misfires the auth guard and can briefly show the splash or login screen twice.

ios/Runner/Runner.entitlements needs the applinks:webuddhist.com entry, and lib/main.dart / lib/core/deep_linking/deep_link_service.dart need to reconcile the overlap between Flutter's built-in deep link handling and the DeepLinkService cold-start path.

Reviews (2): Last reviewed commit: "Add deep linking support in Android and ..." | Re-trigger Greptile

Comment on lines +104 to +109
bool _isOpenLink(Uri uri) {
final isHttps =
(uri.scheme == 'https') && (uri.host == 'webuddhist.com') && (uri.path == '/open');
final isCustom =
(uri.scheme == 'webuddhist') && (uri.host == 'open');
return isHttps || isCustom;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Unreachable custom-scheme branch

The webuddhist://open custom URI scheme is checked here but is registered on neither platform — the Android manifest only declares an App Link for https://webuddhist.com/open, and the iOS entitlements only add applinks:webuddhist.com (Universal Links). The OS will never deliver a webuddhist:// URI to the app, so the isCustom branch can never evaluate to true. Leaving it in place misleads future developers into thinking the scheme is already wired up, or into adding a platform entry expecting the routing to be handled.

Comment on lines +10 to +12
/// Universal link — opens the app when installed, redirects to the
/// correct store (via your hosted /open page) when not installed.
static const String _deepLinkUrl = 'https://webuddhist.com/open';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Web fallback assumed but not confirmed

The comment states that https://webuddhist.com/open "redirects to the correct store (via your hosted /open page) when not installed." If no web page actually lives at that path, any user without the app — on desktop browsers, unsupported platforms, or before the web-side is deployed — will land on a 404 rather than being sent to the App Store / Play Store. The previous implementation explicitly embedded both store URLs as a plain-text fallback, so this regression would silently break acquisition for users who don't already have the app installed. Is https://webuddhist.com/open already a live web page that sniffs the platform and redirects to the correct store, or is that still pending deployment?

Comment on lines 6 to +8
<array>
<string>webcredentials:dev-vz6o17motc18g45h.us.auth0.com</string>
<string>applinks:webuddhist.com</string>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Universal Links only wired for dev builds

The Xcode project uses Runner.entitlements for every non-dev configuration (Release, Profile, Staging). applinks:webuddhist.com was added only to Runner-dev.entitlements, so Universal Links will silently fail on all production and staging iOS builds — the OS will open https://webuddhist.com/open in Safari instead of launching the app. Runner.entitlements needs the same applinks:webuddhist.com entry.

Comment thread lib/main.dart
ref.watch(notificationSyncBootstrapProvider);
NotificationService.setRouter(router);
NotificationService().consumeLaunchNotification();
DeepLinkService.instance.setRouter(router);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Double navigation on cold start

Both FlutterDeepLinkingEnabled = true (iOS) / flutter_deeplinking_enabled (Android) and DeepLinkService process the same incoming link independently. On cold start with https://webuddhist.com/open, go_router receives the URI as the initial location (routing /open/home), and then setRouter fires a second _router!.go('/home') from the pending URI. For authenticated users this is a benign duplicate; for unauthenticated users the auth guard redirects to splash once from go_router's pass, and then immediately again from the DeepLinkService pass, causing a visible navigation flash or double-splash. Consider clearing _pendingUri in initialize() when FlutterDeepLinkingEnabled is active so the service is only used as a warm-start listener, leaving cold-start routing entirely to go_router.

@dhakar66 dhakar66 marked this pull request as draft June 18, 2026 04:40
@dhakar66 dhakar66 requested review from CodingWithTashi and removed request for tentamdin June 18, 2026 04:42
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.

1 participant