# MessagesList MessagesList is a component for displaying and management of messages in conversation. Its main feature is a correct and simple interaction with a list of messages and date headers, which is implemented via adapter. Also, it supports several customization levels to let you add all of the features, that wasn’t included by default.

## How to make it work To start using the component you just need to follow several steps: #### Add this widget into your xml layout: ```xml ``` #### Set an adapter Adapter’s constructor takes a sender id (a user on whose behalf messages are sending) and implementation of ImageLoader to load avatars in messages ([Click here to learn more about image loading](IMAGE_LOADER.md)). ```java MessagesListAdapter adapter = new MessagesListAdapter<>(senderId, imageLoader); messagesList.setAdapter(adapter); ``` Anyway, you can pass second parameter as `null`, and avatars will be hidden. #### Prepare your model To be able to add messages, you must implement the `IMessage` interface into your existing model and override its methods: ```java public class Message implements IMessage { /*...*/ @Override public String getId() { return id; } @Override public String getText() { return text; } @Override public Author getUser() { return author; } @Override public Date getCreatedAt() { return createdAt; } } ``` As you can see, you need also to add the Author object, which must to implement `IUser` interface: ```java public class Author implements IUser { /*...*/ @Override public String getId() { return id; } @Override public String getName() { return name; } @Override public String getAvatar() { return avatar; } } ``` It describes info of message author, such as user id (to differ the incoming and outgoing messages), user name and his/her avatar. If user doesn't have avatar image, you can return `null`, and image will not be shown in incoming message item:

That's all! This approach allows the adapter to work with your message model without type converting of any kind! ## Data management #### Adding new messages When your models are ready to be used by adapter, you can simply add them to the list. There are two ways to do it: * by adding one message to the start (bottom) of the list with scrolling (if needed). This method is best suitable for adding new messages by calling the `adapter.addToStart(IMessage message, boolean scroll)`; * by adding the previous messages of chatting history to the end (top) of the list with `adapter.addToEnd(List messages, boolean reverse)` method. Note that you can reverse the list before adding, if messages aren’t in the right order. #### Adding messages from history Ok, now we can add our messages. But how can we handle the history pagination? For this case, adapter has `OnLoadMoreListener` with `onLoadMore(int page, int totalItemsCount)` callback. It fires every time the user scrolls a list to the top. You can do your logic like this: ```java @Override public void onLoadMore(int page, int totalItemsCount) { if (totalItemsCount < this.total) { loadMessages(...); } } ``` The `page` variable contains next page number to load (which is equals to the amount of executions) and `totalItemsCount`, that contains current messages counter in the list. #### Adding image message Can modern chat exist without media message exchange? The right answer is - no, it can't. Even in the simplest apps this feature is “must have”. With ChatKit, adding this feature is easier than ever! At this moment "out of the box" library contains anly the most popular type of media messages - image messages. All you need to implement it is to mark your `Message` object with the `MessageContentType.Image` interface and override its `getImageUrl()` method: ```java public class Message implements IMessage, MessageContentType.Image { ... @Override public String getImageUrl() { return image == null ? null : image.url; } } ``` If the method returns `null`, the adapter recognizes the message as a text message and displays it in the appropriate form. If the url of the image is present, an image will be displayed using `ImageLoader`, which we passed to the adapter:

#### Date headers There’s no need to worry about date headers generation, it proceeds automatically while adding and deleting messages from the list considering all the possible cases. Also it’s fully localized, because it’s created with native java methods. #### Deleting messages To delete messages from the list, you need to call `adapter.deleteById(String id)` and `adapter.deleteByIds(String[] ids)`. Just for convenience, these methods also have overloadings for objects (delete(IMessage message), delete(List messages)), which work exactly the same. To delete all messages, just call `adapter.clear()` method. #### Updating messages If message has changed, you can update it by calling `adapter.update(IMessage message)`. Or call `adapter.update(String oldId, IMessage message)`, when identifier has changed. ## Interact with user’s actions #### Click listeners Of course, the adapter has listeners for such important actions as short and long clicks. They just returns a message object that has been pressed, with a type that is specified as the generic type of adapter: ```java public interface OnMessageClickListener { void onMessageClick(MESSAGE message); } public interface OnMessageLongClickListener { void onMessageLongClick(MESSAGE message); } ``` Also here is an ability to set listeners on separate Views in message item: ``` public void registerViewClickListener(int viewId, OnMessageViewClickListener onMessageViewClickListener) ``` #### Links highlighting In 99% of cases the user is confused, when he can not follow the link or call the phone, indicated in the message. If you think the same way, just include `textAutoLink="all"`, like in the ordinary `TextView`. Similarly, you can specify highlighting for certain types, for example, `email|phone|web`:

#### Selection mode But these listeners are not enough, if you want to create really convenient UX. Just imagine, how long users will remove, copy or share messages by doing those actions one by one. What a waste of time! For this case we have the selection mode, that turns on after user long press, and turns off, if amount of selected items is changed to zero. By default this behaviour is disabled, but you can enable it by calling `adapter.enableSelectionMode(SelectionListener selectionListener)`. The object in this parameter is the listener for selected messages count, that can be used as trigger for toolbar action mode (for example):

...and the code looks like this: ```java @Override public void onSelectionChanged(int count) { menu.findItem(R.id.action_delete).setVisible(count > 0); } ``` After messages are selected, you can get them with `adapter.getSelectedMessages()` as a list of objects. Then you can call `adapter.unselectAllItems()`, it will cancel messages’ selection, and call `SelectionListener.onSelectionChanged()` with zero as parameter to let you change the action mode. Furthermore, if you want to delete selected messages, just call `adapter.deleteSelectedMessages()` method, and it will do all the work for you. But you need to call `adapter.getSelectedMessages()` before deleting messages from the list to make delete request from your data source. If you need to disable selection mode on-the-fly, use `adapter.disableSelectionMode()`. It will cancel items’ selection and remove the listener. **N.B.! When selection mode is enabled, your custom `OnMessageLongClickListener` will be ignored due to the conflict of logic.** #### Messages copying Another convenient feature is the copying of the selected messages to the clipboard. If you enable `selection mode`, you can very easily copy the text by calling the `copySelectedMessagesText` method, which will also return this text after the call. If for some reason you needed to get the text without copying it, you can use the `getSelectedMessagesText` method. Both of these methods receive `MessagesAttribute.Formatter` as a parameter, which allows you to specify your own copying format: ```java new MessagesListAdapter.Formatter() { @Override public String format(Message message) { String createdAt = new SimpleDateFormat("MMM d, EEE 'at' h:mm a", Locale.getDefault()) .format(message.getCreatedAt()); return String.format(Locale.getDefault(), "%s: %s (%s)", message.getUser().getName(), text, createdAt); } }; ``` If `Formatter` is null, the `toString()` method will be called for each message. ## Make it look the way you want Certainly, initially defined appearance will not be suitable for everybody. And again, we have several options to solve this problem. #### Dates format for headers By default, the date in the date header is specified in the `d MMMM yyyy` format, but this does not satisfy all requirements. For example, you need to display such common options as "Today", "Yesterday" etc. To achieve this we have the `DateFormatter.Formatter` interface: ```java @Override public String format(Date date) { if (DateFormatter.isToday(date)) { return DateFormatter.format(date, DateFormatter.Template.TIME); } else if (DateFormatter.isYesterday(date)) { return getString(R.string.date_header_yesterday); …... } else { return DateFormatter.format(date, DateFormatter.Template.STRING_DAY_MONTH_YEAR); } } ``` After creating it, you can pass it to adapter: ```java messagesAdapter.setDateHeadersFormatter(formatter); ``` ...and get this kind of result:

#### Styling via attributes The simplest way to customize appearance is to use `MessageList` widget attributes. With them we can set up a default background color for incoming and outcoming messages, and even the colors for their pushed conditions and single selection. You can also change color and size for message font, send time and data in the headers. Now, by defining attributes this way: ```xml ``` ...we can get something like this:

If the shape of bubble doesn't suits you, set your own drawable through the `incomingBubbleDrawable` / `outcomingBubbleDrawable` attribute. However, color customization attributes would not work for setting their state color, you should describe this behavior using selectors. Also you can set padding for each of them by using `incomingBubblePaddingXXX` and `outcomingBubblePaddingXXX` attributes. In the same way you can set custom overlay drawable for image messages (without padding, of course). In case if you don’t like default data format, you can set it with only one line by using java date format — `dateHeaderFormat="dd.MM.yy"` You can view [all available attributes here](STYLES_ATTR.md) #### Create your own layout But what if you need not only to change the appearance of the elements, but also to change their position? It does not matter, because you can create your own layout! The only condition — IDs must match with default IDs, and widget types should not cause a ClassCastException (ie, either be the same or the subclass type). Here are IDs with types, which were defined in MessageList: * `@id/bubble` (ViewGroup) * `@id/messageText` (TextView) * `@id/messageTime` (TextView) * `@id/messageUserAvatar` (ImageView) For better understanding see how [custom layout looks like](https://github.com/stfalcon-studio/ChatKit/blob/master/sample/src/main/res/layout/item_custom_incoming_text_message.xml) After a layout was created, you need to put it into `HoldersConfig` object, which has appropriate methods for each layout files: `setIncomingLayout(int layoutRes)`, `setOutcomingLayout(int layoutRes)` `setDateHeaderLayout(int layoutRes)`. To hook up a config object, you need to transfer it to adapter through a constructor: ```java MessagesListAdapter.HoldersConfig holdersConfig = new MessagesListAdapter.HoldersConfig(); holdersConfig.setIncomingLayout(R.layout.item_custom_incoming_message); holdersConfig.setOutcomingLayout(R.layout.item_custom_outcoming_message); adapter = new MessagesListAdapter<>(senderId, holdersConfig, imageLoader); ```

Here we’ve changed a message time position, an avatar shape and a bubble shape.

#### Not enough features? Create your own holder! Sometimes a message text displaying is not enough. For example, you need to add the message processing status and reactions to the message (as in Slack). Of course, for this purpose you have to create your own layout, but you can’t do it without changing the logic of ViewHolder. `HolderConfig` can do the trick. You can transfer your own holder class with `setIncomingHolder(Class holderClass)`, `setOutcomingHolder(Class holderClass)` and `setDateHeaderHolder(Class holderClass)` methods in it. For convenience' sake, it also contains methods for simultaneous adding of layout file and holder: ```java holdersConfig.setIncoming(CustomIncomingMessageViewHolder.class, R.layout.item_custom_holder_incoming_message); holdersConfig.setOutcoming(CustomOutcomingMessageViewHolder.class, R.layout.item_custom_holder_outcoming_message); ``` To create your own holder, you need to inherit your class from `MessagesListAdapter.BaseMessageViewHolder<>`, and transfer your message class to generic type, because on the assumption of it the `onBind(IMessage message)` method will be typified. This method is similar to `onBindViewHolder()` method from `RecyclerView.Adapter` class: you can manipulate your data from it and upload images through `protected` of the `ImageLoader` field, and get `isSelected` state. However, if you’re going to add new features without rewriting behavior from a scratch, you can inherit standard realization class from a holder you need: * `IncomingMessageViewHolder` - incoming message holder; * `OutcomingMessageViewHolder` - outgoing message holder; * `DefaultDateHeaderViewHolder` - date separator holder. For example, you can add status for outgoing messages with only few lines: ```java public class CustomOutcomingMessageViewHolder extends MessagesListAdapter.OutcomingMessageViewHolder { public CustomOutcomingMessageViewHolder(View itemView, Object payload) { super(itemView, payload); } @Override public void onBind(Message message) { super.onBind(message); time.setText(message.getStatus() + " " + time.getText()); } } ```

Pay attention to outgoing message’ status and online indicator.

#### Passing custom data to your ViewHolder You can pass any data to your custom ViewHolder. To do this, firstly you need override the constructor `super(View itemView, Object payload)` (the constructor `super(View itemView)` is deprecated and will be deleted in one of the new version of library). After that you can pass data as third parameter in method `MessageHolders().setXXXConfig`. For example, let's add click listener in incoming text message on avatar click. Create interface for click callback and payload class to store it: ```java public interface OnAvatarClickListener { void onAvatarClick(); } public class Payload { public OnAvatarClickListener avatarClickListener; } ``` Then in our custom ViewHolder in method `onBind`: ```java @Override public void onBind(Message message) { super.onBind(message); ... //We can set click listener on view from payload final Payload payload = (Payload) this.payload; userAvatar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (payload != null && payload.avatarClickListener != null) { payload.avatarClickListener.onAvatarClick(); } } }); } ``` Then create Payload and set it and our ViewHolder class to MessageHolders config. ```java //We can pass any data to ViewHolder with payload CustomIncomingTextMessageViewHolder.Payload payload = new CustomIncomingTextMessageViewHolder.Payload(); //For example click listener payload.avatarClickListener = new CustomIncomingTextMessageViewHolder.OnAvatarClickListener() { @Override public void onAvatarClick() { Toast.makeText(CustomHolderMessagesActivity.this, "Text message avatar clicked", Toast.LENGTH_SHORT).show(); } }; MessageHolders holdersConfig = new MessageHolders() .setIncomingTextConfig( CustomIncomingTextMessageViewHolder.class, R.layout.item_custom_incoming_text_message, payload) ... ``` #### Custom content types We understand that ony images as media messages are often not enough. Therefore, we implemented the ability to add custom content types for displaying different types of content (geopoints, video, voice messages etc.). To do this, you just need to implement the `MessageContentType` interface. However, you do not need to override any methods at his time. After that, you need to prepare your own layout files for displaying incoming and outcoming messages of this type, as well as ViewHolders (for binding data to view). And after that you can register them in the adapter via the `MessageHolders` object. For example, let's add support of voice messages: ```java MessageHolders holders = new MessageHolders() .registerContentType( CONTENT_TYPE_VOICE, IncomingVoiceMessageViewHolder.class, R.layout.item_custom_incoming_voice_message, OutcomingVoiceMessageViewHolder.class, R.layout.item_custom_outcoming_voice_message, contentChecker); super.messagesAdapter = new MessagesListAdapter<>(super.senderId, holders, super.imageLoader); ``` We passed an identifier (which will come in handy to us a little later), markup files and ViewHolder classes for incoming and outcoming states, and `contentChecker` to the method. `ContentChecker` is an instance of the `MessageHolders.ContentChecker` class, where the content is checked for the registered type by its id: ```java @Override public boolean hasContentFor(Message message, byte type) { switch (type) { case CONTENT_TYPE_VOICE: return message.getVoice() != null && message.getVoice().getUrl() != null && !message.getVoice().getUrl().isEmpty(); } return false; } ``` If the `hasContentFor` method returns `true` for the selected type, the corresponding item for that type will be created and the `onBind(Message message)` method of the registered ViewHolder will be called (the adapter itself recognizes and processes incoming and outcoming message types). In case the method returns `false`, the adapter will poll all other registered types. If there's no content for all known types, the message will be recognized as text. As the result, well get the following: