Introduction to stream_chat_flutter_coreCopied!

This package provides basic building blocks for integrating Stream Chat into your application. The core package allows more customisation and hence provides business logic but no UI components. Please use the stream_chat_flutter package for the full-fledged suite of UI components or stream_chat for the low-level client.

The package primarily contains three types of classes:

  1. Business Logic Components

  2. Core Components

  3. Core Controllers

Business Logic Components (BLoCs)Copied!

These components allow you to have the maximum and lower-level control of the queries being executed. These widgets are used by the core components to access the API client.

The BLoCs we provide are:

  1. ChannelsBloc

  2. MessageSearchBloc

  3. UsersBloc

Core Components Copied!

Core components usually are an easy way to fetch data associated with Stream Chat which are decoupled from UI and often expose UI builders. Data fetching can be controlled with the controllers of the respective core components.

  1. ChannelListCore (Fetch a list of channels)

  2. MessageListCore (Fetch a list of messages from a channel)

  3. MessageSearchListCore (Fetch a list of search messages)

  4. UserListCore (Fetch a list of users)

  5. StreamChatCore (This is different from the other core components - it's an inherited widget that exposes the client to all the other widgets)

Core Controllers Copied!

Core Controllers are supplied to respective CoreList widgets which allows reloading and pagination of data whenever needed.

  1. ChannelListController

  2. MessageListController

  3. MessageSearchListController

  4. ChannelListController

Adding stream_chat_flutter_core to your appCopied!

Add stream_chat_flutter_core to your dependencies, to do that just open pubspec.yaml and add it inside the dependencies section using the latest version you can find here

1
2
3
4
5
dependencies: 
  flutter: 
    sdk: flutter 
 
  stream_chat_flutter_core: ^latest_version

Instantiate the Dart API client and add the StreamChatCore widget to your widget tree.

Note that:

  1. The Dart API client is initialized with your API Key

  2. The current user is set by calling connectUser on the client

  3. The client is then passed to the top-level StreamChatCore widget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Future<void> main() async { 
  /// Create a new instance of [StreamChatClient] passing the apikey obtained from your 
  /// project dashboard.YOUR_API_KEY 
  final client = StreamChatClient('{{ api_key }}'); 
 
  /// Set the current user. In a production scenario, this should be done using 
  /// a backend to generate a user token using our server SDK. 
  /// Please see the following for more information: 
  /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ 
  await client.connectUser( 
    User( 
      id: 'cool-shadow-7', 
      extraData: { 
        'image': 
            'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow', 
      }, 
    ), 
    'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', 
  ); 
 
  runApp( 
    StreamExample( 
      client: client, 
    ), 
  ); 
} 
 
/// Example application using Stream Chat core widgets. 
/// Stream Chat Core is a set of Flutter wrappers which provide basic functionality 
/// for building Flutter applications using Stream. 
/// If you'd prefer using pre-made UI widgets for your app, please see our other 
/// package, `stream_chat_flutter`. 
class StreamExample extends StatelessWidget { 
  /// Minimal example using Stream's core Flutter package. 
  /// If you'd prefer using pre-made UI widgets for your app, please see our other 
  /// package, `stream_chat_flutter`. 
  const StreamExample({ 
    Key key, 
     this.client, 
  }) : super(key: key); 
 
  /// Instance of Stream Client. 
  /// Stream's [StreamChatClient] can be used to connect to our servers and set the default 
  /// user for the application. Performing these actions trigger a websocket connection 
  /// allowing for real-time updates. 
  final StreamChatClient client; 
 
   
  Widget build(BuildContext context) { 
    return MaterialApp( 
      title: 'Stream Chat Core Example', 
      home: HomeScreen(), 
      builder: (context, child) => StreamChatCore( 
        client: client, 
        child: child, 
      ), 
    ); 
  } 
}

StreamChatCore is an inherited widget and must be the parent of all Chat related widgets.

Fetching and displaying a list of channelsCopied!

To display a list of channels you need two of our widgets

  • ChannelsBloc - a business logic component that handles API-calls/pagination

  • ChannelListCore - that renders the list of channels providing builders for every state of the list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/// Basic layout displaying a list of [Channel]s the user is a part of. 
/// This is implemented using [ChannelListCore]. 
/// 
/// [ChannelListCore] is a `builder` with callbacks for constructing UIs based 
/// on different scenarios. 
class HomeScreen extends StatelessWidget { 
  final channelListController = ChannelListController(); 
 
   
  Widget build(BuildContext context) { 
    return Scaffold( 
      appBar: AppBar( 
        title: Text('Channels'), 
      ), 
      body: ChannelsBloc( 
        child: ChannelListCore( 
          channelListController: channelListController, 
          filter: { 
            'type': 'messaging', 
            'members': { 
              r'$in': [ 
                StreamChatCore.of(context).user.id, 
              ] 
            } 
          }, 
          emptyBuilder: (BuildContext context) { 
            return Center( 
              child: Text('Looks like you are not in any channels'), 
            ); 
          }, 
          loadingBuilder: (BuildContext context) { 
            return Center( 
              child: SizedBox( 
                height: 100.0, 
                width: 100.0, 
                child: CircularProgressIndicator(), 
              ), 
            ); 
          }, 
          errorBuilder: (BuildContext context, dynamic error) { 
            return Center( 
              child: Text( 
                  'Oh no, something went wrong. Please check your config.'), 
            ); 
          }, 
          listBuilder: ( 
            BuildContext context, 
            List<Channel> channels, 
          ) => 
              LazyLoadScrollView( 
            onEndOfPage: () async { 
              channelListController.paginateData(); 
            }, 
            child: ListView.builder( 
              itemCount: channels.length, 
              itemBuilder: (BuildContext context, int index) { 
                final _item = channels[index]; 
                return ListTile( 
                  title: Text(_item.name), 
                  subtitle: StreamBuilder<Message>( 
                    stream: _item.state.lastMessageStream, 
                    initialData: _item.state.lastMessage, 
                    builder: (context, snapshot) { 
                      if (snapshot.hasData) { 
                        return Text(snapshot.data.text); 
                      } 
 
                      return SizedBox(); 
                    }, 
                  ), 
                  onTap: () { 
                    /// Display a list of messages when the user taps on an item. 
                    /// We can use [StreamChannel] to wrap our [MessageScreen] screen 
                    /// with the selected channel. 
                    /// 
                    /// This allows us to use a built-in inherited widget for accessing 
                    /// our `channel` later on. 
                    Navigator.of(context).push( 
                      MaterialPageRoute( 
                        builder: (context) => StreamChannel( 
                          channel: _item, 
                          child: MessageScreen(), 
                        ), 
                      ), 
                    ); 
                  }, 
                ); 
              }, 
            ), 
          ), 
        ), 
      ), 
    ); 
  } 
}
Screenshot

As you can see the ChannelsBloc must be above the ChannelListCore in the widget hierarchy. That's because the latter uses the former to retrieve data from the client.

Note that on line 70 while pushing the new route we wrap it with a StreamChannel widget, needed to expose the channel information to the other widgets in MessageScreen.

Checkout the ChannelsBloc and ChannelListCore API reference to see the available options.

The package provides a LazyLoadScrollView that together with ChannelListController helps you implement pagination in a few lines of code.

Fetching and displaying channel informationCopied!

Once you tap on your channel preview tile you want to show the channel information.

Usually, you want a list of messages and other general channel details, like the channel name, image, number of online members and so on...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/// A list of messages sent in the current channel. 
/// When a user taps on a channel in [HomeScreen], a navigator push [MessageScreen] 
/// to display the list of messages in the selected channel. 
/// 
/// This is implemented using [MessageListCore], a convenience builder with 
/// callbacks for building UIs based on different api results. 
class MessageScreen extends StatefulWidget { 
   
  _MessageScreenState createState() => _MessageScreenState(); 
} 
 
class _MessageScreenState extends State<MessageScreen> { 
  TextEditingController _controller; 
  ScrollController _scrollController; 
  final messageListController = MessageListController(); 
 
   
  void initState() { 
    super.initState(); 
    _controller = TextEditingController(); 
    _scrollController = ScrollController(); 
  } 
 
   
  void dispose() { 
    _controller.dispose(); 
    _scrollController.dispose(); 
    super.dispose(); 
  } 
 
  void _updateList() { 
    _scrollController.animateTo( 
      0, 
      duration: const Duration(milliseconds: 200), 
      curve: Curves.easeOut, 
    ); 
  } 
 
   
  Widget build(BuildContext context) { 
    /// To access the current channel, we can use the `.of()` method on [StreamChannel] 
    /// to fetch the closest instance. 
    final channel = StreamChannel.of(context).channel; 
    return Scaffold( 
      appBar: AppBar( 
        title: StreamBuilder<List<User>>( 
          initialData: channel.state.typingEvents, 
          stream: channel.state.typingEventsStream, 
          builder: (context, snapshot) { 
            if (snapshot.hasData && snapshot.data.isNotEmpty) { 
              return Text('${snapshot.data.first.name} is typing...'); 
            } 
            return SizedBox(); 
          }, 
        ), 
      ), 
      body: SafeArea( 
        child: Column( 
          children: [ 
            Expanded( 
              child: LazyLoadScrollView( 
                onEndOfPage: () async { 
                  messageListController.paginateData(); 
                }, 
                child: MessageListCore( 
                  emptyBuilder: (BuildContext context) { 
                    return Center( 
                      child: Text('Nothing here yet'), 
                    ); 
                  }, 
                  loadingBuilder: (BuildContext context) { 
                    return Center( 
                      child: SizedBox( 
                        height: 100.0, 
                        width: 100.0, 
                        child: CircularProgressIndicator(), 
                      ), 
                    ); 
                  }, 
                  messageListBuilder: ( 
                    BuildContext context, 
                    List<Message> messages, 
                  ) { 
                    return ListView.builder( 
                      controller: _scrollController, 
                      itemCount: messages.length, 
                      reverse: true, 
                      itemBuilder: (BuildContext context, int index) { 
                        final item = messages[index]; 
                        final client = StreamChatCore.of(context).client; 
                        if (item.user.id == client.uid) { 
                          return Align( 
                            alignment: Alignment.centerRight, 
                            child: Padding( 
                              padding: const EdgeInsets.all(8.0), 
                              child: Text(item.text), 
                            ), 
                          ); 
                        } else { 
                          return Align( 
                            alignment: Alignment.centerLeft, 
                            child: Padding( 
                              padding: const EdgeInsets.all(8.0), 
                              child: Text(item.text), 
                            ), 
                          ); 
                        } 
                      }, 
                    ); 
                  }, 
                  errorWidgetBuilder: (BuildContext context, error) { 
                    print(error?.toString()); 
                    return Center( 
                      child: SizedBox( 
                        height: 100.0, 
                        width: 100.0, 
                        child: 
                            Text('Oh no, an error occured. Please see logs.'), 
                      ), 
                    ); 
                  }, 
                ), 
              ), 
            ), 
            Padding( 
              padding: const EdgeInsets.all(8.0), 
              child: Row( 
                children: [ 
                  Expanded( 
                    child: TextField( 
                      controller: _controller, 
                      decoration: const InputDecoration( 
                        hintText: 'Enter your message', 
                      ), 
                    ), 
                  ), 
                  Material( 
                    type: MaterialType.circle, 
                    color: Colors.blue, 
                    clipBehavior: Clip.hardEdge, 
                    child: InkWell( 
                      onTap: () async { 
                        if (_controller.value.text.isNotEmpty) { 
                          await channel.sendMessage( 
                            Message(text: _controller.value.text), 
                          ); 
                          _controller.clear(); 
                          _updateList(); 
                        } 
                      }, 
                      child: const Padding( 
                        padding: EdgeInsets.all(8.0), 
                        child: Center( 
                          child: Icon( 
                            Icons.send, 
                            color: Colors.white, 
                          ), 
                        ), 
                      ), 
                    ), 
                  ) 
                ], 
              ), 
            ) 
          ], 
        ), 
      ), 
    ); 
  } 
} 
 
/// Extensions can be used to add functionality to the SDK. In the examples 
/// below, we add two simple extensions to the [StreamChatClient] and [Channel]. 
extension on StreamChatClient { 
  /// Fetches the current user id. 
  String get uid => state.user.id; 
} 
 
extension on Channel { 
  /// Fetches the name of the channel by accessing [extraData] or [cid]. 
  String get name { 
    final _channelName = extraData['name']; 
    if (_channelName != null) { 
      return _channelName; 
    } else { 
      return cid; 
    } 
  } 
}
Screenshot

As you can see MessageListCore is similar to ChannelListCore. It shows the message list retrieving data from the API and lets you customize the state of the list using UI builders. It also accepts a MessageListController that helps you handle pagination.

In the snippet above you can find a basic message input implementation, but you can implement your own one and use StreamChannel.of(context) to access the specific Channel client and send/edit a message.

In the AppBar we show the channel title, but you can access other channel details using the channel client.

Checkout the MessageListCore, MessageListController, and Channel API reference to explore the main options.

Typing indicatorsCopied!

Building a typing indicator is very simple.

You can find a Stream<List<User>> in channel.state.typingEventsStream that contains the list of users currently typing.

Using a StreamBuilder you can listen to changes and render the information the way you want.

1
2
3
4
5
6
7
8
9
10
StreamBuilder<List<User>>( 
  initialData: channel.state.typingEvents, 
  stream: channel.state.typingEventsStream, 
  builder: (context, snapshot) { 
    if (snapshot.hasData && snapshot.data.isNotEmpty) { 
      return Text('${snapshot.data.first.name} is typing...'); 
    } 
    return SizedBox(); 
    }, 
)