LaunchlySites Flutter SDK

pub package pub points popularity License: MIT

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