In late 2022, as Flutter was solidifying its position in the cross-platform development ecosystem, I created an open-source project with a specific purpose: to provide the Flutter community with a high-quality reference implementation of a chat application.
Flutter Social Chat was never intended as a commercial product or market application, but rather as an educational resource and architectural showcase that developers could learn from and adapt for their projects.
This educational initiative quickly gained traction within the Flutter community, eventually catching the attention of Stream, a leading provider of chat, video, moderation, and activity feed APIs. In early 2023, Stream sponsored the project, a significant validation of its educational value and technical approach. This partnership enabled me to develop a more comprehensive template application that demonstrated the integration of Stream’s robust messaging infrastructure while maintaining best practices in architecture and code organization.
💡 Flutter Social Chat was explicitly created as a community resource and educational template, not a market application. Its primary value lies in demonstrating architectural principles, integration patterns, and code quality standards that developers can study and apply to their projects.
Now in 2025, after maintaining and evolving the project to keep pace with Flutter’s development, Stream has renewed its sponsorship, recognizing the continued educational value of Flutter Social Chat for the growing Flutter community.


Technical Highlights: Beyond Just Another Chat App
Before diving into the architectural details, it’s worth highlighting what makes Flutter Social Chat stand out as a technical template:
- MVVM Architecture: Demonstrating clean separation of UI, business logic, and data layers
- BLoC Pattern: Implementing efficient reactive state management with Flutter BLoC
- Stream Chat SDK: Showcasing powerful real-time messaging capabilities
- Firebase Integration: Illustrating authentication and data storage patterns
- Responsive Design: Adapting UI elements for different device sizes
- Modular Structure: Organizing code with a clear separation of concerns
- Environment Configuration: Managing API keys and secrets securely
- Dependency Injection: Using GetIt for flexible service location
- Functional Programming: Demonstrating elegant flow control with FPDart
These technical choices weren’t made arbitrarily—each represents a best practice that contributes to the codebase's overall maintainability, scalability, and educational value.
Core Features: What Developers Can Learn From
The template implements several key features that demonstrate standard real-world requirements:
Phone Authentication with Firebase
The app showcases a secure sign-in flow with SMS verification, a pattern increasingly common in modern applications. This implementation demonstrates:
- Handling phone number formatting and validation
- Managing verification codes
- Persisting authentication state
- Error handling for authentication failures
Real-Time Messaging with Stream
At the heart of the application is a robust real-time messaging system powered by Stream Chat:
- Individual and group conversations
- Message delivery status and read receipts
- Rich media messaging (photos, links)
- Typing indicators and presence detection
Architectural Evolution: From DDD to MVVM
Initial Implementation with Domain-Driven Design
When I first published Flutter Social Chat in late 2022, I implemented it using Domain-Driven Design (DDD) principles. This architectural choice was deliberate, intended to showcase how DDD concepts could be applied in Flutter applications:
- Strong Domain Focus: The business logic centres on defined domain entities and value objects.
- Context Boundaries: The template established explicit boundaries between different application parts, preventing unwanted dependencies.
- Ubiquitous Language: A shared language across the codebase improved readability and understanding.
While DDD served educational purposes well initially, as the Flutter ecosystem and official guidance evolved, I recognized an opportunity to improve the template’s relevance for the community.
The Transition to MVVM

By 2024, Flutter’s best practices had evolved, with the team increasingly advocating for the Model-View-ViewModel (MVVM) architecture. To maintain Flutter Social Chat as a valuable learning resource, I strategically decided to refactor the codebase from DDD to MVVM.
This transition was motivated by several educational considerations:
- Official Recommendation: Alignment with Flutter’s officially recommended architectural patterns.
- Community Adoption: The adoption of MVVM within the Flutter community has increased, making the template more immediately useful to more developers.
- Widget-Friendly Structure: Demonstrating how MVVM’s separation of concerns complements Flutter’s widget-based UI approach.
- Accessibility for Learners: A more widely understood architecture would make the template more accessible as a learning resource.
⚡ Architecture Insight: The transition from DDD to MVVM wasn’t merely a technical exercise; it represented an update to reflect the evolving Flutter ecosystem, ensuring that the template remains relevant as a learning resource for current best practices.
Project Structure: A Blueprint for Scalable Apps
The folder structure of Flutter Social Chat reflects the MVVM architecture, with a clear separation of concerns:
12345678910111213141516171819lib/ ├── core/ # Core functionality, utils, and app-wide services │ ├── config/ # Application configuration │ ├── constants/ # App-wide constants and enums │ ├── di/ # Dependency injection setup │ ├── init/ # App initialization │ └── interfaces/ # Repository and service interfaces ├── data/ # Data layer implementation │ ├── extensions/ # Extension methods │ └── repository/ # Repository implementations ├── domain/ # Domain layer with model definitions │ └── models/ # Domain models ├── presentation/ # UI layer │ ├── blocs/ # BLoC state management │ ├── design_system/ # Reusable UI components │ ├── views/ # Screen implementations │ ├── gen/ # Generated asset paths │ └── l10n/ # Localization resources └── main.dart # Application entry point
This structure facilitates the separation of concerns and makes the codebase more accessible to developers looking to understand how different components interact. Each directory has a specific responsibility:
- Core: Contains application-wide functionality that supports all other layers
- Data: Handles data access and persistence, implementing interfaces defined in Core
- Domain: Defines the business entities and logic
- Presentation: Manages UI rendering and user interaction
Implementation Details: The Power of BLoC

To implement the ViewModel layer in the MVVM architecture, I chose the BLoC (Business Logic Component) pattern and its cubits, making it a central educational aspect of the template.
Why BLoC?
The template uses BLoC to demonstrate several important patterns:
- Reactive Programming Model: Showcasing how BLoC leverages Dart’s stream-based reactive programming model, which aligns perfectly with Flutter’s reactive UI paradigm.
- Unidirectional Data Flow: Illustrating events flowing from the UI to the BLoC, and states flowing from the BLoC to the UI, creating a predictable data flow pattern.
- Separation of Concerns: Demonstrating how business logic can be completely decoupled from the UI, making it easier to test and maintain.
- Testability: Showing how BLoCs can be tested in isolation from the UI, enabling comprehensive unit testing of business logic (although we did not test it yet).
At the heart of the implementation is the use of HydratedBloc, an extension to the flutter_bloc package that provides state persistence capabilities:
123456789101112131415161718192021222324252627282930313233343536373839// Chat Session Management with HydratedBloc class ChatSessionCubit extends HydratedCubit\<ChatSessionState\> { final IChatRepository \_chatRepository; ChatSessionCubit(this.\_chatRepository) : super(ChatSessionState.initial()); // State persistence methods Map\<String, dynamic\> toJson(ChatSessionState state) \=\> state.toJson(); ChatSessionState fromJson(Map\<String, dynamic\> json) \=\> ChatSessionState.fromJson(json); } All state classes in the template extend Equatable to demonstrate efficient state comparison: // Equatable-based State Class class ChatSessionState extends Equatable { final ChatUserModel user; final ConnectionStatus status; const ChatSessionState({required this.user, required this.status}); List\<Object\> get props \=\> \[user, status\]; // Factory methods and copyWith for immutability factory ChatSessionState.initial() \=\> ChatSessionState( user: ChatUserModel.empty(), status: ConnectionStatus.disconnected, ); ChatSessionState copyWith({ChatUserModel? user, ConnectionStatus? status}) \=\> ChatSessionState( user: user ?? this.user, status: status ?? this.status, ); }
Stream Chat Integration: A Technical Learning Resource
The integration of Stream’s Chat SDK represents a core educational component of Flutter Social Chat. The template demonstrates how developers can leverage Stream’s comprehensive platform for real-time messaging to address complex challenges that would otherwise require significant engineering resources.
Secure Environment Configuration
One important aspect of the Stream integration is the secure management of API keys and secrets. The project includes a detailed environment configuration setup:
123# .env file structure STREAM\_CHAT\_API\_KEY=your\_actual\_stream\_chat\_api\_key STREAM\_CHAT\_API\_SECRET=your\_actual\_stream\_chat\_api\_secret
This approach, documented in detail in the project’s ENV_SETUP.md
file, demonstrates secure credential management—a critical consideration for any production application. The setup includes:
- Environment variable isolation using
.env
files - Exclusion of sensitive files from version control
- Clear documentation for contributors
- Runtime configuration loading
Technical Integration Approach
The template showcases a layered approach to Stream integration, designed to teach best practices while maintaining architectural integrity:
1. Repository Layer Integration
The first layer of integration occurs at the repository level, demonstrating how to abstract third-party services through custom interfaces:
123456789101112131415161718192021222324// Chat Repository Implementation class ChatRepository implements IChatRepository { final StreamChatClient \_client; final IAuthRepository \_authRepository; ChatRepository(this.\_client, this.\_authRepository); Future\<Either\<Failure, Unit\>\> connectUser() async { try { final currentUser \= await \_authRepository.getCurrentUser(); final token \= \_generateToken(currentUser.id); await \_client.connectUser( User(id: currentUser.id, name: currentUser.name), token, ); return right(unit); } on StreamChatError catch (e) { return left(ChatFailure.connectionError(e.message)); } } }
This abstraction provides several educational benefits:
- Demonstrating how the application core doesn’t need to depend on third-party implementations directly
- Showing how to facilitate testing through interface abstractions
- Illustrating how to add custom business logic around external services
2. BLoC Integration Layer
The template shows how BLoCs can consume repository interfaces and transform service events into application states:
12345678910111213141516171819// Chat Management BLoC class ChatManagementCubit extends Cubit\<ChatManagementState\> { final IChatRepository \_chatRepository; StreamSubscription\<List\<Channel\>\>? \_channelsSubscription; ChatManagementCubit(this.\_chatRepository) : super(ChatManagementState.initial()) { \_subscribeToChannels(); } void \_subscribeToChannels() { \_channelsSubscription \= \_chatRepository.userChannels .listen(\_updateChannelsList); } void \_updateChannelsList(List\<Channel\> channels) { emit(state.copyWith(channels: channels)); } }
3. UI Integration Layer
At the UI layer, the template demonstrates a hybrid approach, showcasing how to combine third-party UI components with custom UI:
12345678910111213141516171819202122232425// Channel List View with Stream Components Widget buildChannelList() { return StreamChannelListView( controller: channelListController, itemBuilder: (context, channels, index, defaultWidget) { final channel \= channels\[index\]; return CustomChannelPreview( channel: channel, onTap: () \=\> navigateToChannel(channel), ); }, ); } // Message List with Stream Components Widget buildMessageList() { return StreamMessageListView( messageBuilder: (context, details, messages, defaultWidget) { return CustomMessageWidget( message: details.message, isMyMessage: details.isMyMessage, ); }, ); }
Advanced Feature Examples
Beyond basic messaging, the template demonstrates several advanced features that developers can learn from:
1. Channel Creation and Management
The template shows implementation patterns for both one-on-one and group chats with different creation flows:
123456789101112131415161718192021222324// Channel Creation Logic Future\<void\> createChannel({ required List\<String\> memberIds, required String name, required bool isGroup, }) async { final channelId \= \_generateUniqueId(); final extraData \= { 'name': name, 'image': isGroup ? \_defaultGroupImage : null, 'members': memberIds, }; final result \= await \_chatRepository.createChannel( channelType: 'messaging', channelId: channelId, extraData: extraData, ); result.fold( (failure) \=\> emit(state.copyWith(error: failure)), (\_) \=\> emit(state.copyWith(channelCreated: true)), ); }
2. Rich Media Messaging
The template demonstrates how to implement media sharing features:
1234567891011121314151617// Photo Message Sending Future\<void\> sendPhotoMessage({ required String channelId, required File photo, }) async { emit(state.copyWith(sendingMedia: true)); final result \= await \_chatRepository.sendPhotoMessage( channelId: channelId, photo: photo, ); result.fold( (failure) \=\> emit(state.copyWith(error: failure, sendingMedia: false)), (\_) \=\> emit(state.copyWith(sendingMedia: false, mediaSent: true)), ); }
🚀 Educational Value: A key learning point of the template is how Stream’s SDK abstracts away complex real-time infrastructure, allowing developers to focus on creating user experiences rather than building communication systems from scratch.
Firebase Integration: Authentication and Data Persistence

While Stream provides the chat infrastructure, Firebase serves as the backbone for authentication and data persistence in the Flutter Social Chat. This integration demonstrates several key patterns:
Phone Authentication Flow
The template implements a complete phone authentication flow using Firebase Auth:
- Phone number input and validation
- SMS code verification
- User profile creation/retrieval
- Secure session management
Firestore for User Data
User profiles and metadata are stored in Firestore, demonstrating patterns for:
- Document structure and organization
- Real-time data synchronization
- Security rules implementation
- Efficient data querying
Integration with Stream Chat
The template showcases how to bridge Firebase and Stream services:
- Mapping Firebase user IDs to Stream user IDs
- Synchronizing user metadata between platforms
- Managing authentication state across services
- Handling error cases and edge conditions
Demonstrating Resilience: Connectivity and State Persistence
The template demonstrates how chat applications can function reliably across varying network conditions—a critical aspect of real-world applications:
1. Connection State Monitoring
The code shows how to monitor both device connectivity and connection status:
123456789101112131415161718192021222324// Connectivity Monitoring BLoC class ConnectivityCubit extends Cubit\<ConnectivityState\> { final Connectivity \_connectivity; StreamSubscription\<ConnectivityResult\>? \_subscription; ConnectivityCubit(this.\_connectivity) : super(ConnectivityState.initial()) { \_initialize(); } void \_initialize() { \_subscription \= \_connectivity.onConnectivityChanged.listen(\_updateState); \_checkCurrentStatus(); } Future\<void\> \_checkCurrentStatus() async { final result \= await \_connectivity.checkConnectivity(); \_updateState(result); } void \_updateState(ConnectivityResult result) { final isConnected \= result \!= ConnectivityResult.none; emit(state.copyWith(isConnected: isConnected)); } }
2. State Persistence with HydratedBloc
The template demonstrates state persistence techniques:
1234567891011121314151617// State Persistence with HydratedBloc Map\<String, dynamic\> toJson(ChatManagementState state) { return { 'channels': state.channels.map((c) \=\> c.cid).toList(), 'selectedChannelId': state.selectedChannelId, 'lastKnownMessageIds': state.lastKnownMessageIds, }; } ChatManagementState fromJson(Map\<String, dynamic\> json) { // Reconstruct state from persisted data return ChatManagementState( // Implementation details... ); }
This multi-layered approach provides developers with patterns they can adopt for robust applications.
Educational Design Considerations
While the architectural focus of Flutter Social Chat is on maintainability and scalability, the template also demonstrates important UI/UX patterns:
1. Responsive UI Design
The template shows practical implementations of responsive layouts that adapt to various screen sizes, an essential skill for modern Flutter developers.
2. Performance Optimization Patterns
The template demonstrates several optimization techniques:
- Lazy loading of content to minimize initial load times
- Efficient state management to prevent unnecessary rebuilds
- Caching of frequently accessed data
- Background processing for media uploads
How to Run and Contribute
An important aspect of any open-source educational template is accessibility. The project includes detailed setup instructions that walk developers through:
- Environment Configuration: Setting up the
.env
file with Stream credentials following the guidance in ENV_SETUP.md - Firebase Integration: Creating and configuring a Firebase project, enabling Phone Authentication
- Dependency Installation: Managing project dependencies
- Running the Application: Steps to launch and test the application
This attention to onboarding documentation ensures that developers can quickly get the template running and start exploring its implementation details.
Conclusion: Bridging Theory and Practice
Flutter Social Chat represents the intersection of architectural theory and practical implementation—a resource created by developers, for developers. Through this template, I’ve aimed to demonstrate that implementing production-grade features doesn’t require compromising on code quality or architectural principles.
The key insights from this project include:
- Architecture Matters: The transition from DDD to MVVM demonstrates how architectural choices impact development velocity and code maintainability.
- Leverage Specialized Services: Stream’s specialized chat infrastructure allows developers to focus on application features rather than building complex real-time systems.
- State Management is Critical: The BLoC pattern provides a consistent, testable approach to state management that scales with application complexity.
Continuing the Learning Journey

This article only scratches the surface of what Flutter Social Chat demonstrates. To dive deeper:
- Watch the Tutorial Series: I’m publishing a comprehensive video tutorial series on the FlutterWiz YouTube channel that walks through every implementation aspect.
- Explore the Source Code: The complete source code, with extensive documentation and comments, is available on GitHub.
- Take a look at our previous article: Build a Flutter Social Chat App
The beauty of open-source projects lies in their collaborative nature—I encourage you to use this template, adapt it, improve it, and share your learnings with the community.