flutter_redirectly 1.0.0 copy "flutter_redirectly: ^1.0.0" to clipboard
flutter_redirectly: ^1.0.0 copied to clipboard

A Flutter plugin for Redirectly dynamic links - similar to Firebase Dynamic Links but using your own backend.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_redirectly/flutter_redirectly.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Redirectly Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Redirectly Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final FlutterRedirectly _redirectly = FlutterRedirectly();
  final TextEditingController _slugController = TextEditingController();
  final TextEditingController _targetController = TextEditingController();
  final TextEditingController _apiKeyController = TextEditingController();

  List<RedirectlyLink> _links = [];
  List<RedirectlyLinkClick> _linkClicks = [];
  bool _isInitialized = false;
  bool _isLoading = false;
  String? _errorMessage;

  @override
  void initState() {
    super.initState();
    _setupRedirectly();
  }

  Future<void> _setupRedirectly() async {
    // Listen to incoming link clicks
    _redirectly.onLinkClick.listen((linkClick) {
      setState(() {
        _linkClicks.insert(0, linkClick);
      });

      // Show a dialog when link is clicked
      if (mounted) {
        _showLinkClickDialog(linkClick);
      }
    });

    // Check for initial link
    final initialLink = await _redirectly.getInitialLink();
    if (initialLink != null) {
      setState(() {
        _linkClicks.insert(0, initialLink);
      });
      if (mounted) {
        _showLinkClickDialog(initialLink);
      }
    }
  }

  Future<void> _initialize() async {
    if (_apiKeyController.text.trim().isEmpty) {
      setState(() {
        _errorMessage = 'Please enter your API key';
      });
      return;
    }

    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      await _redirectly.initialize(RedirectlyConfig(
        apiKey: _apiKeyController.text.trim(),
        enableDebugLogging: true,
      ));

      setState(() {
        _isInitialized = true;
      });

      await _fetchLinks();
    } catch (e) {
      setState(() {
        _errorMessage = 'Failed to initialize: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _fetchLinks() async {
    if (!_isInitialized) return;

    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      final links = await _redirectly.getLinks();
      setState(() {
        _links = links;
      });
    } catch (e) {
      setState(() {
        _errorMessage = 'Failed to fetch links: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _createLink() async {
    if (!_isInitialized) return;

    if (_slugController.text.trim().isEmpty ||
        _targetController.text.trim().isEmpty) {
      setState(() {
        _errorMessage = 'Please enter both slug and target URL';
      });
      return;
    }

    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      await _redirectly.createLink(
        slug: _slugController.text.trim(),
        target: _targetController.text.trim(),
      );

      _slugController.clear();
      _targetController.clear();

      await _fetchLinks();
    } catch (e) {
      setState(() {
        _errorMessage = 'Failed to create link: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _createTempLink() async {
    if (!_isInitialized) return;

    if (_targetController.text.trim().isEmpty) {
      setState(() {
        _errorMessage = 'Please enter target URL';
      });
      return;
    }

    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      final tempLink = await _redirectly.createTempLink(
        target: _targetController.text.trim(),
        ttlSeconds: 900, // 15 minutes
      );

      _targetController.clear();

      if (mounted) {
        _showTempLinkDialog(tempLink);
      }
    } catch (e) {
      setState(() {
        _errorMessage = 'Failed to create temp link: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  void _showLinkClickDialog(RedirectlyLinkClick linkClick) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Link Clicked'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('URL: ${linkClick.originalUrl}'),
            Text('Username: ${linkClick.username}'),
            Text('Slug: ${linkClick.slug}'),
            if (linkClick.error != null)
              Text('Error: ${linkClick.error}',
                  style: const TextStyle(color: Colors.red)),
            if (linkClick.linkDetails != null)
              Text('Target: ${linkClick.linkDetails!.target}'),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  void _showTempLinkDialog(RedirectlyTempLink tempLink) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Temporary Link Created'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('URL: ${tempLink.url}'),
            Text('Target: ${tempLink.target}'),
            Text('Expires: ${tempLink.expiresAt}'),
            const SizedBox(height: 8),
            const Text('This link will expire in 15 minutes.'),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // API Key Section
            if (!_isInitialized) ...[
              const Text(
                'Initialize Flutter Redirectly',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              TextField(
                controller: _apiKeyController,
                decoration: const InputDecoration(
                  labelText: 'API Key',
                  hintText: 'Enter your Redirectly API key',
                  border: OutlineInputBorder(),
                ),
                obscureText: true,
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: _isLoading ? null : _initialize,
                child: _isLoading
                    ? const SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(strokeWidth: 2),
                      )
                    : const Text('Initialize'),
              ),
            ],

            // Main Features Section
            if (_isInitialized) ...[
              const Text(
                'Create Link',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              TextField(
                controller: _slugController,
                decoration: const InputDecoration(
                  labelText: 'Slug',
                  hintText: 'my-awesome-link',
                  border: OutlineInputBorder(),
                ),
              ),
              const SizedBox(height: 8),
              TextField(
                controller: _targetController,
                decoration: const InputDecoration(
                  labelText: 'Target URL',
                  hintText: 'https://example.com',
                  border: OutlineInputBorder(),
                ),
              ),
              const SizedBox(height: 16),
              Row(
                children: [
                  Expanded(
                    child: ElevatedButton(
                      onPressed: _isLoading ? null : _createLink,
                      child: const Text('Create Permanent Link'),
                    ),
                  ),
                  const SizedBox(width: 8),
                  Expanded(
                    child: ElevatedButton(
                      onPressed: _isLoading ? null : _createTempLink,
                      child: const Text('Create Temp Link'),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 24),

              // Links List
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Text(
                    'Your Links',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  IconButton(
                    onPressed: _isLoading ? null : _fetchLinks,
                    icon: const Icon(Icons.refresh),
                  ),
                ],
              ),
              const SizedBox(height: 8),
              if (_links.isEmpty)
                const Card(
                  child: Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Text('No links created yet.'),
                  ),
                )
              else
                ...(_links.map((link) => Card(
                      child: ListTile(
                        title: Text(link.slug),
                        subtitle: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text('Target: ${link.target}'),
                            Text('Clicks: ${link.clickCount}'),
                            Text('URL: ${link.url}'),
                          ],
                        ),
                        isThreeLine: true,
                      ),
                    ))),

              const SizedBox(height: 24),

              // Link Clicks
              const Text(
                'Recent Link Clicks',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              if (_linkClicks.isEmpty)
                const Card(
                  child: Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Text('No link clicks received yet.'),
                  ),
                )
              else
                ...(_linkClicks.take(5).map((click) => Card(
                      child: ListTile(
                        title: Text('${click.username}/${click.slug}'),
                        subtitle: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text('URL: ${click.originalUrl}'),
                            if (click.error != null)
                              Text('Error: ${click.error}',
                                  style: const TextStyle(color: Colors.red)),
                            if (click.linkDetails != null)
                              Text('Target: ${click.linkDetails!.target}'),
                            Text('Received: ${click.receivedAt}'),
                          ],
                        ),
                        isThreeLine: true,
                        leading: Icon(
                          click.isSuccessful ? Icons.check_circle : Icons.error,
                          color: click.isSuccessful ? Colors.green : Colors.red,
                        ),
                      ),
                    ))),
            ],

            // Error Message
            if (_errorMessage != null) ...[
              const SizedBox(height: 16),
              Card(
                color: Colors.red.shade50,
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Text(
                    _errorMessage!,
                    style: const TextStyle(color: Colors.red),
                  ),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _slugController.dispose();
    _targetController.dispose();
    _apiKeyController.dispose();
    _redirectly.dispose();
    super.dispose();
  }
}
13
likes
150
points
317
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for Redirectly dynamic links - similar to Firebase Dynamic Links but using your own backend.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

app_links, flutter, http, plugin_platform_interface

More

Packages that depend on flutter_redirectly

Packages that implement flutter_redirectly