launchlysites_flutter_sdk 1.0.0
launchlysites_flutter_sdk: ^1.0.0 copied to clipboard
Official Flutter SDK for LaunchlySites CMS - Build beautiful mobile apps with headless content management and dynamic content delivery.
LaunchlySites Flutter SDK #
Official Flutter SDK for LaunchlySites CMS - Build beautiful mobile apps with headless content management.
Installation #
Add this to your pubspec.yaml
:
dependencies:
launchlysites_flutter_sdk: ^1.0.0
Then run:
flutter pub get
Quick Start #
1. Setup Provider #
Wrap your app with the LaunchlySites provider:
import 'package:flutter/material.dart';
import 'package:launchlysites_flutter_sdk/launchlysites_flutter_sdk.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LaunchlySitesProvider(
client: LaunchlySitesClient(
LaunchlySitesConfig(
apiUrl: 'https://your-cms-domain.com',
projectId: 'your-project-id',
apiKey: 'your-api-key', // Optional for public content
),
),
child: MaterialApp(
title: 'My App',
home: HomeScreen(),
),
);
}
}
2. Display Content List #
import 'package:flutter/material.dart';
import 'package:launchlysites_flutter_sdk/launchlysites_flutter_sdk.dart';
class BlogScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Blog Posts')),
body: ContentListView(
contentTypeId: 'your-blog-content-type-id',
status: 'published',
itemBuilder: (context, content) {
return Card(
margin: EdgeInsets.all(8.0),
child: ListTile(
title: Text(content.title),
subtitle: Text(content.data['excerpt'] ?? ''),
leading: content.data['featured_image'] != null
? LaunchlySitesImage(
path: content.data['featured_image'],
width: 80,
height: 80,
borderRadius: 8.0,
)
: null,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlogPostScreen(
contentId: content.id,
contentTypeId: 'your-blog-content-type-id',
),
),
);
},
),
);
},
),
);
}
}
3. Display Single Content Item #
import 'package:flutter/material.dart';
import 'package:launchlysites_flutter_sdk/launchlysites_flutter_sdk.dart';
class BlogPostScreen extends StatelessWidget {
final String contentId;
final String contentTypeId;
const BlogPostScreen({
Key? key,
required this.contentId,
required this.contentTypeId,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<Content>(
future: LaunchlySitesProvider.of(context)
.getContentItem(contentTypeId, contentId),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
final content = snapshot.data!;
return CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
title: Text(content.title),
background: content.data['featured_image'] != null
? LaunchlySitesImage(
path: content.data['featured_image'],
fit: BoxFit.cover,
)
: Container(color: Colors.grey),
),
),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
content.title,
style: Theme.of(context).textTheme.headlineMedium,
),
SizedBox(height: 16),
Text(
content.data['content'] ?? '',
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
),
),
],
);
},
),
);
}
}
4. Search Content #
class SearchScreen extends StatefulWidget {
@override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
final _searchController = TextEditingController();
List<Content> _searchResults = [];
bool _isLoading = false;
void _search() async {
if (_searchController.text.isEmpty) return;
setState(() {
_isLoading = true;
});
try {
final client = LaunchlySitesProvider.of(context);
final response = await client.searchContent(_searchController.text);
setState(() {
_searchResults = response.data;
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Search failed: $e')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Search content...',
suffixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: _search,
),
),
onSubmitted: (_) => _search(),
),
),
body: _isLoading
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: _searchResults.length,
itemBuilder: (context, index) {
final content = _searchResults[index];
return ListTile(
title: Text(content.title),
subtitle: Text(content.data['excerpt'] ?? ''),
onTap: () {
// Navigate to content detail
},
);
},
),
);
}
}
Widgets #
LaunchlySitesImage #
Displays optimized images from your CMS:
LaunchlySitesImage(
path: 'path/to/image.jpg',
width: 200,
height: 200,
quality: 80,
fit: BoxFit.cover,
borderRadius: 12.0,
placeholder: 'assets/loading.png',
errorWidget: Icon(Icons.error),
)
ContentListView #
Displays a paginated list of content with automatic loading:
ContentListView(
contentTypeId: 'content-type-id',
status: 'published',
limit: 20,
enablePagination: true,
itemBuilder: (context, content) {
return YourContentWidget(content);
},
loadingBuilder: (context) => YourLoadingWidget(),
errorBuilder: (context, error) => YourErrorWidget(error),
emptyBuilder: (context) => YourEmptyWidget(),
)
Direct Client Usage #
You can also use the client directly:
final client = LaunchlySitesClient(
LaunchlySitesConfig(
apiUrl: 'https://your-cms-domain.com',
projectId: 'your-project-id',
apiKey: 'your-api-key',
),
);
// Get content types
final contentTypes = await client.getContentTypes();
// Get content
final content = await client.getContent('content-type-id');
// Search content
final searchResults = await client.searchContent('query');
// Generate optimized image URLs
final imageUrl = client.generateImageUrl(
'path/to/image.jpg',
width: 400,
height: 300,
quality: 85,
);
Configuration #
Environment Variables #
Create a .env
file or use build configurations:
// For development
const config = LaunchlySitesConfig(
apiUrl: 'http://localhost:8081',
projectId: 'dev-project-id',
apiKey: 'dev-api-key',
);
// For production
const config = LaunchlySitesConfig(
apiUrl: 'https://your-cms-domain.com',
projectId: 'prod-project-id',
apiKey: 'prod-api-key',
);
Error Handling #
The SDK includes built-in error handling:
try {
final content = await client.getContent('content-type-id');
} on Exception catch (e) {
// Handle network or API errors
print('Error: $e');
}
Caching #
Images are automatically cached using cached_network_image
. You can configure caching behavior:
// Images are cached automatically
LaunchlySitesImage(
path: 'image.jpg',
// Cached version will be used on subsequent loads
)
License #
MIT