JavaFX Chat Client with File I/O Persistence
JavaFX layout containers and event handlers enable the construction of a responsive message client. The application utiilzes a BorderPane containing a TextArea for message history and a TextField for user input. Keyboard events on the input field difefrentiate between standard ENTER for regular messages and SHIFT+ENTER for echoed messages. File operations are delegated to a dedicated persistence handler.
Application Logic (ChatClientApp.java)
package app;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.time.LocalTime;
public class ChatClientApp extends Application {
private final TextArea messageDisplay = new TextArea();
private final TextField messageInput = new TextField();
private final DocumentStorageHandler storageHandler = new DocumentStorageHandler();
@Override
public void start(Stage window) {
messageDisplay.setEditable(false);
messageDisplay.setWrapText(true);
VBox centerLayout = new VBox(10,
new Label("Chat History:"),
messageDisplay,
new Label("Compose Message:"),
messageInput
);
centerLayout.setPadding(new Insets(15));
VBox.setVgrow(messageDisplay, javafx.scene.layout.Priority.ALWAYS);
Button sendBtn = new Button("Send");
Button exportBtn = new Button("Export Log");
Button importBtn = new Button("Import Log");
Button closeBtn = new Button("Close");
sendBtn.setOnAction(evt -> transmitMessage(false));
closeBtn.setOnAction(evt -> Platform.exit());
exportBtn.setOnAction(evt -> {
String timestamp = LocalTime.now().withNano(0).toString();
storageHandler.writeToFile(timestamp + "\n" + messageDisplay.getText());
});
importBtn.setOnAction(evt -> {
String fileContent = storageHandler.readFromFile();
if (fileContent != null) {
messageDisplay.clear();
messageDisplay.setText(fileContent);
}
});
messageInput.setOnKeyPressed(this::handleKeyPress);
HBox bottomControls = new HBox(15, sendBtn, exportBtn, importBtn, closeBtn);
bottomControls.setPadding(new Insets(10, 15, 15, 15));
BorderPane root = new BorderPane();
root.setCenter(centerLayout);
root.setBottom(bottomControls);
window.setScene(new Scene(root, 750, 500));
window.setTitle("Message Client");
window.show();
}
private void handleKeyPress(KeyEvent evt) {
if (evt.getCode() == KeyCode.ENTER && !messageInput.getText().trim().isEmpty()) {
boolean isShiftActive = evt.isShiftDown();
transmitMessage(isShiftActive);
}
}
private void transmitMessage(boolean isShiftHeld) {
String text = messageInput.getText();
if (isShiftHeld) {
messageDisplay.appendText("[Echo] " + text + "\n");
} else {
messageDisplay.appendText(text + "\n");
}
messageInput.clear();
}
public static void main(String[] args) {
launch(args);
}
}
File I/O Handler (DocumentStorageHandler.java)
To manage file loading and saving, the DocumentStorageHandler utilizes java.nio.file.Files. It opens a FileChooser dialog to determine the target or source file, then writes the chat log with UTF-8 encoding or reads the file contenst line by line into the display area.
package app;
import javafx.stage.FileChooser;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.List;
public class DocumentStorageHandler {
public void writeToFile(String content) {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save Chat Log");
File targetFile = fileChooser.showSaveDialog(null);
if (targetFile == null) return;
try {
Files.write(targetFile.toPath(), content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
} catch (IOException e) {
e.printStackTrace();
}
}
public String readFromFile() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open Chat Log");
File sourceFile = fileChooser.showOpenDialog(null);
if (sourceFile == null) return null;
StringBuilder builder = new StringBuilder();
try {
List<String> lines = Files.readAllLines(sourceFile.toPath(), StandardCharsets.UTF_8);
for (int i = 0; i < lines.size(); i++) {
builder.append(lines.get(i));
if (i < lines.size() - 1) {
builder.append("\n");
}
}
} catch (IOException e) {
e.printStackTrace();
}
return builder.toString();
}
}