MetaLink Flutter
A Flutter package for beautiful, highly customizable link preview widgets, built on top of the MetaLink package.
✨ Features
- 🔗 Rich link previews with images, favicon, title, and description
- 🎨 Multiple styles: Card, Compact, Large, and custom
- 🎭 Fully themeable with Material 3 integration
- 🔄 Smart image optimization and responsive sizing
- 💾 Built-in caching for faster loading
- 👆 Tap handling with URL launching or custom callbacks
- 🚧 Loading skeleton placeholders with shimmer effects
- 🧩 Highly customizable components
- 📱 RTL support using Flutter's logical directional properties
📸 Screenshots
🚀 Getting Started
Installation
Add the package to your pubspec.yaml:
dependencies:
metalink_flutter: ^<LATEST VERSION>
Run the installation command:
flutter pub get
Basic Usage
Import the package:
import 'package:metalink_flutter/metalink_flutter.dart';
Add a simple link preview widget:
LinkPreview(
url: 'https://flutter.dev',
)
That's it! The widget will automatically fetch metadata and display a card-style preview of the link.
🎨 Link Preview Styles
MetaLink Flutter comes with three built-in styles and the ability to create custom styles.
Card Style (Default)
Displays a card with the link's image on top, and title, description, and site information below.
LinkPreview.card(
url: 'https://flutter.dev',
titleMaxLines: 2,
descriptionMaxLines: 3,
)
Compact Style
A horizontal layout suitable for inline previews in chat interfaces or lists.
LinkPreview.compact(
url: 'https://flutter.dev',
titleMaxLines: 1,
descriptionMaxLines: 1,
)
Large Style
A prominent display with a large image and detailed content, suitable for featured links.
LinkPreview.large(
url: 'https://flutter.dev',
titleMaxLines: 2,
descriptionMaxLines: 4,
)
Custom Style
Create your own unique link preview style:
LinkPreview.custom(
url: 'https://flutter.dev',
builder: (context, data) {
return Card(
child: Column(
children: [
if (data.hasImage)
Image.network(data.imageUrl!),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
data.title ?? 'No Title',
style: Theme.of(context).textTheme.titleLarge,
),
if (data.description != null)
Text(data.description!),
Text(data.hostname, style: TextStyle(color: Colors.blue)),
],
),
),
],
),
);
},
)
🔧 Advanced Configuration
Configuration Options
The LinkPreview
widget accepts a config
parameter for customizing its behavior:
LinkPreview(
url: 'https://flutter.dev',
config: LinkPreviewConfig(
style: LinkPreviewStyle.card,
titleMaxLines: 2,
descriptionMaxLines: 3,
showImage: true,
showFavicon: true,
handleNavigation: true,
animateLoading: true,
cacheDuration: Duration(hours: 24),
),
onTap: () {
print('Link tapped!');
},
)
Error and Loading Handling
Customize the appearance of loading and error states:
LinkPreview(
url: 'https://flutter.dev',
errorBuilder: (context, error) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.red),
borderRadius: BorderRadius.circular(8),
),
child: Text('Failed to load preview: $error'),
);
},
loadingBuilder: (context) {
return Container(
padding: EdgeInsets.all(16),
child: Row(
children: [
CircularProgressIndicator(),
SizedBox(width: 16),
Text('Loading preview...'),
],
),
);
},
)
🎮 Using Controllers
The LinkPreviewController
allows you to programmatically control link previews:
class _MyWidgetState extends State<MyWidget> {
late LinkPreviewController _controller;
@override
void initState() {
super.initState();
_controller = LinkPreviewController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
decoration: InputDecoration(labelText: 'Enter URL'),
onSubmitted: (url) {
_controller.setUrl(url);
},
),
SizedBox(height: 16),
LinkPreview(
url: '', // Will be set by controller
controller: _controller,
),
Row(
children: [
ElevatedButton(
onPressed: () => _controller.fetchData(forceRefresh: true),
child: Text('Refresh'),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: () => _controller.clear(),
child: Text('Clear'),
),
],
),
],
);
}
}
🎭 Theming
Adding Theme Extension
MetaLink Flutter integrates with your app's theme system using Theme Extensions:
final myTheme = ThemeData.light().copyWith(
extensions: [
LinkPreviewTheme(
data: LinkPreviewThemeData(
backgroundColor: Colors.grey[100],
titleStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
descriptionStyle: TextStyle(
fontSize: 14,
color: Colors.black87,
),
urlStyle: TextStyle(
fontSize: 12,
color: Colors.blue,
),
borderRadius: BorderRadius.circular(12),
elevation: 2.0,
imageHeight: 150.0,
faviconSize: 16.0,
cardShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: Colors.grey.withOpacity(0.2)),
),
),
),
],
);
// Apply the theme
MaterialApp(
theme: myTheme,
// ...
)
Theme Extension Method
You can also use the extension method on ThemeData
:
final myTheme = ThemeData.light().withLinkPreviewTheme(
LinkPreviewThemeData(
backgroundColor: Colors.grey[100],
titleStyle: TextStyle(fontWeight: FontWeight.bold),
borderRadius: BorderRadius.circular(12),
// ...other properties
),
);
🔍 URL Detection
Automatically detect URLs in text:
final text = "Check out this cool site: https://flutter.dev and this one www.example.com";
final urls = UrlDetector.detectUrls(text);
for (final match in urls) {
print('URL: ${match.url}, Position: ${match.start}-${match.end}');
// Create a preview for each detected URL
LinkPreview.compact(url: match.url);
}
📦 MetadataProvider
The MetadataProvider
class handles caching and fetching metadata:
final provider = MetadataProvider(
cacheDuration: Duration(hours: 24),
enableCache: true,
);
// Get metadata for a URL
final metadata = await provider.getMetadata('https://flutter.dev');
// Get metadata for multiple URLs in parallel
final metadataList = await provider.getMultipleMetadata([
'https://flutter.dev',
'https://pub.dev',
'https://material.io',
]);
// Clear the cache
await provider.clearCache();
Web Platform Limitations
When using this package in Flutter Web, browser security policies, specifically Cross-Origin Resource Sharing (CORS), restrict direct HTTP requests to external domains. To work around this limitation, consider the following options:
- If you control the server: Enable CORS by configuring the server to include appropriate response headers (e.g.,
Access-Control-Allow-Origin: *
or your app’s domain). This allows the browser to permit requests from your Flutter Web app. - Alternative: Set up your server to act as a proxy. Make a direct request from your Flutter Web app to your server, which then fetches the metadata from the external domain and returns it to your app. This bypasses CORS restrictions entirely, as the request originates server-side.
📄 API Documentation
Main Classes
LinkPreview
- The main widget for displaying link previewsLinkPreviewData
- UI-specific data model for link previewsLinkPreviewController
- Controls the state of link previewsMetadataProvider
- Handles caching and fetching metadataLinkPreviewTheme
- Theme extension for customizing appearanceUrlDetector
- Utility for finding and analyzing URLs in textImageResolver
- Utility for optimizing images
For complete API documentation, please see the API reference.
🙋 FAQ
Q: Does this work with any URL?
A: Yes, the package attempts to extract metadata from any valid URL.
The quality of the preview depends on the metadata available on the target website.
Q: Why do I get errors when fetching metadata on Flutter Web?
A: On Flutter Web, browser CORS restrictions prevent direct requests to external domains. To resolve this, either enable
CORS on the target server (if you control it) by adding headers like Access-Control-Allow-Origin
, or use a proxy
server to fetch the metadata and relay it to your app. See "Web Platform Limitations" for details.
Q: How is caching handled?
A: The package caches metadata in memory and optionally on disk using hive_ce
. You can configure the cache duration
and clear the cache programmatically.
Q: Does it support RTL languages?
A: Yes, the package uses Flutter's logical directional properties (start
/end
instead of left
/right
) for proper
RTL support.
Q: Can I customize the loading animation?
A: Yes, you can provide your own loading widget using the loadingBuilder
parameter.
👨💻 Contributing
Contributions are welcome! If you find a bug or want a feature, please open an issue. If you want to contribute code, please fork the repository and submit a pull request.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
Libraries
- metalink_flutter
- Metalink Flutter - Link Preview Widget Library Flutter UI components for the Metalink metadata extraction package