StreamMessageInputController

This commit is contained in:
Salvatore Giordano
2022-05-02 11:59:32 +02:00
parent 736d70e466
commit 402b0038c6
13 changed files with 121 additions and 286 deletions
@@ -1,90 +0,0 @@
---
id: channels_bloc
sidebar_position: 7
title: ChannelsBloc
---
A Widget Dedicated To The Management Of A Channel List With Pagination.
### Background
Most widgets in the Core SDK are focused on fetching a particular type of object from Stream Chat - channels,
messages, users, etc. The BLoC widgets bundle up the base functions used to fetch data as well as the current
data fetched by the respective functions. Furthermore, the Core widgets use this BLoC to fetch new or
existing data and build UI based on it.
All Core and UI widgets which focus on fetching a list of objects need to have their respective functions
above them in the widget tree. The ChannelListCore and ChannelListView require the ChannelsBloc
above them in the widget hierarchy without which they will fail.
### Understanding The Widget
`ChannelsBloc` is used together with `ChannelListCore` to manage a list of
Channels with pagination, re-ordering, querying and other operations
associated with Channels.
`ChannelsBloc` can be accessed at anytime by using the static `.of` method
using Flutter's `BuildContext`.
```dart
var _channelsBloc = ChannelsBloc.of(context);
```
The `ChannelsBloc` widget encapsulates common functionality related to channel lists such as fetching
the existing channels and querying new channels and also supplies them down the widget tree.
The widget is required for the respective core widget (`ChannelListCore`) to fetch channels and hence
must be above the core widget in the tree.
Here is a basic implementation of `ChannelsBloc`:
```dart
ChannelsBloc(
child: // Further Widget Tree
),
```
The `ChannelsBloc` widget allows three customisations:
#### Lock Channels Order
ChannelsBloc may change the order of channels when new messages arrive. To lock this order, we can
set the `lockChannelsOrder` property to true.
```dart
ChannelsBloc(
lockChannelsOrder: true,
child: // Further Widget Tree
),
```
#### Set custom channel order
We can decide the order of the channels in the list by supplying a comparator to the `channelsComparator`
parameter:
```dart
ChannelsBloc(
channelsComparator: (a, b) {
return a.createdAt!.millisecondsSinceEpoch >
b.createdAt!.millisecondsSinceEpoch
? 1
: -1;
},
child: // Further Widget Tree
),
```
#### Decide if channel should be added on new message event
When a new message arrives, a `message.new` event is received. We can decide if we want to add the channel
to the list using the `shouldAddChannel` parameter which is a callback supplying the event data:
```dart
ChannelsBloc(
shouldAddChannel: (event) {
return event.message!.extraData['priority'] == '1';
},
child: // Further Widget Tree
),
```
@@ -1,40 +0,0 @@
---
id: message_search_list_block
sidebar_position: 8
title: MessageSearchListBloc
---
A Widget Used To Manage A List Of Messages With Pagination.
### Background
Most widgets in the Core SDK are focused on fetching a particular type of object from Stream Chat - channels,
messages, users etc. The BLoC widgets bundle up the base functions used to fetch data as well as the current
data fetched by the respective functions. Furthermore, the Core widgets use this BLoC to fetch new or
existing data and build UI based on it.
All Core and UI widgets which focus on fetching a list of objects need to have their respective functions
above them in the widget tree. The MessageSearchListCore and MessageSearchListView require the
MessageSearchListCore above them in the widget hierarchy without which they will fail.
### Understanding The Widget
This class can be used to load messages, perform queries, etc.
`MessageSearchBloc` can be accessed at anytime by using the static `.of` method
using Flutter's BuildContext.
```dart
var _searchBloc = MessageSearchBloc.of(context);
```
The `MessageSearchBloc` widget encapsulates common functionality related to searching for messages
across channels and also supplies them down the widget tree.
Here is a basic implementation of `ChannelsBloc`:
```dart
MessageSearchBloc(
child: // Further Widget Tree
),
```
@@ -1,41 +0,0 @@
---
id: message_search_list_core
sidebar_position: 6
title: MessageSearchListCore
---
A Widget For Displaying Message Searches
### Background
The UI SDK of Stream Chat supplies a `MessageSearchListView` class that builds a list of channels fetching
according to the filters and sort order given. However, in some cases, implementing novel UI is necessary
that cannot be done using the customization approaches given in the widget.
To do this, we extracted the logic required for fetching channels into a 'Core' widget - a widget that
fetches channels in the expected way via the usual params but does not supply any UI and instead
exposes builders to build the UI in situations such as loading, empty data, errors, and on data received.
### Basic Example
`MessageSearchListCore` is a simplified class that allows searching for
messages across channels while exposing UI builders.
A `MessageSearchListController` is used to load and paginate data.
```dart
class MessageSearchPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: MessageSearchListCore(
messageQuery: _messageFilter,
filters: _channelsFilter,
limit: 20,
),
);
}
}
```
Make sure to have a `MessageSearchBloc` ancestor in order to provide the
information about the messages.
@@ -0,0 +1,87 @@
---
id: stream_message_input_controller
sidebar_position: 4
title: StreamMessageInputController
---
A Widget For Controlling A Message Input
### Background
The `StreamMessageInputController` is a controller class that embed the business logic to compose a message.
`StreamMessageInputController` is a parameter of the `StreamMessageInput` widget.
Check the [`StreamMessageInput` documentation](../stream_chat_flutter/stream_message_input.mdx) to read more about that.
### Basic Example
Building a custom message input is a common task. Here is an example of how to use the `StreamMessageInputController` to build a simple custom message input widget.
First of all we should create an instance of the `StreamMessageInputController`.
```dart
class MessageScreenState extends State<MessageScreen> {
final StreamMessageInputController messageInputController = StreamMessageInputController();
```
Make sure you call `messageInputController.dispose()` when the controller is no longer required.
```dart
@override
void dispose() {
messageInputController.dispose();
super.dispose();
}
```
The `StreamMessageInputController` is basically a `ValueNotifier` that notifies you when the message being composed has changed.
You can use a `ValueListenableBuilder` to build your UI depending on the latest message.
For a very simple message input you could even pass the `messageInputController.textEditingController` to your `TextField` and set the `onChanged` callback.
```dart
...
Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: TextField(
controller: messageInputController.textEditingController,
onChanged: (s) => messageInputController.text = s,
decoration: const InputDecoration(
hintText: 'Enter your message',
),
),
),
Material(
type: MaterialType.circle,
color: Colors.blue,
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () async {
if (messageInputController.message.text?.isNotEmpty ==
true) {
await channel.sendMessage(
messageInputController.message,
);
messageInputController.clear();
if (mounted) {
_updateList();
}
}
},
child: const Padding(
padding: EdgeInsets.all(8),
child: Center(
child: Icon(
Icons.send,
color: Colors.white,
),
),
),
),
),
],
),
),
...
```
@@ -1,6 +1,6 @@
---
id: stream_message_search_list_controller
sidebar_position: 5
sidebar_position: 6
title: StreamMessageSearchListController
---
@@ -20,7 +20,7 @@ First of all we should create an instance of the `StreamMessageSearchListControl
You can also add a `Filter`, a list of `SortOption`s and other pagination-related parameters.
```dart
class SearchListPage extends State<HomeScreen> {
class SearchListPageState extends State<SearchListPage> {
/// Controller used for loading more data and controlling pagination in
/// [StreamMessageSearchListController].
late final messageSearchListController = StreamMessageSearchListController(
@@ -20,7 +20,7 @@ First of all we should create an instance of the `StreamUserListController` and
You can also add a `Filter`, a list of `SortOption`s and other pagination-related parameters.
```dart
class UserListPageState extends State<HomeScreen> {
class UserListPageState extends State<UserListPage> {
/// Controller used for loading more data and controlling pagination in
/// [StreamUserListController].
late final userListController = StreamUserListController(
@@ -1,64 +0,0 @@
---
id: user_list_core
sidebar_position: 10
title: UserListCore
---
A Widget For Building A List Of Users
### Background
The UI SDK of Stream Chat supplies a `UserListView` class that builds a list of channels fetching
according to the filters and sort order given. However, in some cases, implementing novel UI is necessary
that cannot be done using the customization approaches given in the widget.
To do this, we extracted the logic required for fetching channels into a 'Core' widget - a widget that
fetches channels in the expected way via the usual params but does not supply any UI and instead
exposes builders to build the UI in situations such as loading, empty data, errors, and on data received.
### Basic Example
`UserListCore` is a simplified class that allows fetching users while
exposing UI builders.
A `UserListController` is used to load and paginate data.
```dart
class UsersListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: UsersListCore(
sort: [SortOption('last_active')],
pagination: PaginationParams(
limit: 20,
),
errorBuilder: (err) {
return Center(
child: Text('An error has occured'),
);
},
emptyBuilder: (context) {
return Center(
child: Text('Nothing here...'),
);
},
emptyBuilder: (context) {
return Center(
child: CircularProgressIndicator(),
);
},
listBuilder: (context, list) {
return UsersPage(list);
}
),
);
}
}
```
`UsersBloc` must be the ancestor of this widget. This is necessary since
`UserListCore` depends on functionality contained within `UsersBloc`.
The parameters `listBuilder`, `loadingBuilder`, `emptyBuilder` and
`errorBuilder` must all be supplied and not null.
@@ -1,37 +0,0 @@
---
id: users_bloc
sidebar_position: 9
title: UsersBloc
---
A Widget Dedicated To The Management Of A Users List With Pagination.
### Background
Most widgets in the Core SDK are focused on fetching a particular type of object from Stream Chat - channels,
messages, users, etc. The BLoC widgets bundle up the base functions used to fetch data as well as the current
data fetched by the respective functions. Furthermore, the Core widgets use this BLoC to fetch new or
existing data and build UI based on it.
All Core and UI widgets which focus on fetching a list of objects need to have their respective functions
above them in the widget tree. The UserListCore and UserListView require the UserListCore
above them in the widget hierarchy without which they will fail.
### Understanding The Widget
`UsersBloc` can be accessed at anytime by using the static `.of` method
using Flutter's `BuildContext`.
```dart
var _userBloc_ = UsersBloc.of(context);
```
The `UsersBloc` widget encapsulates common functionality related to user lists and also supplies them down the widget tree.
Here is a basic implementation of `UsersBloc`:
```dart
UsersBloc(
child: // Further Widget Tree
),
```
@@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>9.0</string>
</dict>
</plist>
@@ -139,6 +139,7 @@
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
EDC4CAB19281E65D2B18E888 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -155,7 +156,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -247,6 +248,23 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
EDC4CAB19281E65D2B18E888 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
location = "self:">
</FileRef>
</Workspace>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -205,20 +205,20 @@ class MessageScreen extends StatefulWidget {
}
class _MessageScreenState extends State<MessageScreen> {
late final TextEditingController _controller;
final StreamMessageInputController messageInputController =
StreamMessageInputController();
late final ScrollController _scrollController;
final messageListController = MessageListController();
@override
void initState() {
super.initState();
_controller = TextEditingController();
_scrollController = ScrollController();
}
@override
void dispose() {
_controller.dispose();
messageInputController.dispose();
_scrollController.dispose();
super.dispose();
}
@@ -319,7 +319,8 @@ class _MessageScreenState extends State<MessageScreen> {
children: [
Expanded(
child: TextField(
controller: _controller,
controller: messageInputController.textEditingController,
onChanged: (s) => messageInputController.text = s,
decoration: const InputDecoration(
hintText: 'Enter your message',
),
@@ -331,12 +332,13 @@ class _MessageScreenState extends State<MessageScreen> {
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () async {
if (_controller.value.text.isNotEmpty) {
if (messageInputController.message.text?.isNotEmpty ==
true) {
await channel.sendMessage(
Message(text: _controller.value.text),
messageInputController.message,
);
messageInputController.clear();
if (mounted) {
_controller.clear();
_updateList();
}
}