Blogify is a modern, feature-rich blogging platform built with Flutter and Supabase. It provides a seamless and visually appealing experience for users to create, share, and manage blogs. Designed with clean architecture, Blogify ensures a scalable, maintainable, and testable codebase, making it ideal for developers and contributors who value high-quality standards.
- Features
- Screenshots
- Tech Stack
- Architecture
- Getting Started
- Running Tests
- Project Structure
- Supabase Backend Structure
- Contributing
- Roadmap
- User Sign Up with validation
- User Sign In with validation
- User Logout with confirmation dialog
- Current User Session Management
- Secure password requirements
- Create New Blogs with cover images
- View All Blogs with Pull to Refresh
- Blog Detail View with reading time
- Share Blogs via native share sheet
- Edit Blogs (owner only)
- Delete Blogs with confirmation (owner only)
- Infinite scroll pagination (10 blogs per page)
- Professional and modern UI
- Dark/Light theme support
- Responsive design for mobile devices
- Smooth animations and transitions
- Custom animated loader
- Image Picker for blog cover images
- Snackbar Notifications with types (success, error, warning, info)
- Date Formatting and Reading Time Calculation
- Input validation for all forms
| Technology | Purpose |
|---|---|
| Flutter 3.38+ | Cross-platform UI framework |
| Dart 3.10+ | Programming language |
| Supabase | Backend (Auth, Database, Storage) |
| flutter_bloc | State management |
| get_it | Dependency injection |
| equatable | Value equality for entities/states |
| dartz | Functional programming (Either type) |
| share_plus | Native sharing functionality |
| cached_network_image | Image caching |
Blogify follows Clean Architecture with three distinct layers:
┌─────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ (Pages, Widgets, BLoC/Cubits) │
├─────────────────────────────────────────────────────────┤
│ Domain Layer │
│ (Entities, UseCases, Repository Interfaces) │
├─────────────────────────────────────────────────────────┤
│ Data Layer │
│ (Models, Data Sources, Repository Implementations) │
└─────────────────────────────────────────────────────────┘
- UI dispatches Event to BLoC
- BLoC calls UseCase
- UseCase calls Repository (interface)
- Repository implementation calls DataSource
- DataSource interacts with Supabase
- Result flows back as
Either<Failure, SuccessType> - BLoC emits new State
- Flutter SDK 3.38.4 or higher
- Dart SDK 3.10.0 or higher
- A Supabase account and project
-
Clone the repository:
git clone https://github.com/mahmoodhamdi/blogify.git cd blogify -
Install dependencies:
flutter pub get
-
Create environment file:
Copy
.env.exampleto.env:cp .env.example .env
-
Add your Supabase credentials to
.env:SUPABASE_URL=your_supabase_url_here SUPABASE_ANON_KEY=your_supabase_anon_key_here
-
Set up Supabase:
- Create a new Supabase project
- Run the SQL scripts from the Supabase Backend Structure section
- Create a storage bucket named
blog_images
-
Run the app:
flutter run
Blogify includes comprehensive unit tests for core utilities and validators.
# Run all tests
flutter test
# Run tests with coverage
flutter test --coverage
# Run a specific test file
flutter test test/core/validators/validation_test.darttest/
├── core/
│ ├── utils/
│ │ ├── calculate_reading_time_test.dart
│ │ └── format_date_test.dart
│ └── validators/
│ └── validation_test.dart
└── widget_test.dart
lib/
├── core/
│ ├── common/
│ │ ├── cubits/app_user/ # App-wide user state
│ │ ├── entities/ # Shared entities
│ │ └── widgets/ # Reusable widgets
│ ├── error/ # Exception & Failure classes
│ ├── network/ # Connection checker
│ ├── secrets/ # Environment configuration
│ ├── theme/ # App themes & colors
│ ├── usecase/ # Base UseCase interface
│ ├── utils/ # Utility functions
│ └── validators/ # Input validation
├── features/
│ ├── auth/
│ │ ├── data/ # Data sources, models, repos
│ │ ├── domain/ # Entities, repos interface, usecases
│ │ └── presentation/ # BLoC, pages, widgets
│ └── blog/
│ ├── data/
│ ├── domain/
│ └── presentation/
├── init_dependencies.dart # Dependency injection setup
└── main.dart # App entry point
CREATE TABLE profiles (
id UUID REFERENCES auth.users NOT NULL PRIMARY KEY,
updated_at TIMESTAMP WITH TIME ZONE,
name TEXT,
CONSTRAINT name_length CHECK (char_length(name) >= 3)
);
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public profiles are viewable by everyone." ON profiles
FOR SELECT USING (true);
CREATE POLICY "Users can insert their own profile." ON profiles
FOR INSERT WITH CHECK ((SELECT auth.uid()) = id);
CREATE POLICY "Users can update their own profile." ON profiles
FOR UPDATE USING ((SELECT auth.uid()) = id);CREATE TABLE blogs (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
poster_id UUID REFERENCES auth.users NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
image_url TEXT,
topics _TEXT,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE blogs ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public blogs are viewable by everyone." ON blogs
FOR SELECT USING (true);
CREATE POLICY "Users can insert their own blogs." ON blogs
FOR INSERT WITH CHECK ((SELECT auth.uid()) = poster_id);
CREATE POLICY "Users can update their own blogs." ON blogs
FOR UPDATE USING ((SELECT auth.uid()) = poster_id);
CREATE POLICY "Users can delete their own blogs." ON blogs
FOR DELETE USING ((SELECT auth.uid()) = poster_id);INSERT INTO storage.buckets (id, name) VALUES ('blog_images', 'blog_images');
CREATE POLICY "Blog images are publicly accessible." ON storage.objects
FOR SELECT USING (bucket_id = 'blog_images');
CREATE POLICY "Anyone can upload a blog image." ON storage.objects
FOR INSERT WITH CHECK (bucket_id = 'blog_images');
CREATE POLICY "Anyone can update their own blog images." ON storage.objects
FOR UPDATE USING ((SELECT auth.uid()) = owner) WITH CHECK (bucket_id = 'blog_images');CREATE FUNCTION public.handle_new_user()
RETURNS TRIGGER
SET search_path = ''
AS $$
BEGIN
INSERT INTO public.profiles (id, name)
VALUES (new.id, new.raw_user_meta_data->>'name');
RETURN new;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user();Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
flutter test) - Ensure code passes analysis (
flutter analyze) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
We follow Conventional Commits:
feat:- New featuresfix:- Bug fixesdocs:- Documentation changestest:- Adding/updating testsrefactor:- Code refactoringchore:- Maintenance tasks
- Edit Blogs functionality
- Delete Blogs functionality
- User Logout functionality
- Pagination with infinite scroll
- User Profile pages
- Blog search with filters
- Comment system
- Blog drafts
- Push notifications
- Offline mode with local caching
- Markdown editor support
- Blog analytics
- CI/CD with GitHub Actions
- Web and desktop support
This project is open source and available under the MIT License.
- Author: Mahmood Hamdi
- Email: [email protected]
- GitHub: @mahmoodhamdi