A Flutter-based Android application designed to facilitate medication donation and distribution in Philippine healthcare centers. The app connects donors with patients in need, ensuring safe and transparent medicine redistribution.
- AkbayMed
- Table of Contents
- Features
- Tech Stack
- Installation
- Project Structure
- API Integration
- Database Schema
- UI/UX Design
- Development Setup
- Contributing
Secure and user-friendly authentication system for both donors and patients.
- Email and password-based authentication
- User registration with role selection
- Secure session management
- Profile management with avatar upload
Streamlined process for donors to contribute medications to those in need.
- Donation submission with medication details
- Integration with openFDA API for medication verification
- Expiration date tracking
- Donation history and status tracking
Easy-to-use interface for patients to request needed medications.
- Browse available medications
- Submit medication requests
- Track request status
- View request history
Personalized user experience with comprehensive profile management.
- View and edit personal information
- Track donation/request history
- Update profile picture
- Manage account settings
Centralized hub for all user activities and quick access to features.
- Personalized welcome screen
- Activity tracking
- Quick access to main features
- Recent transactions overview
- Framework: Flutter 3.29.3
- Language: Dart 3.7.2
- UI Components: Material 3
- State Management: Flutter's built-in StatefulWidget
- Navigation: Flutter Navigator 2.0
- Authentication: Supabase Auth
- Database: Supabase PostgreSQL
- Storage: Supabase Storage
- API Integration:
- openFDA API for medication information
- RESTful API architecture
dependencies:
flutter:
sdk: flutter
supabase_flutter: ^2.9.0
flutter_dotenv: ^5.2.1
logger: ^2.5.0
image_picker: ^1.0.4
path: ^1.8.3
permission_handler: ^11.0.0
http: ^1.4.0
intl: ^0.19.0
-
Prerequisites
- Flutter SDK 3.x
- Android Studio / VS Code
- Android SDK (API level 26+)
- Git
-
Setup Steps
# Clone the repository git clone https://github.com/lucifron28/AkbayMed_User.git cd AkbayMed_User # Install dependencies flutter pub get # Create .env file cp .env.example .env
-
Environment Configuration Create a
.env
file in the root directory with:SUPABASE_URL=your_supabase_url SUPABASE_ANON_KEY=your_supabase_anon_key
-
Run the App
flutter run
lib/
├── main.dart # Application entry point
├── app.dart # Main app configuration
└── screens/ # UI screens
├── home_screen.dart
├── login_screen.dart
├── registration_screen.dart
├── donation_screen.dart
├── request_screen.dart
└── profile_screen.dart
- Endpoint:
https://api.fda.gov/drug/label.json
- Used for medication verification and information
- Implements rate limiting and error handling
- Authentication
- Real-time database
- File storage
- Row Level Security (RLS) policies
CREATE TABLE users (
id UUID PRIMARY KEY REFERENCES auth.users(id),
name TEXT NOT NULL,
email TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
avatar_url TEXT,
is_verified BOOLEAN NOT NULL DEFAULT TRUE,
donation_count BIGINT NOT NULL DEFAULT 0
);
CREATE TABLE medications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
description TEXT,
category TEXT NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE TABLE donations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
donor_id UUID NOT NULL REFERENCES users(id),
medication_id UUID NOT NULL REFERENCES medications(id),
quantity INTEGER NOT NULL,
expiration_date DATE NOT NULL,
status VARCHAR NOT NULL DEFAULT 'approved',
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE TABLE requests (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
patient_id UUID NOT NULL REFERENCES users(id),
medication_id UUID NOT NULL REFERENCES medications(id),
quantity INTEGER NOT NULL,
status VARCHAR NOT NULL DEFAULT 'approved',
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE TABLE inventory (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
medication_id UUID REFERENCES medications(id),
quantity INTEGER NOT NULL DEFAULT 0,
expiration_date DATE,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW()
);
CREATE TABLE appointments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id),
donation_id UUID REFERENCES donations(id),
request_id UUID REFERENCES requests(id),
appointment_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
type VARCHAR NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW()
);
- Users (1) → (N) Donations: One user can make multiple donations
- Users (1) → (N) Requests: One user can make multiple requests
- Users (1) → (N) Appointments: One user can have multiple appointments
- Medications (1) → (N) Donations: One medication can be donated multiple times
- Medications (1) → (N) Requests: One medication can be requested multiple times
- Medications (1) → (1) Inventory: One medication has one inventory record
- Donations (1) → (N) Appointments: One donation can have multiple appointments
- Requests (1) → (N) Appointments: One request can have multiple appointments
- UUID primary keys for all tables
- Automatic timestamp management for created_at and updated_at
- Foreign key constraints for referential integrity
- Default values for status fields
- Proper indexing on frequently queried columns
- Timestamp with time zone for user-related timestamps
- Timestamp without time zone for business logic timestamps
CREATE OR REPLACE FUNCTION increment_donation_count()
RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'INSERT' AND NEW.status = 'approved') OR
(TG_OP = 'UPDATE' AND NEW.status = 'approved' AND OLD.status != 'approved') THEN
UPDATE public.users
SET donation_count = donation_count + 1
WHERE id = NEW.donor_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Purpose: Automatically tracks and updates the donation count for users
- Triggers when a donation is approved
- Updates the user's donation_count in the users table
- Handles both new donations and status changes
CREATE OR REPLACE FUNCTION create_donation_appointment()
RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'INSERT' AND NEW.status = 'approved')
OR (TG_OP = 'UPDATE' AND NEW.status = 'approved' AND OLD.status != 'approved') THEN
IF NOT EXISTS (
SELECT 1 FROM appointments
WHERE donation_id = NEW.id
) THEN
INSERT INTO appointments (
user_id,
donation_id,
appointment_date,
type,
created_at
)
VALUES (
NEW.donor_id,
NEW.id,
CURRENT_TIMESTAMP + INTERVAL '3 days',
'dropoff',
NOW()
);
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Purpose: Automatically creates dropoff appointments for approved donations
- Creates appointments 3 days after donation approval
- Prevents duplicate appointments
- Links appointments to both users and donations
CREATE OR REPLACE FUNCTION create_request_appointment()
RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'INSERT' AND NEW.status = 'approved')
OR (TG_OP = 'UPDATE' AND NEW.status = 'approved' AND OLD.status != 'approved') THEN
IF NOT EXISTS (
SELECT 1 FROM appointments
WHERE request_id = NEW.id
) THEN
INSERT INTO appointments (
user_id,
request_id,
appointment_date,
type,
created_at
)
VALUES (
NEW.patient_id,
NEW.id,
CURRENT_TIMESTAMP + INTERVAL '3 days',
'pickup',
NOW()
);
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Purpose: Automatically creates pickup appointments for approved requests
- Creates appointments 3 days after request approval
- Prevents duplicate appointments
- Links appointments to both users and requests
CREATE OR REPLACE FUNCTION update_inventory_from_request()
RETURNS TRIGGER AS $$
DECLARE
total_available INTEGER;
remaining_request INTEGER := NEW.quantity;
rec RECORD;
BEGIN
IF (TG_OP = 'INSERT' AND NEW.status = 'approved' AND NEW.medication_id IS NOT NULL)
OR (TG_OP = 'UPDATE' AND NEW.status = 'approved' AND OLD.status != 'approved' AND NEW.medication_id IS NOT NULL) THEN
SELECT COALESCE(SUM(quantity), 0) INTO total_available
FROM inventory
WHERE medication_id = NEW.medication_id
AND (expiration_date IS NULL OR expiration_date >= CURRENT_DATE);
IF total_available < NEW.quantity THEN
RAISE EXCEPTION 'Insufficient inventory for medication_id %: requested %, available %',
NEW.medication_id, NEW.quantity, total_available;
END IF;
FOR rec IN (
SELECT id, quantity
FROM inventory
WHERE medication_id = NEW.medication_id
AND (expiration_date IS NULL OR expiration_date >= CURRENT_DATE)
AND quantity > 0
ORDER BY COALESCE(expiration_date, '9999-12-31'::DATE) ASC
) LOOP
IF remaining_request <= 0 THEN
EXIT;
END IF;
DECLARE
deduct_amount INTEGER := LEAST(remaining_request, rec.quantity);
BEGIN
UPDATE inventory
SET quantity = quantity - deduct_amount,
updated_at = NOW()
WHERE id = rec.id;
remaining_request := remaining_request - deduct_amount;
END;
END LOOP;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Purpose: Manages inventory levels when requests are approved
- Validates available inventory before processing
- Uses FIFO (First In, First Out) method for inventory management
- Considers expiration dates when allocating inventory
- Updates inventory quantities automatically
- Prevents over-allocation of medications
CREATE OR REPLACE FUNCTION update_inventory_from_donation()
RETURNS TRIGGER AS $$
DECLARE
remaining_quantity INTEGER := NEW.quantity;
rec RECORD;
BEGIN
IF (TG_OP = 'INSERT' AND NEW.status = 'approved' AND NEW.medication_id IS NOT NULL)
OR (TG_OP = 'UPDATE' AND NEW.status = 'approved' AND OLD.status != 'approved' AND NEW.medication_id IS NOT NULL) THEN
INSERT INTO inventory (
medication_id,
quantity,
expiration_date,
source,
donation_id,
created_at,
updated_at
)
VALUES (
NEW.medication_id,
NEW.quantity,
NEW.expiration_date,
'donation',
NEW.id,
NOW(),
NOW()
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Purpose: Manages inventory levels when donations are approved
- Creates new inventory entries for approved donations
- Records medication quantity and expiration date
- Tracks donation source and reference
- Maintains inventory tracking
- Updates timestamps for tracking
-- Enable RLS on all tables
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE medications ENABLE ROW LEVEL SECURITY;
ALTER TABLE donations ENABLE ROW LEVEL SECURITY;
ALTER TABLE requests ENABLE ROW LEVEL SECURITY;
ALTER TABLE inventory ENABLE ROW LEVEL SECURITY;
ALTER TABLE appointments ENABLE ROW LEVEL SECURITY;
-- Users table policies
CREATE POLICY "Users can view their own profile"
ON users FOR SELECT
USING (auth.uid() = id);
CREATE POLICY "Users can update their own profile"
ON users FOR UPDATE
USING (auth.uid() = id);
-- Medications table policies
CREATE POLICY "Anyone can view medications"
ON medications FOR SELECT
USING (true);
CREATE POLICY "Only admins can modify medications"
ON medications FOR ALL
USING (auth.uid() IN (SELECT id FROM users WHERE role = 'admin'));
-- Donations table policies
CREATE POLICY "Users can view their own donations"
ON donations FOR SELECT
USING (auth.uid() = donor_id);
CREATE POLICY "Users can create donations"
ON donations FOR INSERT
WITH CHECK (auth.uid() = donor_id);
CREATE POLICY "Users can update their own donations"
ON donations FOR UPDATE
USING (auth.uid() = donor_id);
-- Requests table policies
CREATE POLICY "Users can view their own requests"
ON requests FOR SELECT
USING (auth.uid() = patient_id);
CREATE POLICY "Users can create requests"
ON requests FOR INSERT
WITH CHECK (auth.uid() = patient_id);
CREATE POLICY "Users can update their own requests"
ON requests FOR UPDATE
USING (auth.uid() = patient_id);
-- Inventory table policies
CREATE POLICY "Anyone can view inventory"
ON inventory FOR SELECT
USING (true);
CREATE POLICY "Only admins can modify inventory"
ON inventory FOR ALL
USING (auth.uid() IN (SELECT id FROM users WHERE role = 'admin'));
-- Appointments table policies
CREATE POLICY "Users can view their own appointments"
ON appointments FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can create appointments"
ON appointments FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update their own appointments"
ON appointments FOR UPDATE
USING (auth.uid() = user_id);
-- Storage policies
CREATE POLICY "Users can upload their own avatars"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'avatars' AND
auth.uid()::text = (storage.foldername(name))[1]
);
CREATE POLICY "Anyone can view avatars"
ON storage.objects FOR SELECT
USING (bucket_id = 'avatars');
CREATE POLICY "Users can update their own avatars"
ON storage.objects FOR UPDATE
USING (
bucket_id = 'avatars' AND
auth.uid()::text = (storage.foldername(name))[1]
);
CREATE POLICY "Users can delete their own avatars"
ON storage.objects FOR DELETE
USING (
bucket_id = 'avatars' AND
auth.uid()::text = (storage.foldername(name))[1]
);
- Bucket Name:
avatars
- Purpose: Stores user profile pictures
- Configuration:
- Public access for viewing
- Secure upload process
- Automatic URL generation
- File size limits and type restrictions
-
Avatar Upload:
- User selects image in profile screen
- Image is processed and compressed
- Uploaded to Supabase Storage
- URL stored in user's avatar_url field
-
Avatar Display:
- Retrieved from storage using avatar_url
- Cached for performance
- Fallback to default avatar if not found
-
Donation Count Trigger
- Event: AFTER INSERT OR UPDATE
- Table: donations
- Condition: status = 'approved'
- Action: Increments user's donation_count
-
Donation Appointment Trigger
- Event: AFTER INSERT OR UPDATE
- Table: donations
- Condition: status = 'approved'
- Action: Creates dropoff appointment
-
Donation Inventory Trigger
- Event: AFTER INSERT OR UPDATE
- Table: donations
- Condition: status = 'approved'
- Action: Adds to inventory
-
Request Appointment Trigger
- Event: AFTER INSERT OR UPDATE
- Table: requests
- Condition: status = 'approved'
- Action: Creates pickup appointment
-
Request Inventory Trigger
- Event: AFTER INSERT OR UPDATE
- Table: requests
- Condition: status = 'approved'
- Action: Updates inventory quantities
- Inventory validation before updates
- Exception handling for insufficient stock
- Duplicate appointment prevention
- Transaction management for data consistency
- Material 3 components
- Healthcare-focused color palette
- Accessible design elements
- Responsive layouts
- Primary:
#00796B
(Teal) - Secondary:
#004D40
(Dark Teal) - Background:
#E0F2F1
(Light Teal) - Accent:
#B2DFDB
(Pale Teal)
-
IDE Configuration
- Install Flutter and Dart plugins
- Configure Android SDK
- Set up Flutter SDK path
-
Code Style
- Follow Flutter style guide
- Use Flutter lints
- Implement proper error handling
-
Testing
- Unit tests
- Widget tests
- Integration tests
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Create a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.