Skip to content

Instantly share code, notes, and snippets.

@callmephil
Created October 15, 2024 13:58
Show Gist options
  • Save callmephil/aaffeb12183cd85ffd5ffc6058475c6a to your computer and use it in GitHub Desktop.
Save callmephil/aaffeb12183cd85ffd5ffc6058475c6a to your computer and use it in GitHub Desktop.
(Flutter) Firestore stream example
class AlertModel {
final String id;
final String title;
final String message;
final DateTime createdAt;
const AlertModel({
required this.id,
required this.title,
required this.message,
required this.createdAt,
});
String get timeAgo {
final now = DateTime.now();
final diff = now.difference(createdAt);
if (diff.inDays > 365) {
return '${diff.inDays ~/ 365}y';
} else if (diff.inDays > 30) {
return '${diff.inDays ~/ 30}mo';
} else if (diff.inDays > 7) {
return '${diff.inDays ~/ 7}w';
} else if (diff.inDays > 0) {
return '${diff.inDays}d';
} else if (diff.inHours > 0) {
return '${diff.inHours}h';
} else if (diff.inMinutes > 0) {
return '${diff.inMinutes}m';
}
return 'Just Now';
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'message': message,
'createdAt': createdAt.toIso8601String(),
};
}
factory AlertModel.fromJson(Map<String, dynamic> json) {
return AlertModel(
id: json['id'],
title: json['title'],
message: json['message'],
createdAt: DateTime.parse(json['createdAt']),
);
}
AlertModel copyWith({
String? title,
String? message,
DateTime? createdAt,
}) {
return AlertModel(
id: id,
title: title ?? this.title,
message: message ?? this.message,
createdAt: createdAt ?? this.createdAt,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is AlertModel &&
other.id == id &&
other.title == title &&
other.message == message &&
other.createdAt == createdAt;
}
@override
int get hashCode {
return id.hashCode ^ title.hashCode ^ message.hashCode ^ createdAt.hashCode;
}
@override
String toString() =>
'AlertsModel(title: $title, message: $message, createdAt: $createdAt)';
}
import 'dart:developer';
import 'package:alerts/red_alert_model.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class AlertService {
const AlertService();
static const String _alertsCollection = 'alerts';
static final CollectionReference _dbRef =
FirebaseFirestore.instance.collection(_alertsCollection);
Future<void> create(AlertModel alerts) async {
try {
await _dbRef.doc(alerts.id).set(alerts.toJson());
} catch (e) {
log('Error creating alerts: $e');
}
}
Future<void> update(AlertModel alerts) async {
try {
await _dbRef.doc(alerts.id).update(alerts.toJson());
} catch (e) {
log('Error updating alerts: $e');
}
}
Future<void> delete(String alertsId) async {
try {
await _dbRef.doc(alertsId).delete();
} catch (e) {
log('Error deleting alerts: $e');
}
}
Future<AlertModel?> getById(String alertsId) async {
try {
final snapshot = await _dbRef.doc(alertsId).get();
if (!snapshot.exists) return null;
final json = snapshot.data();
if (json is! Map<String, dynamic>) {
throw Exception('Invalid JSON data');
}
return AlertModel.fromJson(json);
} catch (e) {
log('Error fetching alerts: $e');
return null;
}
}
Stream<List<AlertModel>> getStream() {
return _dbRef.snapshots().map((snapshot) {
return snapshot.docs.map((doc) {
final json = doc.data();
if (json is! Map<String, dynamic>) {
throw Exception('Invalid JSON data');
}
return AlertModel.fromJson(json);
}).toList(growable: false);
});
}
}
import 'package:alerts/firebase_options.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'firestore_service.dart';
import 'red_alert_model.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Firebase
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseFirestore.instance.settings = const Settings(
persistenceEnabled: true,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Alert Service Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AlertScreen(),
);
}
}
// TODO: DEMO ONLY
class AlertScreen extends StatefulWidget {
const AlertScreen({super.key});
@override
State<AlertScreen> createState() => _AlertScreenState();
}
class _AlertScreenState extends State<AlertScreen> {
final AlertService _alertService = const AlertService();
final TextEditingController _idController = TextEditingController();
final TextEditingController _titleController = TextEditingController();
final TextEditingController _messageController = TextEditingController();
// for updating alerts
DateTime? _dateTime;
@override
void dispose() {
_idController.dispose();
_titleController.dispose();
_messageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Alert Service Demo'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _idController,
decoration: const InputDecoration(labelText: 'Alert ID'),
),
TextField(
controller: _titleController,
decoration: const InputDecoration(labelText: 'Alert Title'),
),
TextField(
controller: _messageController,
decoration: const InputDecoration(labelText: 'Alert Message'),
),
Row(
children: [
ElevatedButton(
onPressed: _createAlert,
child: const Text('Create'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _updateAlert,
child: const Text('Update'),
),
],
),
Expanded(
child: StreamBuilder<List<AlertModel>>(
stream: _alertService.getStream(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
final alerts = snapshot.data ?? [];
return ListView.builder(
itemCount: alerts.length,
itemBuilder: (context, index) {
final alert = alerts[index];
return ListTile(
title: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: Text(alert.title)),
const SizedBox(width: 8),
Text(
alert.timeAgo,
style: const TextStyle(fontSize: 12),
),
],
),
subtitle: Text(alert.message),
onTap: () => _fillUpdateFields(alert),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _deleteAlert(alert.id),
),
);
},
);
},
),
),
],
),
),
);
}
void _fillUpdateFields(AlertModel alert) {
_idController.text = alert.id;
_titleController.text = alert.title;
_messageController.text = alert.message;
_dateTime = alert.createdAt;
}
void _createAlert() {
final alert = AlertModel(
id: _idController.text,
title: _titleController.text,
message: _messageController.text,
createdAt: DateTime.now(),
);
_alertService.create(alert);
}
void _updateAlert() {
final alert = AlertModel(
id: _idController.text,
title: _titleController.text,
message: _messageController.text,
createdAt: _dateTime!,
);
_alertService.update(alert);
}
void _deleteAlert(String id) {
_alertService.delete(id);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment