tmdb_flutter 0.1.0
tmdb_flutter: ^0.1.0 copied to clipboard
A comprehensive Flutter package for TMDB (The Movie Database) integration with modern UI components, search, and metadata display.
example/lib/main.dart
// example/lib/main.dart - Update to include new features
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tmdb_flutter/tmdb_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
// Import providers
Future<void> main() async {
// Initialize the TMDB Flutter package with your API key
await dotenv.load(fileName: '.env');
TmdbFlutter.init(apiKey: dotenv.env['TMDB_API_KEY'] ?? '');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => SearchProvider()),
ChangeNotifierProvider(create: (_) => TitleDetailsProvider()),
ChangeNotifierProvider(create: (_) => SeasonEpisodesProvider()),
ChangeNotifierProvider(create: (_) => PersonProvider()),
ChangeNotifierProvider(create: (_) => GenreProvider()),
ChangeNotifierProvider(create: (_) => DiscoveryProvider()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'TMDB Flutter Example',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _currentIndex = 0;
final List<Widget> _pages = [
const DiscoveryPage(),
const SearchPage(),
const GenresPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TMDB Flutter Demo'),
),
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Discover',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
label: 'Genres',
),
],
),
);
}
}
/// Discovery page for trending and popular content
class DiscoveryPage extends StatelessWidget {
const DiscoveryPage({super.key});
@override
Widget build(BuildContext context) {
return TmdbDiscoveryWidget(
onTitleSelected: (title) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TitleDetailsPage(
titleId: title.id,
titleType: title.type,
),
),
);
},
);
}
}
/// Search page for finding content
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
TmdbTitle? _selectedTitle;
String _searchQuery = '';
TmdbSearchCategory _searchCategory = TmdbSearchCategory.multi;
bool _showSearchResults = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
// Search bar - fixed at the top
Padding(
padding: const EdgeInsets.all(16.0),
child: TmdbSearchBar(
onTitleSelected: (title) {
setState(() {
_selectedTitle = title;
});
// Navigate to details page
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TitleDetailsPage(
titleId: title.id,
titleType: title.type,
),
),
);
},
onSearchCleared: () {
setState(() {
_selectedTitle = null;
_showSearchResults = false;
_searchQuery = '';
});
},
// Handle Enter key press with onSearchSubmitted
onSearchSubmitted: (query, category) {
setState(() {
_searchQuery = query;
_searchCategory = category;
_showSearchResults = true;
});
},
),
),
// Content - takes remaining screen space with lazy loading
Expanded(
child: _buildContent(),
),
],
);
}
Widget _buildContent() {
// Show search results either when Enter key was pressed or a title was selected
if (_showSearchResults || _selectedTitle != null) {
final query = _selectedTitle?.name ?? _searchQuery;
final category = _selectedTitle != null
? (_selectedTitle!.type == TmdbTitleType.movie
? TmdbSearchCategory.movie
: TmdbSearchCategory.tv)
: _searchCategory;
// Use the updated lazy loading search results widget
return TmdbSearchResults(
query: query,
category: category,
scrollable: true, // Enable scrolling
scrollThreshold: 0.8, // Load more when 80% scrolled
onTitleSelected: (title) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TitleDetailsPage(
titleId: title.id,
titleType: title.type,
),
),
);
},
);
} else {
// Show a placeholder when no search has been performed
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.search, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text(
'Search for a movie or TV show',
style: TextStyle(fontSize: 18, color: Colors.grey),
),
SizedBox(height: 8),
Text(
'Press Enter or select a suggestion to see results',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
],
),
);
}
}
}
/// Genres page for browsing by genre
class GenresPage extends StatelessWidget {
const GenresPage({super.key});
@override
Widget build(BuildContext context) {
return TmdbGenreSearch(
onTitleSelected: (title) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TitleDetailsPage(
titleId: title.id,
titleType: title.type,
),
),
);
},
);
}
}
/// Title details page
class TitleDetailsPage extends StatelessWidget {
final int titleId;
final TmdbTitleType titleType;
const TitleDetailsPage({
super.key,
required this.titleId,
required this.titleType,
});
@override
Widget build(BuildContext context) {
// Reset the providers for a fresh state
WidgetsBinding.instance.addPostFrameCallback((_) {
Provider.of<TitleDetailsProvider>(context, listen: false)
.loadTitleDetails(titleId, titleType);
});
return Scaffold(
body: TmdbTitleDetailsWidget(
titleId: titleId,
titleType: titleType,
onSimilarTitleSelected: (id, type) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TitleDetailsPage(
titleId: id,
titleType: type,
),
),
);
},
onCastMemberSelected: (personId) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PersonDetailsPage(
personId: personId,
),
),
);
},
onTrailerTap: (trailerUrl) async {
final url = Uri.parse(trailerUrl);
try {
// Force using external browser instead of in-app webview
await launchUrl(
url,
mode: LaunchMode.externalApplication,
);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Could not open trailer: $e')),
);
}
}
},
),
);
}
}
/// Person details page
class PersonDetailsPage extends StatelessWidget {
final int personId;
const PersonDetailsPage({
super.key,
required this.personId,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Person Details'),
),
body: TmdbPersonDetailsWidget(
personId: personId,
onTitleSelected: (title) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TitleDetailsPage(
titleId: title.id,
titleType: title.type,
),
),
);
},
),
);
}
}