shyun_link 🚀

pub package License: MIT Flutter

A comprehensive, enterprise-grade deeplink and short URL management system built with Clean Architecture principles. Perfect for complex Flutter applications that need robust, scalable, and testable deeplink handling.

✨ Features

  • Universal Link Support - Handle both app schemes and web URLs
  • Parametrized Links - Extract parameters from complex URLs
  • Fallback Strategies - Graceful handling of unknown links
  • Route Validation - Ensure links point to valid destinations
  • Preview Mode - Debug and test deeplinks before deployment

📎 Short URL System

  • Multiple Providers - HTTP-based or custom implementations
  • Analytics Tracking - Click counts, geographic data, device stats
  • Expiration Management - Time-based link expiration
  • Custom Aliases - Branded short links
  • Batch Operations - Create multiple links efficiently

⚙️ Configuration & DI

  • Framework Agnostic - Works with GetX, get_it, Provider, or built-in DI
  • Environment-Based Config - Development, staging, production presets
  • Runtime Configuration - Modify behavior without rebuilding
  • Security Controls - Domain whitelisting, rate limiting, validation

🛡️ Enterprise Ready

  • Functional Error Handling - Result
  • Comprehensive Logging - Debug, performance, and error tracking
  • Type Safety - Full Dart null safety and strong typing
  • Testing Support - Built-in mocks and test utilities
  • Performance Optimized - Caching, request coalescing, concurrent processing

🚀 Quick Start

Installation

Add to your pubspec.yaml:

dependencies:
  shyun_link: ^1.0.0
  
  # Optional: Choose your preferred DI framework
  get: ^4.6.0  # For GetX
  # OR
  get_it: ^7.0.0  # For get_it
  # OR use built-in DI (no additional dependencies)

Basic Setup

import 'package:shyun_link/shyun_link.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 1. Configure the system
  final config = DeepLinkConfig.defaults(
    appScheme: 'myapp',
    baseUrl: 'https://myapp.com',
  );
  
  // 2. Initialize the dependency injection
  await DeepLinkArchitect.initialize(config);
  
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      home: HomePage(),
      // 3. Handle deeplinks
      onGenerateRoute: (settings) {
        return DeepLinkArchitect.handleRoute(settings);
      },
    );
  }
}
class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late final DeepLinkFacade _deepLinkFacade;
  
  @override
  void initState() {
    super.initState();
    _deepLinkFacade = DIContainer.get<DeepLinkFacade>();
    _setupDeepLinkListener();
  }
  
  void _setupDeepLinkListener() {
    // Handle incoming deeplinks
    DeepLinkArchitect.onDeepLink.listen((deepLink) async {
      final success = await _deepLinkFacade.handleDeepLink(deepLink);
      if (!success) {
        // Handle fallback
        Navigator.of(context).pushReplacementNamed('/');
      }
    });
  }
}
class ProductPage extends StatelessWidget {
  final int productId;
  
  const ProductPage({required this.productId});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Product $productId'),
        actions: [
          IconButton(
            icon: Icon(Icons.share),
            onPressed: () => _shareProduct(context),
          ),
        ],
      ),
      body: ProductDetails(productId: productId),
    );
  }
  
  Future<void> _shareProduct(BuildContext context) async {
    final facade = DIContainer.get<DeepLinkFacade>();
    
    // Create short link for this product
    final shortUrl = await facade.createPageLink(
      pageType: 'product',
      pageId: productId,
      originalUrl: 'https://myapp.com/product/$productId',
    );
    
    // Share the short link
    await facade.shareApp(shareText: 'Check out this product: $shortUrl');
  }
}

📋 Advanced Usage

Custom Configuration

// Development environment
final devConfig = DeepLinkConfig.development(
  appScheme: 'myapp',
  baseUrl: 'https://dev.myapp.com',
);

// Production environment with security
final prodConfig = DeepLinkConfig.production(
  appScheme: 'myapp',
  baseUrl: 'https://myapp.com',
  allowedDomains: ['myapp.com', 'www.myapp.com'],
);

// Custom configuration
final customConfig = DeepLinkConfig(
  base: BaseConfig(
    appScheme: 'myapp',
    baseUrl: 'https://myapp.com',
  ),
  shortLink: ShortLinkConfig(
    enabled: true,
    apiBaseUrl: 'https://api.myapp.com/links',
    defaultExpiration: Duration(days: 90),
    enableAnalytics: true,
  ),
  security: SecurityConfig(
    enableUrlValidation: true,
    allowedDomains: ['myapp.com'],
    enableRateLimiting: true,
    maxRequestsPerMinute: 100,
  ),
);
class CustomDeepLinkStrategy extends DeepLinkProcessingStrategy {
  @override
  bool canHandle(DeepLinkEntity deepLink) {
    return deepLink.scheme == 'myapp' && 
           deepLink.path?.startsWith('/custom') == true;
  }
  
  @override
  Future<Result<DeepLinkProcessingResult>> process(
    DeepLinkEntity deepLink, 
    DeepLinkValidation validation
  ) async {
    // Custom processing logic
    final route = _extractRoute(deepLink);
    final arguments = _extractArguments(deepLink);
    
    return Result.success(DeepLinkProcessingResult(
      routePath: route,
      arguments: arguments,
      metadata: {'strategy': 'custom'},
    ));
  }
  
  String _extractRoute(DeepLinkEntity deepLink) {
    // Custom route extraction logic
    return '/custom-page';
  }
  
  Map<String, dynamic> _extractArguments(DeepLinkEntity deepLink) {
    // Custom argument extraction logic
    return deepLink.queryParameters;
  }
}

// Register custom strategy
final strategyManager = DIContainer.get<DeepLinkStrategyManager>();
strategyManager.addStrategy(CustomDeepLinkStrategy());

Testing Support

// Unit test example
void main() {
  group('DeepLink Processing', () {
    late DeepLinkFacade facade;
    late MockShortLinkRepository mockRepository;
    
    setUp(() {
      mockRepository = MockShortLinkRepository();
      facade = DeepLinkFacade(
        shortLinkRepository: mockRepository,
        // ... other dependencies
      );
    });
    
    test('should handle valid deeplink', () async {
      // Arrange
      const deepLinkUrl = 'myapp://product?id=123';
      
      // Act
      final result = await facade.handleDeepLink(deepLinkUrl);
      
      // Assert
      expect(result, isTrue);
      verify(mockRepository.recordClick(any)).called(1);
    });
    
    test('should create short link', () async {
      // Arrange
      when(mockRepository.createShortLink(any))
          .thenAnswer((_) async => Result.success(mockShortLink));
      
      // Act
      final shortUrl = await facade.createStoreLink(123);
      
      // Assert
      expect(shortUrl, isNotEmpty);
      expect(shortUrl, startsWith('https://'));
    });
  });
}

🏗️ Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                     Presentation Layer                      │
├─────────────────────────────────────────────────────────────┤
│  • DeepLinkHandler    • ShareButton    • RouteGenerator    │
└─────────────────────────────────────────────────────────────┘
                                 │
┌─────────────────────────────────────────────────────────────┐
│                    Application Layer                        │
├─────────────────────────────────────────────────────────────┤
│  • DeepLinkFacade     • UseCases        • Factories        │
└─────────────────────────────────────────────────────────────┘
                                 │
┌─────────────────────────────────────────────────────────────┐
│                      Domain Layer                           │
├─────────────────────────────────────────────────────────────┤
│  • Entities           • Repositories   • Services          │
│  • Strategies         • Value Objects  • Domain Rules      │
└─────────────────────────────────────────────────────────────┘
                                 │
┌─────────────────────────────────────────────────────────────┐
│                   Infrastructure Layer                      │
├─────────────────────────────────────────────────────────────┤
│  • HTTP Clients      • Data Sources    • External APIs     │
│  • Caching           • Analytics       • Platform Services │
└─────────────────────────────────────────────────────────────┘

📱 Platform Support

  • Android - App Links, Custom Schemes
  • iOS - Universal Links, Custom Schemes
  • Web - URL Routing, History API
  • macOS - Custom URL Schemes
  • Windows - Protocol Registration
  • Linux - Desktop Integration

🤝 Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

Development Setup

git clone https://github.com/TeamShyun/shyun_link.git
cd shyun_link
flutter packages get
flutter test

📄 License

This project is licensed under the MIT License - see LICENSE file for details.

🙏 Acknowledgments

  • Inspired by Clean Architecture principles by Robert C. Martin
  • Built with love for the Flutter community
  • Thanks to all contributors and testers

Made with ❤️ for Flutter developers worldwide
If this package helped you, please consider giving it a ⭐ on GitHub!

Libraries

ShyunLink