Menu
Products
Products
Video Hosting
Upload and manage your videos in a centralized video library.
Image Hosting
Upload and manage all your images in a centralized library.
Galleries
Choose from 100+templates to showcase your media in style.
Video Messaging
Record, and send personalized video messages.
CincoTube
Create your own community video hub your team, students or fans.
Pages
Create dedicated webpages to share your videos and images.
Live
Create dedicated webpages to share your videos and images.
For Developers
Video API
Build a unique video experience.
DeepUploader
Collect and store user content from anywhere with our file uploader.
Solutions
Solutions
Enterprise
Supercharge your business with secure, internal communication.
Townhall
Webinars
Team Collaboration
Learning & Development
Creative Professionals
Get creative with a built in-suite of editing and marketing tools.
eCommerce
Boost sales with interactive video and easy-embedding.
Townhall
Webinars
Team Collaboration
Learning & Development
eLearning & Training
Host and share course materials in a centralized portal.
Sales & Marketing
Attract, engage and convert with interactive tools and analytics.
"Cincopa helped my Enterprise organization collaborate better through video."
Book a Demo
Resources
Resources
Blog
Learn about the latest industry trends, tips & tricks.
Help Centre
Get access to help articles FAQs, and all things Cincopa.
Partners
Check out our valued list of partners.
Product Updates
Stay up-to-date with our latest greatest features.
Ebooks, Guides & More
Customer Stories
Hear how we've helped businesses succeed.
Boost Campaign Performance Through Video
Discover how to boost your next campaign by using video.
Download Now
Pricing
Log in
Get a demo
Get Started
Building a Flutter chat app with the Stream SDK starts with understanding how a reliable messaging layer shapes the entire user experience. Real-time communication needs a structure that stays consistent, responds quickly, and handles active conversations without breaking. Flutter gives the flexibility to craft the interface, while Stream provides the foundation that keeps messages flowing smoothly. Bringing these two together creates a setup where design and functionality move in sync, making the chat experience both stable and easy to maintain. Prerequisites 1. System Requirements: Flutter 3.26+ (flutter doctor --verbose) Android Studio/VS Code + Flutter/Dart extensions iOS simulator or Android emulator/device Stream dashboard account (api_key) 2. Create Project: flutter create chat_app cd chat_app 3. pubspec.yaml (exact dependencies): name: chat_app dependencies: flutter: sdk: flutter stream_chat_flutter: ^9.18.0 stream_chat_flutter_core: ^9.18.0 4. Run : flutter pub get Building a Flutter Chat App With The Stream SDK When this part of the app comes together, the goal is to connect the visual structure you build in Flutter with the real-time message handling provided by Stream, ensuring that the chat features remain consistent, responsive, and ready for active conversations. main.dart import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'login_screen.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); final client = StreamChatClient( 'YOUR_API_KEY_HERE', // From Stream dashboard logLevel: Level.INFO, ); runApp(ChatApp(client: client)); } class ChatApp extends StatelessWidget { final StreamChatClient client; const ChatApp({super.key, required this.client}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Stream Chat Demo', theme: ThemeData( useMaterial3: true, primarySwatch: Colors.blue, ), builder: (context, child) => StreamChat( client: client, streamChatThemeData: StreamChatThemeData.fromTheme(Theme.of(context)), child: child!, ), home: const LoginScreen(), ); } } Critical : StreamChat wrapper provides client context to all screens. Login Screen (login_screen.dart) - Secure Auth import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'channel_list_screen.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State
createState() => _LoginScreenState(); } class _LoginScreenState extends State
{ final _usernameController = TextEditingController(); bool _isLoading = false; String? _error; Future
_login() async { if (_usernameController.text.trim().isEmpty) return; setState(() { _isLoading = true; _error = null; }); try { final client = StreamChat.of(context).client; await client.connectUser( User( id: _usernameController.text.trim(), name: _usernameController.text.trim(), ), client.devToken(_usernameController.text.trim()), // Production: JWT ); if (mounted) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const ChannelListScreen()), ); } } on StreamChatException catch (e) { setState(() => _error = e.message); } catch (e) { setState(() => _error = 'Connection failed: $e'); } finally { if (mounted) setState(() => _isLoading = false); } } @override void dispose() { _usernameController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Login')), body: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextField( controller: _usernameController, decoration: InputDecoration( labelText: 'Username', errorText: _error, border: const OutlineInputBorder(), ), enabled: !_isLoading, ), const SizedBox(height: 16), ElevatedButton( onPressed: _isLoading ? null : _login, child: _isLoading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Connect'), ), if (_error != null) Padding( padding: const EdgeInsets.only(top: 8), child: Text(_error!, style: const TextStyle(color: Colors.red)), ), ], ), ), ); } } Channel List (channel_list_screen.dart) import 'package:flutter/material.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'channel_screen.dart'; import 'login_screen.dart'; class ChannelListScreen extends StatelessWidget { const ChannelListScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Channels'), actions: [ IconButton( icon: const Icon(Icons.logout), onPressed: () => _logout(context), ), ], ), body: ChannelsBloc( child: ChannelListView( filter: Filter.in_( 'members', [StreamChat.of(context).currentUser!.id], ), sort: const [ SortOption('last_message_at', direction: SortOption.DESC), ], limit: 20, channelWidget: const ChannelTile(), emptyBuilder: (context, createChannel) => Center( child: ElevatedButton( onPressed: createChannel, child: const Text('Create General Channel'), ), ), onChannelTap: (channel) => Navigator.of(context).push( MaterialPageRoute( builder: (_) => ChannelScreen(channel: channel), ), ), ), ), ); } Future
_logout(BuildContext context) async { final client = StreamChat.of(context).client; await client.disconnectUser(); if (context.mounted) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const LoginScreen()), ); } } } Chat Screen (channel_screen.dart) import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'thread_page.dart'; class ChannelScreen extends StatelessWidget { final Channel channel; const ChannelScreen({super.key, required this.channel}); @override Widget build(BuildContext context) { return Scaffold( appBar: const StreamChannelHeader(), body: StreamChannel( channel: channel, child: Column( children: [ Expanded( child: StreamMessageListView( threadBuilder: (context, details, messages) { return ThreadPage( parentMessage: details.parentMessage!, ); }, ), ), const StreamMessageInput(), ], ), ), ); } } Thread Page (thread_page.dart) import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class ThreadPage extends StatefulWidget { final Message parentMessage; const ThreadPage({super.key, required this.parentMessage}); @override State
createState() => _ThreadPageState(); } class _ThreadPageState extends State
{ late final StreamMessageInputController _controller; @override void initState() { super.initState(); _controller = StreamMessageInputController( parentMessage: widget.parentMessage, ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: StreamThreadHeader(parent: widget.parentMessage), body: Column( children: [ Expanded(child: StreamMessageListView(parentMessage: widget.parentMessage)), StreamMessageInput(messageInputController: _controller), ], ), ); } } Run and Test the App Running & testing the app launches it to check if everything works as expected, from login to sending messages. This step helps spot problems early and ensures the app is ready for real end-users without surprises. Run the App : flutter run on 2 phones/emulators Login : User1 types name → ' Connect ' → See channels Create Chat : Tap ' Create General Channel ' → User1 joins Real-time Test : Login User2 → Send messages → Both see instantly Send Extras : Text + photo → Long-press message → Reply in thread Logout : Tap logout → Back to login (no errors)