Skip to content

Instantly share code, notes, and snippets.

@cklanac
Last active August 5, 2022 17:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cklanac/b1a734fe49862dce6926c66dcae14cd2 to your computer and use it in GitHub Desktop.
Save cklanac/b1a734fe49862dce6926c66dcae14cd2 to your computer and use it in GitHub Desktop.
Code Snippets for "The Firebase Realtime Database and Flutter - Firecasts"

Code Snippets for "Firebase Realtime Database and Flutter" https://www.youtube.com/watch?v=sXBJZD0fBa4

NOTES:

  • The files are ordered by timestamp in the video
  • Only the relevant code is provided. Imports have been removed
  • Sections not included:
    • Security Rules
    • Using the emulator
    • FirebaseAnimatedList
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page!'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text("Check out our examples"),
SizedBox(height: 6, width: MediaQuery.of(context).size.width),
ElevatedButton(
onPressed: () {},
child: const Text("Read Examples"),
),
ElevatedButton(
onPressed: () {},
child: const Text("Write Examples"),
),
],
),
));
}
}
class WriteExamples extends StatefulWidget {
const WriteExamples({Key? key}) : super(key: key);
@override
_WriteExamplesState createState() => _WriteExamplesState();
}
class _WriteExamplesState extends State<WriteExamples> {
@override
Widget build(BuildContext context) {
final dailySpecialRef = database.child('dailySpecial');
return Scaffold(
appBar: AppBar(
title: const Text('Write Examples'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Column(
children: [],
),
),
),
);
}
}
class ReadExamples extends StatefulWidget {
const ReadExamples({Key? key}) : super(key: key);
@override
_ReadExamplesState createState() => _ReadExamplesState();
}
class _ReadExamplesState extends State<ReadExamples> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Read Examples'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Column(
children: [],
),
),
),
);
}
}
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ReadExamples(),
),
);
},
child: Text("Read Examples"),
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WriteExamples(),
),
);
},
child: Text("Write Examples"),
),
class _WriteExamplesState extends State<WriteExamples> {
final database = FirebaseDatabase.instance.reference();
@override
Widget build(BuildContext context) {
final dailySpecialRef = database.child('dailySpecial');
return Scaffold(
appBar: AppBar(
title: const Text('Write Examples'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Column(
children: [
ElevatedButton(
// using future
onPressed: () {
dailySpecialRef
.set({
'description': 'Vanilla latte',
'price': 4.99
})
.then((_) => print("Special of the day has been written"))
.catchError((error) => print('You got an error! $error'));
},
child: const Text("Simple set"),
),
],
),
),
),
);
}
}
// using async/await
onPressed: () async {
await dailySpecialRef.set({
'description': 'Vanilla latte',
'price': 4.99
}).catchError((error) => print('You got an error! $error'));
print("Special of the day has been written");
},
// using async/await with try/catch
onPressed: () async {
try {
await dailySpecialRef
.set({'description': 'Vanilla latte', 'price': 4.99});
print("Special of the day has been written");
} catch (e) {
print('You got an error! $e');
}
},
// update price to '2.99'
onPressed: () async {
try {
await dailySpecialRef.child('price').set(2.99);
print("Special of the day has been written");
} catch (e) {
print('You got an error! $e');
}
},
// overwrite 'dailySpecial' with `{'price': 2.99}`
onPressed: () async {
try {
await dailySpecialRef.set({'price': 2.99});
print("Special of the day has been written");
} catch (e) {
print('You got an error! $e');
}
},
// update 'dailySpecial' with `{'price': 2.99}`
onPressed: () async {
try {
await dailySpecialRef.update({'price': 2.99});
print("Special of the day has been written");
} catch (e) {
print('You got an error! $e');
}
},
// update 'dailySpecial' with both price and inventory
onPressed: () async {
try {
await dailySpecialRef.update({
'price': 2.99,
'inventory': 99
});
print("Special of the day has been written");
} catch (e) {
print('You got an error! $e');
}
},
// update data in multiple paths
onPressed: () async {
try {
// note reference is `database`
await database.update({
'dailySpecial/price': 3.99,
'someOtherDailySpecial/price': 3.99
});
print("Special of the day has been written");
} catch (e) {
print('You got an error! $e');
}
},
String getRandomDrink() {
final drinkList = [
'Latte',
'Cappuccino',
'Macchiato',
'Cortado',
'Mocha',
'Drip coffee',
'Cold brew',
'Espresso',
'Vanilla latte',
'Unicorn frappe'
];
return drinkList[Random().nextInt(drinkList.length)];
}
String getRandomName() {
final customerNames = [
'Sam',
'Arthur',
'Jessica',
'Rachel',
'Vivian',
'Todd',
'Morgan',
'Peter',
'David',
'Sumit'
];
return customerNames[Random().nextInt(customerNames.length)];
}
// add another order to list of orders
ElevatedButton(
onPressed: () {
final nextOrder = <String, dynamic>{
'description': getRandomDrink(),
'price': Random().nextInt(800) / 100.0,
'customer': getRandomName(),
'time': DateTime.now().millisecondsSinceEpoch
};
database
.child('orders')
.push()
.set(nextOrder)
.then((_) => print('Drink has been written!'))
.catchError((error) => print('You got an error $error'));
},
child: Text("Append a drink order"),
)
// listen for data on init (d/n have subscription cancel yet)
class ReadExamples extends StatefulWidget {
const ReadExamples({Key? key}) : super(key: key);
@override
_ReadExamplesState createState() => _ReadExamplesState();
}
class _ReadExamplesState extends State<ReadExamples> {
String _displayText = 'Results go here';
final _database = FirebaseDatabase.instance.reference();
@override
void initState() {
super.initState();
_activateListener();
}
void _activateListener() {
_database.child('dailySpecial/description').onValue.listen((event) {
final String? description = event.snapshot.value;
setState(() {
_displayText = 'Today\'s special: $description';
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Read Examples'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Column(
children: [
// Text(_displayText),
// 27:45 adds formatting to Text()
Text(
_displayText,
style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}
// listen for data on init cancel subscription on deactivate
class _ReadExamplesState extends State<ReadExamples> {
String _displayText = 'Results go here';
final _database = FirebaseDatabase.instance.reference();
// 24:50 adds `late` for null safety
late StreamSubscription _dailySpecialStream;
@override
void initState() {
super.initState();
_activateListener();
}
void _activateListener() {
_dailySpecialStream =
_database.child('dailySpecial/description').onValue.listen((event) {
final String? description = event.snapshot.value;
setState(() {
_displayText = 'Today\'s special: $description';
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Read Examples'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Column(
children: [
Text(
_displayText,
style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
@override
void deactivate() {
_dailySpecialStream.cancel();
super.deactivate();
}
}
// fetch object as Map
void _activateListener() {
_dailySpecialStream =
_database.child('dailySpecial').onValue.listen((event) {
final data = Map<String, dynamic>.from(event.snapshot.value);
final description = data['description'] as String;
final price = data['price'] as double;
setState(() {
_displayText =
'Today\'s special: $description for just \$${price.toStringAsFixed(2)}';
});
});
}
// create class for daily specials
class DailySpecial {
final String description;
final double price;
DailySpecial({required this.description, required this.price});
factory DailySpecial.fromRTDB(Map<String, dynamic> data) {
return DailySpecial(
description: data['description'] ?? 'Drink',
price: data['price'] ?? 0.0);
}
String fancyDescription() {
return 'Today\'s special: A delicious $description for the low low price of \$${price.toStringAsFixed(2)}';
}
}
void _activateListener() {
_dailySpecialStream =
_database.child('dailySpecial').onValue.listen((event) {
final data = new Map<String, dynamic>.from(event.snapshot.value);
final dailySpecial = DailySpecial.fromRTDB(data);
setState(() {
_displayText = dailySpecial.fancyDescription();
});
});
}
@override
void initState() {
super.initState();
// _activateListener();
_performSingleFetch();
}
void _performSingleFetch() {
// _database.child('dailySpecial').once().then((snapshot) {
_database.child('dailySpecial').get().then((snapshot) {
final data = Map<String, dynamic>.from(snapshot.value);
final dailySpecial = DailySpecial.fromRTDB(data);
setState(() {
_displayText = dailySpecial.fancyDescription();
});
});
}
// REMOVED FOR BREVITY
// void _activateListener() {
// ...
// }
// @override
// Widget build(BuildContext context) {
// ...
// }
@override
void deactivate() {
// _dailySpecialStream.cancel();
super.deactivate();
}
StreamBuilder(
stream: _database
.child('orders')
.orderByKey()
.limitToLast(10)
.onValue,
builder: (contect, snapshot) {
final tilesList = <ListTile>[];
if (snapshot.hasData) {
final myOrders = Map<String, dynamic>.from(
(snapshot.data! as Event).snapshot.value);
myOrders.forEach(
(key, value) {
final nextOrder = Map<String, dynamic>.from(value);
final orderTile = ListTile(
leading: const Icon(Icons.local_cafe),
title: Text(nextOrder['description']),
subtitle: Text(nextOrder['customer']),
);
tilesList.add(orderTile);
},
);
}
return Expanded(
child: ListView(
children: tilesList,
),
);
},
)
class Order {
final String description;
final double price;
final String customerName;
final DateTime timestamp;
Order({
required this.description,
required this.price,
required this.customerName,
required this.timestamp,
});
factory Order.fromRTDB(Map<String, dynamic> data) {
return Order(
description: data['description'] ?? 'Drink',
price: data['price'] ?? 0.0,
customerName: data['customer'] ?? 'Unknown',
timestamp: (data['time'] != null)
? DateTime.fromMicrosecondsSinceEpoch(data['time'])
: DateTime.now(),
);
}
}
StreamBuilder(
stream: _database
.child('orders')
.orderByKey()
.limitToLast(10)
.onValue,
builder: (contect, snapshot) {
final tilesList = <ListTile>[];
if (snapshot.hasData) {
final myOrders = Map<String, dynamic>.from(
(snapshot.data! as Event).snapshot.value);
tilesList.addAll(
myOrders.values.map(
(value) {
final nextOrder =
Order.fromRTDB(Map<String, dynamic>.from(value));
return ListTile(
leading: const Icon(Icons.local_cafe),
title: Text(nextOrder.description),
subtitle: Text(nextOrder.customerName),
);
},
),
);
}
return Expanded(
child: ListView(
children: tilesList,
),
);
},
)
FutureBuilder(
future: _database.child('dailySpecial').get(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final dailySpecial = DailySpecial.fromRTDB(
Map<String, dynamic>.from(
(snapshot.data! as DataSnapshot).value));
return Text(
dailySpecial.fancyDescription(),
style: const TextStyle(
fontSize: 18.0, fontWeight: FontWeight.w600),
textAlign: TextAlign.center,
);
} else {
return const CircularProgressIndicator();
}
},
),
// Refactor into StatelessWidget
class ReadExamplesStateless extends StatelessWidget {
final _database = FirebaseDatabase.instance.reference();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Read Examples'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Column(
children: [
StreamBuilder(
stream: _database.child('dailySpecial').onValue,
builder: (context, snapshot) {
if (snapshot.hasData) {
final dailySpecial = DailySpecial.fromRTDB(
Map<String, dynamic>.from(
(snapshot.data! as Event).snapshot.value));
return Text(
dailySpecial.fancyDescription(),
style: const TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
);
} else {
return const CircularProgressIndicator();
}
},
),
const SizedBox(
height: 5,
),
StreamBuilder(
stream: _database
.child('orders')
.orderByKey()
.limitToLast(10)
.onValue,
builder: (contect, snapshot) {
final tilesList = <ListTile>[];
if (snapshot.hasData) {
final myOrders = Map<String, dynamic>.from(
(snapshot.data! as Event).snapshot.value);
tilesList.addAll(
myOrders.values.map(
(value) {
final nextOrder =
Order.fromRTDB(Map<String, dynamic>.from(value));
return ListTile(
leading: const Icon(Icons.local_cafe),
title: Text(nextOrder.description),
subtitle: Text(nextOrder.customerName),
);
},
),
);
}
return Expanded(
child: ListView(
children: tilesList,
),
);
},
)
],
),
),
),
);
}
}
class OrderStreamPublisher {
final _database = FirebaseDatabase.instance.reference();
Stream<List<Order>> getOrderStream() {
final orderStream = _database.child('orders').onValue;
final streamToPublish = orderStream.map((event) {
final orderMap = Map<String, dynamic>.from(event.snapshot.value);
final orderList = orderMap.entries.map((element) {
return Order.fromRTDB(Map<String, dynamic>.from(element.value));
}).toList();
return orderList;
});
return streamToPublish;
}
}
// Extract data access to `order_stream_publisher.dart`
StreamBuilder(
stream: OrderStreamPublisher().getOrderStream(),
builder: (contect, snapshot) {
final tilesList = <ListTile>[];
if (snapshot.hasData) {
final myOrders = snapshot.data as List<Order>;
tilesList.addAll(
myOrders.map(
(nextOrder) {
return ListTile(
leading: const Icon(Icons.local_cafe),
title: Text(nextOrder.description),
subtitle: Text(nextOrder.customerName),
);
},
),
);
}
return Expanded(
child: ListView(
children: tilesList,
),
);
},
)
// Note: Todd adds `ORDERS_PATH` and `get orders` right away but doesn't use it until later
class CafeModel extends ChangeNotifier {
DailySpecial? _dailySpecial;
List<Order> _orders = [];
final _db = FirebaseDatabase.instance.reference();
static const DAILY_SPECIAL_PATH = 'dailySpecial';
static const ORDERS_PATH = 'orders';
late StreamSubscription<Event> _dailySpecialStream;
DailySpecial? get dailySpecial => _dailySpecial;
List<Order> get orders => _orders;
CafeModel() {
_listenToDailySpecial();
}
void _listenToDailySpecial() {
_dailySpecialStream = _db.child(DAILY_SPECIAL_PATH).onValue.listen(
(event) {
// _dailySpecial = DailySpecial.fromRTDB(event.snapshot.value);
// Note at 44:50 add Map<>.from() to fix bug
_dailySpecial = DailySpecial.fromRTDB(
Map<String, dynamic>.from(event.snapshot.value));
notifyListeners();
},
);
}
@override
void dispose() {
_dailySpecialStream.cancel();
super.dispose();
}
}
class CafeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Provider Example'),
),
);
}
}
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider<CafeModel>(
create: (_) => CafeModel(),
child: CafeView(),
),
),
);
},
child: const Text("Provider Example"),
),
class CafeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Provider Example'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Text('Provider Example'),
Consumer<CafeModel>(
builder: (context, model, child) {
if (model.dailySpecial != null) {
return Text(model.dailySpecial!.fancyDescription());
} else {
return const CircularProgressIndicator();
}
},
),
],
),
),
);
}
}
class CafeModel extends ChangeNotifier {
DailySpecial? _dailySpecial;
List<Order> _orders = [];
final _db = FirebaseDatabase.instance.reference();
static const DAILY_SPECIAL_PATH = 'dailySpecial';
static const ORDERS_PATH = 'orders';
late StreamSubscription<Event> _dailySpecialStream;
// Add
late StreamSubscription<Event> _orderStream;
DailySpecial? get dailySpecial => _dailySpecial;
List<Order> get orders => _orders;
CafeModel() {
_listenToDailySpecial();
// Add
_listenToOrders();
}
void _listenToDailySpecial() {
_dailySpecialStream = _db.child(DAILY_SPECIAL_PATH).onValue.listen(
(event) {
// _dailySpecial = DailySpecial.fromRTDB(event.snapshot.value);
// 44:50 add Map<>.from() to fix bug
_dailySpecial = DailySpecial.fromRTDB(
Map<String, dynamic>.from(event.snapshot.value));
notifyListeners();
},
);
}
// Add
void _listenToOrders() {
_orderStream = _db.child(ORDERS_PATH).onValue.listen((event) {
final allOrders = Map<String, dynamic>.from(event.snapshot.value);
_orders = allOrders.values
.map((orderAsJSON) =>
Order.fromRTDB(Map<String, dynamic>.from(orderAsJSON)))
.toList();
notifyListeners();
});
}
@override
void dispose() {
_dailySpecialStream.cancel();
// Add
_orderStream.cancel();
super.dispose();
}
}
class CafeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Provider Example'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Text('Provider Example'),
Consumer<CafeModel>(
builder: (context, model, child) {
if (model.dailySpecial != null) {
return Text(model.dailySpecial!.fancyDescription());
} else {
return const CircularProgressIndicator();
}
},
),
// Add from here down
const SizedBox(
height: 10,
),
const Text(
"ORDERS",
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w600,
),
),
Consumer<CafeModel>(
builder: (context, model, child) {
return Expanded(
child: ListView(
children: [
...model.orders.map((order) => Card(
child: ListTile(
leading: const Icon(Icons.local_cafe_outlined),
title: Text(order.description),
subtitle: Text(
'${order.customerName} \$${order.price}'),
),
))
],
),
);
},
),
],
),
),
);
}
}
ElevatedButton(
onPressed: Provider.of<CafeModel>(context, listen: false)
.repriceDailySpecial,
child: Text("Randomize price"),
),
repriceDailySpecial() {
final newPrice = Random().nextInt(1000) / 100.0;
// Do not do this
// if (_dailySpecial != null) {
// _dailySpecial!.price = newPrice;
// }
// Do this instead. (48:01)
_db.child(DAILY_SPECIAL_PATH).update({'price': newPrice});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment