Welcome to the Flutter Donation App Workshop! In this workshop, you'll learn how to build a donation app using Flutter and Supabase through Zapprun. This guide will walk you through each step of the development process.
- 📌 Introduction to Flutter
- 📌 Introduction to Zapprun
- 📌 Introduction to Supabase
- ⚡ Setting Up Zapprun
- 🎨 Building Basic Flutter UI
- ☁ Setup Supabase
- 🚀 Running Your App
- 🎯 Conclusion
- 🏍 About Panther Racing Auth Team
- 📝 Wikipedia
Flutter is an open-source UI toolkit developed by Google for building natively compiled applications for mobile, web, and desktop from a single codebase.
✅ Widgets – The fundamental building blocks of a Flutter app. Everything is a widget!
✅ Stateful vs Stateless Widgets
- Stateless Widgets: Immutable, do not change over time.
- Stateful Widgets: Maintain state, can change dynamically.
✅ Hot Reload – Instantly see code changes without restarting the app.
✅ Material Design – Pre-designed UI components following Google’s Material Design guidelines.
Zapprun is a cloud-based development platform that enables developers to build, test, and deploy Flutter applications without any local setup. It provides an instant coding environment with all the necessary tools, making development more accessible and collaborative.
💻 Why Zapprun?
⚡ No installation required – Run Flutter apps without setting up an emulator locally.
⚡ Instant setup – Start coding immediately with a pre-configured environment.
⚡ Cloud-based testing – Test your app in the cloud without performance issues.
⚡ Easy collaboration – Work on projects with your team effortlessly.
Supabase is an open-source backend-as-a-service (BaaS) that provides a PostgreSQL-powered database, authentication, and real-time capabilities. It allows developers to build scalable applications without managing complex backend infrastructure.
☁️ Why Supabase?
✅ Open-source alternative to Firebase – Own your backend with no vendor lock-in.
✅ Built-in authentication – Secure user authentication out of the box.
✅ Database management – PostgreSQL-powered database with real-time syncing.
✅ Serverless functions – Run backend logic without managing servers.
1️⃣ Go to Zapprun and create a new Flutter project.
2️⃣ Name your project → donation_app (or any name you prefer).
3️⃣ Wait for initialization → Zapprun will set up your project automatically.
Once your project is ready, structure your files as follows:
📂 Project Directory: donation_app/
📂 donation_app/
│── 📂 lib/
│ ├── 📄 main.dart # Entry point of the app
│ ├── 📄 home.dart # Home screen UI
│ ├── 📄 leaderboard.dart # Leaderboard screen UI
│ ├── 📄 donation_item.dart # Donation Item Model
│ ├── 📄 donation_item_list.dart # Donation Item View Class
│ ├── 📄 donator.dart # Donator Model
│ ├── 📄 supabase_config.dart # Supabase connection
│ ├── 📄 functions.dart # helper functions
│── 📄 pubspec.yaml # Dependencies file
│── 📂 assets/ # (Optional) Store images/icons
✅ lib/ → Contains the main application logic and UI components.
✅ pubspec.yaml → Manages dependencies and configurations.
✅ assets/ (Optional) → Store images, icons, or other resources.
🔹 Ensure your project follows this structure to keep your files organized and manageable!
We’ll start by creating a simple Home Page layout.
Create a new file named home.dart and add the following code:
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Donation App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Welcome to the Donation App!',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
Text(
'Make a difference with your donations.',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
SizedBox(height: 40),
ElevatedButton(
onPressed: () {},
child: Text('Get Started'),
),
],
),
),
);
}
}Modify main.dart to use HomePage:
import 'package:flutter/material.dart';
import 'home.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Donation App',
theme: ThemeData(primarySwatch: Colors.blue),
home: HomePage(),
);
}
}✅ Now your Flutter app has a basic UI!
🔹 Next, we’ll structure our code to be ready for Supabase.
Use Zapprun’s built-in emulator.
Run the command:
flutter runIn this step, we will enhance the basic UI by displaying a list of donations and show a list of donation items with their names in a more structured way. The user will be able to select a donation item to view more details.Also, we will add a leaderboard view for top donors!
First, create a model class to represent donation items. Create a file called donation_item.dart:
class DonationItem {
final int id;
final String name;
final String description;
double amount;
DonationItem({
required this.id,
required this.name,
required this.description,
required this.amount,
});
// Convert a DonationItem object to a Map
Map<String, dynamic> toMap() {
return {
'id' : id,
'name': name,
'description': description,
'amount': amount,
};
}
// Create a DonationItem object from a Map
factory DonationItem.fromMap(Map<String, dynamic> map) {
return DonationItem(
id : map['id'] ?? -1,
name: map['name'] ?? '',
description: map['description'] ?? '',
amount: map['amount']?.toDouble() ?? 0.0,
);
}
}
Then, create a model class to represent the Donor. Create a file called donator.dart:
class Donator {
final int id;
final String name;
final double amountDonated;
final DateTime donationDate;
final int donationItemId;
Donator({
required this.id,
required this.name,
required this.amountDonated,
required this.donationDate,
required this.donationItemId
});
// Example: Convert Donator to a Map (e.g., for database storage)
Map<String, dynamic> toMap() {
return {
'id' : id,
'name': name,
'amount_donated': amountDonated,
'donation_date': donationDate.toIso8601String(),
'donation_item_id' : donationItemId
};
}
// Example: Create Donator from a Map (e.g., when retrieving from a database)
factory Donator.fromMap(Map<String, dynamic> map) {
return Donator(
id : map['id'] ?? -1,
name: map['name'] ?? '',
amountDonated: map['amount_donated'] ?? 0.0,
donationDate: DateTime.parse(map['donation_date']),
donationItemId: map['donation_item_id']
);
}
}
Next, create a new file donation_item_list.dart where we will create a list view to display the donations. Each item will be clickable, allowing the user to choose where they would like to donate.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_app/supabase_config.dart';
import 'donation_item.dart';
class DonationList extends StatefulWidget {
final List<DonationItem> donations;
const DonationList({super.key, required this.donations});
@override
State<DonationList> createState() => _DonationListState();
}
class _DonationListState extends State<DonationList> {
// Function to show the dialog when tapping on a donation item
Future<void> showCardDialog(BuildContext context, DonationItem donation) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25), // Rounded corners for the dialog with a smoother curve
),
backgroundColor: Colors.white, // White background to keep it clean
title: Container(
padding: EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blueAccent, Colors.purpleAccent], // Gradient background for the title
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.vertical(top: Radius.circular(25)), // Rounded top corners
),
child: Text(
donation.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white, // Title text color
),
),
),
content: SingleChildScrollView( // Allows scrolling for long content
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 15),
Text(
donation.description,
style: TextStyle(fontSize: 18, color: Colors.black87),
),
SizedBox(height: 20),
Text(
'Amount: \$${donation.amount.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
SizedBox(height: 30),
// Donation action button with improved styling
Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.green, // Button color
onPrimary: Colors.white, // Text color inside the button
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15), // Button padding
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 10, // Add shadow for depth
textStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
onPressed: () {
Navigator.of(context).pop(); // Close the current dialog
_showDonationInputDialog(context, donation); // Show input dialog
},
child: Text(
'Donate Now',
),
),
),
],
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
"Close",
style: TextStyle(
fontSize: 16,
color: Colors.blueAccent, // Close button color
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
void _showDonationInputDialog(BuildContext context, DonationItem donation) {
final TextEditingController nameController = TextEditingController();
final TextEditingController amountController = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: Text("Complete Donation"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: InputDecoration(labelText: "Your Name"),
),
TextField(
controller: amountController,
decoration: InputDecoration(labelText: "Amount"),
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}')),
],
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel"),
),
ElevatedButton(
onPressed: () {
final name = nameController.text;
final amount = amountController.text;
// Optionally, validate here
if (name.isEmpty || amount.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Please enter both name and amount.")),
);
return;
}
//addDonatorToSupabase(name: name,amountDonated: double.parse(amount),donationItemId: donation.id,donationDate: DateTime.now());
Navigator.pop(context); // Close dialog
},
child: Text("Submit"),
),
],
),
);
}
@override
Widget build(BuildContext context) {
// Get the width of the screen for responsiveness
double screenWidth = MediaQuery.of(context).size.width;
return SizedBox(
width: screenWidth,
height: 200,
child: widget.donations.isEmpty
// If donation list is empty, show a message
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
color: Colors.blueAccent,
size: 50,
),
SizedBox(height: 10),
Text(
"No Donations Available!",
style: TextStyle(
fontSize: 18,
color: Colors.black54,
),
),
SizedBox(height: 5),
Text(
"Come Back Later!",
style: TextStyle(
fontSize: 16,
color: Colors.black87,
),
),
],
),
)
// If donation list is not empty, show the donation items
: Row(
children: widget.donations.map((donation) {
return GestureDetector(
onTap: () => showCardDialog(context, donation), // Open dialog on tap
child: Card(
margin: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
child: Container(
width: screenWidth < 600 ? 250 : 350, // Adjust width for smaller screens
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.favorite_border,
color: Colors.blueAccent,
size: 40,
),
SizedBox(height: 10),
Text(
donation.name,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
SizedBox(height: 5),
Text(
donation.description,
style: TextStyle(fontSize: 16, color: Colors.black54),
overflow: TextOverflow.ellipsis, // Prevent overflow
),
SizedBox(height: 10),
Text(
'\$${donation.amount.toStringAsFixed(2)}',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.green),
),
],
),
),
),
);
}).toList(),
),
);
}
}Next, create a new file leaderboard.dart where we will create a list view to display each donor's contribution.
import 'package:flutter/material.dart';
import 'donator.dart';
class Leaderboard extends StatefulWidget {
final List<Donator> topDonors;
const Leaderboard({super.key,required this.topDonors});
@override
State<Leaderboard> createState() => _LeaderboardState();
}
class _LeaderboardState extends State<Leaderboard> {
// Helper method to get rank color gradients
List<Color> _rankColors(int rank) {
switch (rank) {
case 1:
return [Colors.amber, Colors.orange];
case 2:
return [Colors.grey, Colors.blueGrey];
case 3:
return [Colors.brown, Colors.orangeAccent];
default:
return [Colors.blueAccent, Colors.blue];
}
}
@override
Widget build(BuildContext context) {
return Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title Section
Text(
"Leaderboard",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
),
),
SizedBox(height: 10),
// Check if the list is empty
if (widget.topDonors.isEmpty)
Center(
child: Column(
children: [
Icon(
Icons.leaderboard,
color: Colors.blueAccent,
size: 50,
),
SizedBox(height: 10),
Text(
"No donors yet!",
style: TextStyle(
fontSize: 18,
color: Colors.black54,
),
),
SizedBox(height: 5),
Text(
"Be the first to make a difference!",
style: TextStyle(
fontSize: 16,
color: Colors.black87,
),
),
],
),
)
else
// Donors List
Column(
children: widget.topDonors.asMap().entries.map((entry) {
int rank = entry.key + 1; // Rank starts from 1
Donator donor = entry.value; // Using Donator model
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
// Rank Badge
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: _rankColors(rank),
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Center(
child: Text(
"$rank",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
SizedBox(width: 15),
// Donor Details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
donor.name,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
SizedBox(height: 5),
Text(
'Donated: \$${donor.amountDonated.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 16,
color: Colors.green,
),
),
],
),
),
// Icon for Celebration
Icon(
Icons.emoji_events,
color: _rankColors(rank)[0],
),
],
),
);
}).toList(),
),
],
),
),
);
}
}
Now, update your home.dart to display the DonationList & Leaderboard. The HomePage widget will use the DonationList widget to show a list of donation items & Leaderboard widget to to show each donor's total contribution ,sorted and ranked.
import 'package:flutter/material.dart';
import 'donation_item_list.dart';
import 'leaderboard.dart';
import 'donator.dart';
import 'donation_item.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Donator> donators=[];
List<Donator> topDonators=[];
List<DonationItem> donationItems=[];
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Donation App'),
backgroundColor: Colors.blueAccent,
elevation: 0,
),
backgroundColor: Colors.tealAccent.withOpacity(0.3),
body: SingleChildScrollView( // Enable vertical scrolling for the entire page
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Title section
Text(
'Welcome to the Donation App!',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
),
),
SizedBox(height: 10),
Text(
'Make a difference with your donations.',
style: TextStyle(fontSize: 18, color: Colors.black54),
),
SizedBox(height: 30),
// Donation List title section
Text(
'Browse Donations:',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
),
),
SizedBox(height: 20),
// Donation List
DonationList(donations: donationItems),
SizedBox(height: 15), // Add spacing before the leaderboard
Divider(),
SizedBox(height: 15),
// Leaderboard title section
Text(
'Top Donors Leaderboard:',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
),
),
SizedBox(height: 20),
// Leaderboard widget
Leaderboard(topDonors: topDonators),
],
),
),
),
);
}
}Use Zapprun’s built-in emulator.
Run the command:
flutter run🚀 Now you're ready to setup our Database!
Supabase is an open-source alternative to Firebase, providing a powerful backend with authentication, database, and real-time capabilities.
- Go to Supabase and sign up or log in.
- Click New Project and enter the project name, database password, and region.
- Click Create Project and wait for the setup to complete.
-
In your Supabase Dashboard, go to the Database section.
-
Click Tables and create a new table named
donatorswith the following columns:id | int8 (Primary Key, Auto Increment) name | Text (Required) amount_donated | float8 (Required) donation_date | timestamp (Default: now()) donation_item_id | int4 (Required,with a foreign key linked to Donation Item ID) -
On section Tables, create a new table named
donation_itemswith the following columns:id | int8 (Primary Key, Auto Increment) name | Text (Required) description | Text (Required) amount | float8 (Required) -
❗ Notice This is the code to build your own database in
Supabase, compatible with our app!-- Create the donation_items table CREATE TABLE donation_items ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, description TEXT, amount FLOAT8 NOT NULL ); -- Create the donators table CREATE TABLE donators ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, amount_donated FLOAT8 NOT NULL, donation_date TIMESTAMPTZ DEFAULT now(), donation_item_id INT REFERENCES donation_items(id) ON DELETE SET NULL ); -- Create a view for top donators CREATE VIEW top_donators AS SELECT name, SUM(amount_donated) AS total_donated FROM donators GROUP BY name ORDER BY total_donated DESC;
-
Navigate to Settings > API and copy your Project URL and Anon Key.
For production environments, do not hardcode your Supabase credentials (URL and Anon Key) directly in the source code. Exposing these credentials can lead to security vulnerabilities, such as unauthorized access to your database.
💭 Supabase URL: This is the unique endpoint for your Supabase project, used to interact with the database and services.
💭 Supabase Anon Key: A public API key that allows read and write access based on your project's authentication rules. It should be kept secure in production.
Instead of hardcoding, consider the following best practices:
-
Use Environment Variables Store your Supabase URL and Anon Key in environment variables:
export SUPABASE_URL="https://your-project.supabase.co" export SUPABASE_ANON_KEY="your-anon-key"
Then access them in your Dart/Flutter app using:
import 'dart:io'; final String supabaseUrl = Platform.environment['SUPABASE_URL'] ?? ''; final String supabaseAnonKey = Platform.environment['SUPABASE_ANON_KEY'] ?? '';
-
Use a Secrets Manager Services like AWS Secrets Manager, Google Secret Manager, or Firebase Remote Config can help store and manage sensitive credentials securely.
-
Restrict Supabase Policies Utilize Row-Level Security (RLS) to enforce access control at the database level.
-
Configure API Keys with Limited Permissions Avoid using the
service_rolekey in client-side applications, as it has full database access. -
Monitor API Usage Regularly check your Supabase dashboard for unauthorized requests and set up alerts for suspicious activities.
Navigate to pubspec.yaml & when you find dependencies section, update with this:
dependencies:
flutter:
sdk: flutter
supabase_flutter: ^1.2.0
realtime_client: ^1.4.0Inside supabase_config.dart, add:
import 'package:flutter_app/donation_item.dart';
import 'package:flutter_app/donator.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:realtime_client/realtime_client.dart';
// Initialize Supabase client using the instance.
final SupabaseClient supabase = Supabase.instance.client;
// Supabase URL and Anon Key (these should be kept safe and not hardcoded in production)
const String supabaseUrl = 'https://xglhodvtllegirsfhsep.supabase.co';
const String supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhnbGhvZHZ0bGxlZ2lyc2Zoc2VwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDE0MjQyNjgsImV4cCI6MjA1NzAwMDI2OH0.GiAfg_hGCLe2OtWpzKUeG2LUCUxe0LdivZlvBGJ2n98';
// Realtime Client for listening to changes in the database
final RealtimeClient socket = RealtimeClient(
'wss://xglhodvtllegirsfhsep.supabase.co/realtime/v1',
params: {'apikey': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhnbGhvZHZ0bGxlZ2lyc2Zoc2VwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDE0MjQyNjgsImV4cCI6MjA1NzAwMDI2OH0.GiAfg_hGCLe2OtWpzKUeG2LUCUxe0LdivZlvBGJ2n98'},
);
class RealtimeService {
RealtimeService();
// Fetch initial data for both donation_items and donators tables
Future<Map<String, List<Map<String, dynamic>>>> fetchInitialData() async {
try {
// Fetch donation items from Supabase
final donationItemsResponse = await supabase
.from('donation_items')
.select()
.order('id', ascending: true);
// Fetch donators from Supabase
final donatorsResponse = await supabase
.from('donators')
.select()
.order('id', ascending: true);
// Return both fetched lists of donationItems and donators
return {
'donationItems': List<Map<String, dynamic>>.from(donationItemsResponse),
'donators': List<Map<String, dynamic>>.from(donatorsResponse),
};
} catch (e) {
// If there's an error during the fetch, log the error and return empty lists
print('Error fetching initial data: $e');
return {'donationItems': [], 'donators': []};
}
}
// Set up real-time listeners for donation_items and donators
void setupRealtimeListeners({
required Function(Map<String, dynamic>) onNewDonationItem,
required Function(Map<String, dynamic>) onUpdateDonationItem,
required Function(Map<String, dynamic>) onDeleteDonationItem,
required Function(Map<String, dynamic>) onNewDonator,
required Function(Map<String, dynamic>) onUpdateDonator,
required Function(Map<String, dynamic>) onDeleteDonator,
}) {
final channel = socket.channel('realtime:public');
// Listen to INSERT events for donation_items
channel.on(
RealtimeListenTypes.postgresChanges,
ChannelFilter(event: 'INSERT', schema: 'public', table: 'donation_items'),
(payload, [ref]) => onNewDonationItem(payload['new']),
);
channel.on(
RealtimeListenTypes.postgresChanges,
ChannelFilter(
event: 'UPDATE',
schema: 'public',
table: 'donation_items',
),
(payload, [ref]) {
// Ensure 'payload' is of the correct type and contains the 'new' key
final updatedDonationItem = payload['new'];
if (updatedDonationItem != null) {
// Pass the updated data to the 'onUpdateDonationItem' function
onUpdateDonationItem(updatedDonationItem);
} else {
// Handle the case where 'new' is not available
print('Error: No updated data found in payload');
}
},
);
// Listen to DELETE events for donation_items
channel.on(
RealtimeListenTypes.postgresChanges,
ChannelFilter(event: 'DELETE', schema: 'public', table: 'donation_items'),
(payload, [ref]) => onDeleteDonationItem(payload['old']),
);
// Listen to INSERT events for donators
channel.on(
RealtimeListenTypes.postgresChanges,
ChannelFilter(event: 'INSERT', schema: 'public', table: 'donators'),
(payload, [ref]) => onNewDonator(payload['new']),
);
// Listen to UPDATE events for donators
channel.on(
RealtimeListenTypes.postgresChanges,
ChannelFilter(
event: 'UPDATE',
schema: 'public',
table: 'donators',
),
(payload, [ref]) {
// Ensure 'payload' is of the correct type and contains the 'new' key
final updatedDonator = payload['new'];
if (updatedDonator != null) {
// Pass the updated data to the 'onUpdateDonator' function
onUpdateDonator(updatedDonator);
} else {
// Handle the case where 'new' is not available
print('Error: No updated data found in payload');
}
},
);
// Listen to DELETE events for donators
channel.on(
RealtimeListenTypes.postgresChanges,
ChannelFilter(event: 'DELETE', schema: 'public', table: 'donators'),
(payload, [ref]) => onDeleteDonator(payload['old']),
);
// Connect and subscribe to the channel
socket.connect();
channel.subscribe((state, [_]) => print('SUBSCRIBED with state: $state'));
}
// Disconnect the socket
void disconnect() {
socket.disconnect();
}
// Example function for handling donation item amount updates
void updateDonationItemAmount({required DonationItem updatedDonationItem}) async {
final supabaseClient = Supabase.instance.client;
// Update the donation item in the database
final updateResponse = await supabaseClient.from('donation_items').update({
'amount': updatedDonationItem.amount,
}).eq('id', updatedDonationItem.id);
// Optionally handle the response, check for errors, etc.
}
}
// Function to add a new donator to Supabase
Future<void> addDonatorToSupabase({
required String name,
required double amountDonated,
required DateTime donationDate,
required int donationItemId,
}) async {
final client = Supabase.instance.client;
try {
// Insert the new donator record into the donators table
final response = await client
.from('donators') // Specify the table name
.insert([
{
'name': name,
'amount_donated': amountDonated,
'donation_date': donationDate.toIso8601String(),
'donation_item_id': donationItemId,
}
]);
// Optionally handle the response, check for errors, etc.
} catch (e) {
// Handle any errors during the insertion process
print('Error adding donator: $e');
}
}
Inside functions.dart, add:
import 'package:flutter_app/donation_item.dart';
import 'package:flutter_app/donator.dart';
import 'package:flutter_app/supabase_config.dart';
bool calculateDonationItemTotals({
required List<Donator> donators,
required List<DonationItem> donationItems,
}) {
if (donators.isEmpty || donationItems.isEmpty) return false;
// Reset all amounts raised in donationItems
for (var donationItem in donationItems) {
donationItem.amount = 0.0;
}
// Calculate totals
for (var donator in donators) {
for (var donationItem in donationItems) {
if (donator.donationItemId == donationItem.id) {
donationItem.amount += donator.amountDonated;
}
}
}
for (var donationItem in donationItems) {
RealtimeService().updateDonationItemAmount(updatedDonationItem: donationItem);
}
return true; // Indicate the operation was successful
}
List<Donator> groupAndSortDonators(List<Donator> donators) {
// Group donators by name
final Map<String, Donator> groupedDonators = {};
for (var donator in donators) {
// Ensure the donator object is not null
if (donator == null || donator.name.isEmpty) continue;
if (groupedDonators.containsKey(donator.name)) {
// If the name exists, sum up the donations
final existingDonator = groupedDonators[donator.name]!;
groupedDonators[donator.name] = Donator(
id: existingDonator.id,
name: existingDonator.name,
amountDonated: existingDonator.amountDonated + donator.amountDonated,
donationDate: existingDonator.donationDate,
donationItemId: existingDonator.donationItemId,
);
} else {
// Add the donator to the map
groupedDonators[donator.name] = donator;
}
}
// Convert the map values to a list and sort by amountDonated in descending order
final List<Donator> groupedAndSortedDonators = groupedDonators.values.toList();
groupedAndSortedDonators.sort((a, b) => b.amountDonated.compareTo(a.amountDonated));
return groupedAndSortedDonators;
}Next, update file donation_item_list.dart where we will create a list view to display the donations. Each item will be clickable, allowing the user to choose where they would like to donate.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_app/supabase_config.dart';
import 'donation_item.dart';
class DonationList extends StatefulWidget {
final List<DonationItem> donations;
const DonationList({super.key, required this.donations});
@override
State<DonationList> createState() => _DonationListState();
}
class _DonationListState extends State<DonationList> {
// Function to show the dialog when tapping on a donation item
Future<void> showCardDialog(BuildContext context, DonationItem donation) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25), // Rounded corners for the dialog with a smoother curve
),
backgroundColor: Colors.white, // White background to keep it clean
title: Container(
padding: EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blueAccent, Colors.purpleAccent], // Gradient background for the title
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.vertical(top: Radius.circular(25)), // Rounded top corners
),
child: Text(
donation.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white, // Title text color
),
),
),
content: SingleChildScrollView( // Allows scrolling for long content
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 15),
Text(
donation.description,
style: TextStyle(fontSize: 18, color: Colors.black87),
),
SizedBox(height: 20),
Text(
'Amount: \$${donation.amount.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
SizedBox(height: 30),
// Donation action button with improved styling
Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.green, // Button color
onPrimary: Colors.white, // Text color inside the button
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15), // Button padding
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 10, // Add shadow for depth
textStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
onPressed: () {
Navigator.of(context).pop(); // Close the current dialog
_showDonationInputDialog(context, donation); // Show input dialog
},
child: Text(
'Donate Now',
),
),
),
],
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
"Close",
style: TextStyle(
fontSize: 16,
color: Colors.blueAccent, // Close button color
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
void _showDonationInputDialog(BuildContext context, DonationItem donation) {
final TextEditingController nameController = TextEditingController();
final TextEditingController amountController = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: Text("Complete Donation"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: InputDecoration(labelText: "Your Name"),
),
TextField(
controller: amountController,
decoration: InputDecoration(labelText: "Amount"),
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}')),
],
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel"),
),
ElevatedButton(
onPressed: () {
final name = nameController.text;
final amount = amountController.text;
// Optionally, validate here
if (name.isEmpty || amount.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Please enter both name and amount.")),
);
return;
}
addDonatorToSupabase(name: name,amountDonated: double.parse(amount),donationItemId: donation.id,donationDate: DateTime.now());
Navigator.pop(context); // Close dialog
},
child: Text("Submit"),
),
],
),
);
}
@override
Widget build(BuildContext context) {
// Get the width of the screen for responsiveness
double screenWidth = MediaQuery.of(context).size.width;
return SizedBox(
width: screenWidth,
height: 200,
child: widget.donations.isEmpty
// If donation list is empty, show a message
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
color: Colors.blueAccent,
size: 50,
),
SizedBox(height: 10),
Text(
"No Donations Available!",
style: TextStyle(
fontSize: 18,
color: Colors.black54,
),
),
SizedBox(height: 5),
Text(
"Come Back Later!",
style: TextStyle(
fontSize: 16,
color: Colors.black87,
),
),
],
),
)
// If donation list is not empty, show the donation items
: Row(
children: widget.donations.map((donation) {
return GestureDetector(
onTap: () => showCardDialog(context, donation), // Open dialog on tap
child: Card(
margin: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
child: Container(
width: screenWidth < 600 ? 250 : 350, // Adjust width for smaller screens
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.favorite_border,
color: Colors.blueAccent,
size: 40,
),
SizedBox(height: 10),
Text(
donation.name,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
SizedBox(height: 5),
Text(
donation.description,
style: TextStyle(fontSize: 16, color: Colors.black54),
overflow: TextOverflow.ellipsis, // Prevent overflow
),
SizedBox(height: 10),
Text(
'\$${donation.amount.toStringAsFixed(2)}',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.green),
),
],
),
),
),
);
}).toList(),
),
);
}
}Now, let's establish the initialization of Supabase inside main.dart before running the app.
import 'package:flutter/material.dart';
import 'home.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:flutter_app/supabase_config.dart';
void main() async{
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: supabaseUrl,
anonKey: supabaseAnonKey,
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Donation App',
theme: ThemeData(primarySwatch: Colors.blue),
home: HomePage(),
);
}
}Last step before running the app, we have to update your home.dart,by adding loadInitialData() & setupListeners() functions.
Update home.dart with the following code:
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'supabase_config.dart';
import 'package:flutter_app/donation_item_list.dart';
import 'package:flutter_app/leaderboard.dart';
import 'package:flutter_app/donator.dart';
import 'package:flutter_app/donation_item.dart';
import 'package:flutter_app/functions.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late RealtimeService realtimeService=RealtimeService();
List<Donator> donators = [];
List<Donator> topDonators=[];
List<DonationItem> donationItems=[];
@override
void initState() {
super.initState();
loadInitialData();
setupListeners();
}
@override
void dispose() {
socket.disconnect();
super.dispose();
}
Future<void> loadInitialData() async {
final initialData = await realtimeService.fetchInitialData();
donationItems = List<Map<String, dynamic>>.from(initialData['donationItems'] ?? [])
.map((item) => DonationItem.fromMap(item))
.toList();
donators = List<Map<String, dynamic>>.from(initialData['donators'] ?? [])
.map((item) => Donator.fromMap(item))
.toList();
topDonators = groupAndSortDonators(donators);
setState(() {
});
}
void setupListeners() {
realtimeService.setupRealtimeListeners(
onNewDonationItem: (newItem) {
setState(() {
donationItems.add(DonationItem.fromMap(newItem));
});
},
onUpdateDonationItem: (updatedItem) {
setState(() {
int index = donationItems.indexWhere((element) => element.id == DonationItem.fromMap(updatedItem).id,);
donationItems[index] = DonationItem.fromMap(updatedItem);
});
},
onDeleteDonationItem: (deletedItem) {
setState(() {
donationItems.removeWhere((item) => item.name == DonationItem.fromMap(deletedItem).name);
});
},
onNewDonator: (newDonator) {
setState(() {
donators.add(Donator.fromMap(newDonator));
topDonators = groupAndSortDonators(donators);
calculateDonationItemTotals(donators: donators,donationItems: donationItems);
});
},
onUpdateDonator: (updatedDonor) {
setState(() {
int index = donators.indexWhere((element) => element.id == Donator.fromMap(updatedDonor).id,);
donators[index] = Donator.fromMap(updatedDonor);
topDonators = groupAndSortDonators(donators);
calculateDonationItemTotals(donators: donators,donationItems: donationItems);
});
},
onDeleteDonator: (deletedDonator) {
setState(() {
donators.removeWhere((item) => item.name == Donator.fromMap(deletedDonator).name);
});
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Donation App'),
backgroundColor: Colors.blueAccent,
elevation: 0,
),
backgroundColor: Colors.tealAccent.withOpacity(0.3),
body: SingleChildScrollView( // Enable vertical scrolling for the entire page
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Title section
Text(
'Welcome to the Donation App!',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
),
),
SizedBox(height: 10),
Text(
'Make a difference with your donations.',
style: TextStyle(fontSize: 18, color: Colors.black54),
),
SizedBox(height: 30),
// Donation List title section
Text(
'Browse Donations:',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
),
),
SizedBox(height: 20),
// Donation List
DonationList(donations: donationItems),
SizedBox(height: 15), // Add spacing before the leaderboard
Divider(),
SizedBox(height: 15),
// Leaderboard title section
Text(
'Top Donors Leaderboard:',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
),
),
SizedBox(height: 20),
// Leaderboard widget
Leaderboard(topDonors: topDonators),
],
),
),
),
);
}
}You've successfully built your Donation App with Supabase and Flutter! 🚀
Feel free to explore and play with the app.
You can start:
- Donating 💰
- Viewing real-time data 🔄
The setup is complete, and now the fun part begins! 🎉
- Enjoy building, testing, and expanding your app!
- The possibilities are endless! 🌟
🎉 Congratulations! You've successfully built a Donation App using Flutter and Supabase with the help of Zapprun. This marks an exciting milestone, but your journey doesn't have to stop here!
- 🔹 Enhance Your App: Implement features like user authentication, analytics, or recurring donations.
- 🎨 Improve UI/UX: Experiment with animations, dark mode, or custom themes for a better user experience.
- ⚡ Optimize Performance: Utilize caching, lazy loading, and API optimizations to make your app faster.
💡 Keep refining your app, exploring new tools, and pushing the boundaries of what you can create!
🚀 Happy Coding & Keep Innovating! ✨
Panther Racing AUTh is a dynamic team of Mechanical Engineering students from the Aristotle University of Thessaloniki (AUTh). Since 2017, the team has been innovating and excelling in designing and building high-performance Moto3-type racing motorcycles, proudly representing Greece on international stages.
🌐 Explore their journey and achievements:
At Panther Racing AUTh, we strive to:
- 🛠️ Innovate Motorsport Engineering: Redefining what student engineers can achieve.
- 🚀 Inspire Future Engineers: Offering hands-on experience to bridge theory and real-world application.
- 🌍 Compete Globally: Representing Greece in the prestigious MotoStudent competition in Spain.
- Panthera Pardus: Our most advanced racing motorcycle yet, merging cutting-edge technology with performance.
- Black Panther: A giant leap forward in engineering and design.
- Pink Panther: The pioneering project that started it all.
🏁 MotoStudent Success
Representing AUTh and Greece at an international competition where innovation meets speed.
⚙️ Dynamic Velocity Stack System
A breakthrough in racing performance, adjusting dynamically for race conditions.
📱 Velocity-Handler App
A Flutter-powered mobile app for real-time RPM monitoring and data analysis.
Stay connected and follow our exciting journey:
By supporting Panther Racing AUTh, you join a community that values passion, innovation, and excellence. Let’s create something extraordinary together!
👉 Join the movement today!
Explore, engage, and be part of Panther Racing AUTh’s mission to redefine motorsport engineering. Together, we can achieve greatness. 🚀
- Type: Widget
- Package:
flutter/material.dart - Description: The
Scaffoldwidget is a high-level layout widget that provides the basic structure for your app's visual interface. It includes app bars, floating action buttons, drawers, and more. - Key Properties:
appBar: A widget to display the app's header.body: The primary content of the screen.floatingActionButton: A button that "floats" above the body content.
- Usage in App: Used to structure main pages, including the home screen and donation list page.
- Type: Widget
- Package:
flutter/material.dart - Description: The
AppBarwidget displays a horizontal bar at the top of the screen. It is typically used to show titles, navigation buttons, and actions. - Key Properties:
title: A widget, usually aText, to display the app's title.actions: A list of widgets, such as buttons, displayed on the right side of the bar.
- Usage in App: Used to display the app's title and action buttons like navigation or settings.
- Type: Widget
- Package:
flutter/material.dart - Description: A
ListViewis a scrollable list of widgets. It is ideal for displaying data in rows or items dynamically. - Key Properties:
children: A list of widgets to display.builder: A function that creates widgets lazily based on the index.
- Usage in App: Used to display lists of donation items and donators.
- Type: Widget
- Package:
flutter/material.dart - Description: The
Textwidget displays a string of text with a single style. It is used to show static or dynamic content. - Key Properties:
style: ATextStyleobject that defines the font size, color, weight, etc.textAlign: Aligns the text horizontally within its parent.
- Usage in App: Used to display labels, titles, and descriptions throughout the UI.
- Type: Widget
- Package:
flutter/material.dart - Description: The
ElevatedButtonis a material design button that lifts slightly when pressed. - Key Properties:
onPressed: A callback function triggered when the button is pressed.child: The widget inside the button, usually aTextorIcon.
- Usage in App: Used to handle user actions like submitting donations or navigating between pages.
- Type: Widget
- Package:
flutter/material.dart - Description: The
Iconwidget is used to display material design icons. - Key Properties:
icon: An instance of anIconDataobject, such asIcons.add.color: The color of the icon.size: The size of the icon in logical pixels.
- Usage in App: Used in action buttons and headers to visually indicate actions.
- Type: Widget
- Package:
flutter/material.dart - Description: A
Columnwidget arranges its children vertically. - Key Properties:
children: A list of widgets to display vertically.mainAxisAlignment: Defines how the children are aligned along the main axis.crossAxisAlignment: Defines how the children are aligned along the cross axis.
- Usage in App: Used to create vertically stacked layouts, such as form inputs and details views.
- Type: Widget
- Package:
flutter/material.dart - Description: A
Rowwidget arranges its children horizontally. - Key Properties:
children: A list of widgets to display horizontally.mainAxisAlignment: Defines how the children are aligned along the main axis.crossAxisAlignment: Defines how the children are aligned along the cross axis.
- Usage in App: Used to create horizontal layouts, such as displaying a label and its value side by side.
- Type: Widget
- Package:
flutter/material.dart - Description: The
FloatingActionButtonis a circular button that "floats" above the UI and is commonly used for primary actions. - Key Properties:
onPressed: A callback function triggered when the button is pressed.child: The widget inside the button, usually anIcon.
- Usage in App: Used for adding new donation items or donators.
- Type: Widget
- Package:
flutter/material.dart - Description: A
Snackbaris a temporary message displayed at the bottom of the screen. - Key Properties:
content: A widget, usuallyText, that displays the message.action: An optional action button to provide a quick response to the message.
- Usage in App: Used to show confirmation messages like "Donation added successfully."
- Type: Widget
- Package:
flutter/material.dart - Description: The
Containerwidget is a versatile container for styling and positioning child widgets. - Key Properties:
color: The background color of the container.padding: Adds space inside the container around its child.margin: Adds space outside the container.decoration: Allows for adding borders, gradients, and rounded corners.
- Usage in App: Used for creating visually distinct sections or spacing between elements.
- Type: Widget
- Package:
flutter/material.dart - Description: The
Cardwidget provides a rectangular container with rounded corners and elevation, used for grouping related content. - Key Properties:
elevation: Adds shadow to the card.child: The widget inside the card, usually aColumnorListTile.
- Usage in App: Used to display information about donation items or donors in a visually appealing format.
- Type: Widget
- Package:
flutter/material.dart - Description: The
GestureDetectorwidget detects user interactions, such as taps, swipes, and drags. - Key Properties:
onTap: A callback triggered when the widget is tapped.onLongPress: A callback triggered on a long press.
- Usage in App: Used for detecting user interactions, such as tapping on a list item to view details.
- Type: Widget
- Package:
flutter/material.dart - Description: The
Stackwidget allows for overlapping its children, which can be positioned using thePositionedwidget. - Key Properties:
alignment: Aligns the stack's children.children: A list of widgets to display.
- Usage in App: Used to layer widgets, such as displaying a floating badge over an image.
- Type: Widget
- Package:
flutter/material.dart - Description: The
Positionedwidget is used inside aStackto control the position of a child relative to the stack's edges. - Key Properties:
top,left,right,bottom: Position offsets relative to the stack.
- Usage in App: Used to position badges or labels over donation images.
- Type: Widget
- Package:
flutter/material.dart - Description: The
GridViewwidget displays items in a scrollable, grid-based layout. - Key Properties:
gridDelegate: Defines the grid structure, such as the number of columns.children: A list of widgets to display in the grid.
- Usage in App: Could be used to display donation items in a visually appealing grid format.
- Type: Widget
- Package:
flutter/material.dart - Description: The
Formwidget groups input fields together and manages their validation and submission. - Key Properties:
key: AGlobalKeyused to identify and validate the form.child: The widget tree containing the form fields.
- Usage in App: Used to create forms for adding new donation items or donor details.
- Type: Widget
- Package:
flutter/material.dart - Description: The
TextFormFieldwidget is a form-ready text input field with validation support. - Key Properties:
controller: ATextEditingControllerto retrieve the input value.decoration: Defines the input field's appearance, such as a label or hint.validator: A function for input validation.
- Usage in App: Used for user input, such as entering donor names or donation amounts.
- Type: Widget
- Package:
flutter/material.dart - Description: The
IconButtonwidget displays an interactive icon that triggers a function when tapped. - Key Properties:
icon: AnIconwidget to display.onPressed: A callback triggered when the button is tapped.
- Usage in App: Used for quick actions like deleting or editing donation items.
- Type: Widget
- Package:
flutter/material.dart - Description: The
DropdownButtonwidget allows users to select an item from a dropdown menu. - Key Properties:
items: A list ofDropdownMenuItemwidgets to display.value: The currently selected item.onChanged: A callback triggered when the user selects an item.
- Usage in App: Used to select predefined options, such as donation categories.
- Type: Widget
- Package:
flutter/material.dart - Description: The
Checkboxwidget allows users to toggle between two states: checked and unchecked. - Key Properties:
value: The current state of the checkbox.onChanged: A callback triggered when the checkbox state changes.
- Usage in App: Used for toggling options or filtering data.
- Type: Widget
- Package:
flutter/material.dart - Description: The
ProgressIndicatorwidget displays a visual indicator for ongoing tasks, such as loading data. - Variants:
CircularProgressIndicator: Displays a circular loading spinner.LinearProgressIndicator: Displays a linear loading bar.
- Usage in App: Used during API calls to indicate loading.
Refer to the "Functions Used" section above for detailed information on backend-related functions.
- Type: Class
- Package:
supabase_flutter - Description:
SupabaseClientis the main class used to interact with the Supabase service. It allows you to perform CRUD operations on your Supabase database, authenticate users, and manage storage and real-time capabilities. In this app, theSupabaseClientis used to fetch donation items and donators' data from the Supabase database.
- Type: Class
- Package:
realtime_client - Description:
RealtimeClientis used to establish real-time communication with a Supabase database. This allows the app to listen for changes such as inserts, updates, and deletes in real-time. TheRealtimeClientis responsible for maintaining a websocket connection to the Supabase instance, which allows you to receive updates whenever data changes in the database.
- Type: Class
- Package:
realtime_client - Description: A
Channelrepresents a subscription to a specific set of events in the Supabase real-time system. It allows you to listen for specific events (e.g., INSERT, UPDATE, DELETE) on a given table in the database. Channels are used in conjunction with theRealtimeClientto subscribe to specific real-time updates for tables likedonation_itemsanddonators.
- Type: Class
- Package:
realtime_client - Description:
ChannelFilteris used to filter specific events for a particular table in the Supabase database. It allows you to specify which type of operation (INSERT, UPDATE, DELETE) and which schema and table the real-time listener should be applied to. This is useful to narrow down real-time notifications to relevant changes in the app.
- Type: Enum
- Package:
realtime_client - Description:
RealtimeListenTypesdefines the types of real-time events that can occur in Supabase. It includes events likepostgresChangeswhich triggers when there are changes to a table in the database, such as INSERT, UPDATE, or DELETE.
- Type: Function
- Parameters: None
- Return Type:
Future<Map<String, List<Map<String, dynamic>>>> - Description: This function fetches the initial data for the
donation_itemsanddonatorstables from Supabase. It makes use of theSupabaseClientto query the database and retrieve the data, which is then returned in a map format. If an error occurs, it catches the exception and returns empty lists.
- Type: Function
- Parameters:
onNewDonationItem: A callback function that is triggered when a new donation item is inserted into the database.onUpdateDonationItem: A callback function that is triggered when a donation item is updated in the database.onDeleteDonationItem: A callback function that is triggered when a donation item is deleted from the database.onNewDonator: A callback function that is triggered when a new donator is inserted into the database.onUpdateDonator: A callback function that is triggered when a donator's information is updated in the database.onDeleteDonator: A callback function that is triggered when a donator is deleted from the database.
- Return Type:
void - Description: This function sets up real-time listeners for both
donation_itemsanddonatorstables. It subscribes to events likeINSERT,UPDATE, andDELETEfor each table. Whenever a change is detected, it invokes the corresponding callback functions. These listeners keep the app up-to-date with changes in real time.
- Type: Function
- Parameters: None
- Return Type:
void - Description: This function disconnects the real-time socket connection. It is typically called when the app no longer needs to listen to real-time changes, such as when the user exits the app or navigates away from the page that requires real-time data.
- Type: Function
- Parameters:
updatedDonationItem: An object of typeDonationItemthat contains the updated donation item data.
- Return Type:
Future<void> - Description: This function is used to update the
amountof a specific donation item in thedonation_itemstable. It takes the updated data and sends a request to the Supabase database to update the corresponding row.
- Type: Function
- Parameters:
name: The name of the donator.amountDonated: The amount donated by the donator.donationDate: The date the donation was made.donationItemId: The ID of the donation item associated with the donation.
- Return Type:
Future<void> - Description: This function is used to insert a new donator's information into the
donatorstable in the Supabase database. It takes the donator's details as parameters, formats them as a map, and inserts the record into the database.
- Description: This table holds information about the items available for donation in the app. It includes the following columns:
id: The unique identifier for each donation item (Primary Key, Auto Increment).name: The name of the donation item (e.g., "Food Package", "Clothes").description: A brief description of the donation item.amount: The monetary value of the donation item.
- Description: This table stores information about the donators, including the donation amount and associated donation item. It includes the following columns:
id: The unique identifier for each donator (Primary Key, Auto Increment).name: The name of the donator.amount_donated: The total amount donated by the donator.donation_date: The date when the donation was made.donation_item_id: A foreign key that references the ID of the donation item the donator contributed to.
This app leverages Flutter for the frontend and Supabase for the backend. With features such as real-time updates, the app enables users to see live changes when donation items or donators are added, updated, or deleted. The functions, such as fetchInitialData, setupRealtimeListeners, and addDonatorToSupabase, interact with the Supabase database to handle CRUD operations and maintain real-time sync.
The app structure is designed to keep the user experience seamless and up-to-date, providing a real-time dashboard for users to track donations and items.
This Wikipedia-style guide provides a structured overview of the widgets and functions used in the app, ensuring you have a clear understanding of each component.