firebase_core_plus 1.0.1
firebase_core_plus: ^1.0.1 copied to clipboard
A Flutter package for network operations.
example/lib/main.dart
/// Firebase Core Plus Example Application
///
/// This example demonstrates the basic usage of the firebase_core_plus package:
/// - Type-safe model implementation with InterfacePlus
/// - Simple CRUD operations with FirestorePlus
/// - Real-time streams for live data updates
/// - Modern Flutter UI with Material Design
///
/// ## Features Demonstrated
///
/// 1. **Cart Management**: Create and view shopping carts
/// 2. **Real-time Updates**: Live updates when data changes in Firestore
///
/// ## Firebase Setup
///
/// This example uses Firebase emulator for local development:
/// - No real Firebase project required
/// - Safe for testing and development
/// - Easy to set up and run locally
///
/// ## Model Architecture
///
/// The example uses the Cart model:
/// - **Cart**: Main collection with shopping cart data
///
/// The model implements InterfacePlus for type safety and serialization.
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core_plus/firestore_core_plus.dart';
/// Main entry point of the application.
///
/// Initializes Firebase with emulator configuration for local development.
/// This allows testing without a real Firebase project.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase with configuration
// Note: These are fake values for emulator testing
// In production, use your actual Firebase project configuration
await Firebase.initializeApp(
options: const FirebaseOptions(
apiKey: "demo-api-key",
authDomain: "demo-project.firebaseapp.com",
projectId: "demo-project",
storageBucket: "demo-project.appspot.com",
messagingSenderId: "123456789",
appId: "demo-app-id",
),
);
// Connect to Firebase emulator for local development
// This allows testing without a real Firebase backend
FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080);
runApp(const MyApp());
}
/// Shopping cart model implementing InterfacePlus.
///
/// This model demonstrates:
/// - Proper InterfacePlus implementation
/// - Type-safe serialization/deserialization
/// - Business logic methods (getters)
/// - Safe type casting in withMap constructor
///
/// ## Properties
/// - **uid**: Document identifier (nullable for new documents)
/// - **name**: Cart name/description
/// - **price**: Total cart price
///
/// ## Business Logic
/// - **priceWithTVA**: Calculates price with 20% tax
/// - **isExpensive**: Determines if cart is expensive (>50€)
class Cart implements InterfacePlus {
Cart({required this.uid, required this.name, required this.price});
@override
String? uid;
final String name;
final double price;
/// JSON serialization for Firestore storage.
///
/// Converts the Cart instance to a Map that can be stored in Firestore.
/// Includes all necessary fields for reconstruction.
@override
Map<String, dynamic> get json => {
'uid': uid,
'name': name,
'price': price,
};
/// Factory constructor for deserialization from Firestore data.
///
/// Handles safe type casting and provides default values for missing fields.
/// This constructor is called by FirestorePlus when reading documents.
static Cart withMap(Map<String, dynamic> map) => Cart(
uid: map['uid'],
name: map['name'] ?? 'Unnamed Cart',
price: (map['price'] as num?)?.toDouble() ?? 0.0,
);
/// Calculates the price including 20% VAT.
///
/// This is an example of business logic that can be added to model classes.
/// The calculation is performed on the client side for immediate feedback.
double get priceWithTVA => price * 1.2;
/// Determines if the cart is considered expensive (>50€).
///
/// Another example of business logic that can be encapsulated in the model.
/// This can be used for UI decisions like showing warnings or special styling.
bool get isExpensive => price > 50;
}
/// Main application widget.
///
/// Sets up the Material Design theme and navigation structure.
/// Uses a modern indigo color scheme for a professional appearance.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FirestorePlus Demo',
theme: ThemeData(
primarySwatch: Colors.indigo,
useMaterial3: true,
cardTheme: CardThemeData(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
),
),
home: const CartDemoPage(),
);
}
}
/// Main demo page showcasing FirestorePlus features.
///
/// This page demonstrates:
/// - Simple cart creation
/// - Real-time cart display
/// - Modern UI with cards and icons
class CartDemoPage extends StatefulWidget {
const CartDemoPage({super.key});
@override
State<CartDemoPage> createState() => _CartDemoPageState();
}
/// State management for the cart demo page.
///
/// Manages:
/// - FirestorePlus instance for cart operations
/// - UI state and user interactions
class _CartDemoPageState extends State<CartDemoPage> {
late final FirestorePlus<Cart> cartCollection;
@override
void initState() {
super.initState();
// Initialize FirestorePlus instance for cart operations
// This instance will handle all cart-related Firestore operations
cartCollection = FirestorePlus<Cart>.instance(
tConstructor: Cart.withMap,
path: 'carts',
);
}
/// Adds a new cart to the collection.
///
/// Demonstrates:
/// - Creating new documents with auto-generated IDs
/// - Type-safe object creation
/// - User feedback with SnackBar
/// - Error handling
Future<void> _addCart() async {
try {
// Create a new cart with a unique name based on timestamp
final cart = Cart(
uid: '', // Empty UID for new documents
name: 'Cart ${DateTime.now().millisecondsSinceEpoch}',
price: 19.99,
);
// Add the cart to Firestore and get the generated ID
final id = await cartCollection.add(object: cart);
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Cart added with ID: $id'),
backgroundColor: Colors.green,
),
);
} catch (e) {
// Handle errors and show user feedback
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error adding cart: $e'),
backgroundColor: Colors.red,
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('FirestorePlus Demo'),
backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
elevation: 4,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Action buttons section
_buildActionButtons(),
const SizedBox(height: 24),
// Real-time carts stream section
_buildCartsStream(),
],
),
),
);
}
/// Builds the action buttons section.
///
/// Contains button for:
/// - Adding new carts
Widget _buildActionButtons() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Actions',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Center(
child: ElevatedButton.icon(
onPressed: _addCart,
icon: const Icon(Icons.add_shopping_cart),
label: const Text('Add Cart'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
),
),
),
],
),
),
);
}
/// Builds the real-time carts stream section.
///
/// Displays:
/// - Live updates of all carts
/// - Cart information with business logic
/// - Visual indicators for expensive carts
/// - Loading and empty states
Widget _buildCartsStream() {
return Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Real-time Carts Stream:',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Expanded(
child: StreamBuilder<List<Cart>>(
stream: cartCollection.streams,
builder: (context, snapshot) {
// Handle loading state
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Loading carts...'),
],
),
);
}
// Handle error state
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
size: 64,
color: Colors.red,
),
const SizedBox(height: 16),
Text(
'Error: ${snapshot.error}',
style: const TextStyle(color: Colors.red),
textAlign: TextAlign.center,
),
],
),
);
}
// Handle empty state
final carts = snapshot.data ?? [];
if (carts.isEmpty) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.shopping_cart_outlined,
size: 64,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
'No carts available.\nAdd a cart to get started!',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
textAlign: TextAlign.center,
),
],
),
);
}
// Display carts list
return ListView.builder(
itemCount: carts.length,
itemBuilder: (context, index) {
final cart = carts[index];
return Card(
margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile(
leading: CircleAvatar(
backgroundColor: cart.isExpensive
? Colors.orange
: Colors.green,
child: Icon(
cart.isExpensive
? Icons.warning
: Icons.check_circle,
color: Colors.white,
),
),
title: Text(
cart.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Price: ${cart.price.toStringAsFixed(2)} €'),
Text(
'TTC: ${cart.priceWithTVA.toStringAsFixed(2)} €',
style: const TextStyle(
fontWeight: FontWeight.w500,
color: Colors.blue,
),
),
],
),
trailing: cart.isExpensive
? const Chip(
label: Text('Expensive'),
backgroundColor: Colors.orange,
labelStyle: TextStyle(color: Colors.white),
)
: const Chip(
label: Text('Affordable'),
backgroundColor: Colors.green,
labelStyle: TextStyle(color: Colors.white),
),
),
);
},
);
},
),
),
],
),
),
),
);
}
}