Lesson 24 - Java Chat - Client - Finishing Part 2
In the last lesson, Java Chat - Client - Finishing Part 1, we started working on the GUI of our chat client in Java.
In today's last Java tutorial, we're going to complete the implementation of the chat client and finish the entire course on creating a Java server.
Handling Events to View the Conversation
We'll start by viewing the conversation with the selected user.
Double-clicking a user in the ListView
will either show a tab with
a new conversation or switch to an existing conversation. In the
MainController
class, we'll create a new constant with an anonymous
function handling double-clicks in the ListView
:
private final EventHandler <<? super MouseEvent> listContactsClick = event -> { final int clickCount = event.getClickCount(); if (clickCount != 2) { return; } final ChatContact contact = lvContactList.getSelectionModel().getSelectedItem(); if (contact == null) { return; } showConversation(contact); };
At the beginning we check whether we really clicked twice. If not, we don't
do anything. Next, we check that we clicked on a contact, not an empty space. If
we clicked a contact, we show the conversation using the
showConversation()
method.
Showing the Selected Conversation
We'll show the selected conversation using the
showConversation()
method:
private void showConversation(ChatContact contact) { final Optional <ChatTab> optionalTab = paneChatContainer.getTabs() .stream() .filter(tab -> tab.getUserData() == contact) .map(tab -> (ChatTab) tab) .findFirst(); if (optionalTab.isPresent()) { paneChatContainer.getSelectionModel().select(optionalTab.get()); } else { paneChatContainer.getTabs().add(makeNewTab(contact)); } }
The method accepts, as a parameter, a ChatContact
class instance
representing the contact we want to display the conversation for. First, we
check whether the conversation is already present in the TabPane
.
If so, we simply switch to it using the select()
method. Otherwise,
we create a new tab with the conversation and insert it between the other
conversations.
Creating a New Conversation
We'll create a new conversation in a separate makeNewTab()
method:
private ChatTab makeNewTab(ChatContact chatContact) { final ChatTab chatTab = new ChatTab(chatContact); chatTab.setUserData(chatContact); return chatTab; }
First, we create a new ChatTab
class instance. Using the
setUserData()
method we set the user data. We use this data in the
previous code, where we filter the Tab
according to the given
contact.
Registering a Contact List Handler
Finally, in the initialize()
method, we'll register a click
handler for the ListView
:
lvContactList.setOnMouseClicked(this.listContactsClick);
Testing
Now when we start the server, start the client, and connect to the server, a
single contact is displayed in the ListView
- Us. We can
double-click that contact and a new conversation tab will appear on the right
side of the window:
Sending a Message
In the following chapter we'll implement sending a message. We'll only be able to write in the text field if a conversation is selected. The submit button will only be accessible if the text field contains some text. Let's start by implementing these conditions.
Disabling the Components
We'll bind the disable property of the components in the
initialize()
method. We'll do the button first and bind its
disableProperty()
to textProperty().isEmpty()
:
btnSend.disableProperty().bind(txtMessage.textProperty().isEmpty());
The text box binding is similar:
txtMessage.disableProperty().bind(paneChatContainer.getSelectionModel().selectedItemProperty().isNull());
Submit Message Button Handler
The message will be sent when we press the send button. We already have all the necessary logic implemented, lety's just call it:
private void handleSendMessage(ActionEvent actionEvent) { final ChatTab tab = (ChatTab) paneChatContainer.getSelectionModel().getSelectedItem(); if (tab == null) { return; } final String id = ((ChatContact) tab.getUserData()).getId(); final String message = txtMessage.getText(); chatService.sendMessage(id, message); txtMessage.clear(); txtMessage.requestFocus(); }
First, we test whether a Tab
open. If not, the method does
nothing. The contact ID is obtained from the user data from the Tab
and the message contents from the text field. Finally, we call our service to
take care of sending the message. This is followed by clearing the text field
and requesting focus so we can type again.
Displaying Messages
Received messages already have all the logic implemented so they are automatically displayed when they are sent. If the conversation isn't visible, the number of unread messages will appear in the contact list next to the contact name.
Typing Information
Now we'll implement a function that will inform the user whether or not the
client at the other end is typing. In the MainController
class,
we'll create a new class constant with an anonymous function that will be called
when the contents of the message text field is changed:
private ChangeListener <<? super String> messageContentListener = (observable, oldValue, newValue) -> { final ChatTab tab = (ChatTab) paneChatContainer.getSelectionModel().getSelectedItem(); if (tab == null) { return; } final String id = ((ChatContact) tab.getUserData()).getId(); chatService.notifyTyping(id, !newValue.isEmpty()); };
It's important to call the notifyTyping()
method on the last
line of the function to inform whether the user is typing or not.
Tab Switching Handling
It's nice that we informed the user that we're typing, but what if we switch to another conversation as we type because we realize that the message is for someone else. So we'll create a listener responding to the conversation change. We'll create another class constant with an anonymous function:
private ChangeListener <<? super Tab> tabChangeListener = (observable, oldValue, newValue) -> { if (oldValue != null) { final ChatTab oldTab = (ChatTab) oldValue; final String id = ((ChatContact) oldTab.getUserData()).getId(); chatService.notifyTyping(id, false); } if (newValue != null) { if (!txtMessage.getText().isEmpty()) { final ChatTab newTab = (ChatTab) newValue; final String id = ((ChatContact) newTab.getUserData()).getId(); chatService.notifyTyping(id, true); } } else { txtMessage.clear(); } };
The function receives oldValue
(the old conversations) and
newValue
(new conversation) as parameters. If we're really
switching from the old conversation, oldValue
won't be
null
and we inform the user in the old conversation that we're no
longer typing a message for him. If we switched to a new conversation and the
message text field contains text, we inform the new user that we're probably
typing a message for him.
Registering the Listener
Finally, we'll register the listener for changing the conversation in the
initialize()
method:
paneChatContainer.getSelectionModel().selectedItemProperty().addListener(this.tabChangeListener);
Testing the Typing Indicator
Now when we open a conversation and start typing, the Tab icon will turn into a very simple animation.
Result
Below you can see the resulting chat client. There's the number of unread messages displayed next to contacts. In addition, the typing indicator appears in the tab. If the conversation is longer, a scrollbar appears:
The End
By this we've finished the whole course on creating a simple Java chat client. Of course, the client deserves a number of improvements, which I leave up to you, the readers. I hope you enjoyed the series and if you have any comments, there's a comment section below.