It's my first time here but here goes:
I have a JavaFX application that changes the FXML UI labels dynamically.
I want to pass the details from the MainController
class to the interface but I get an error saying the nameLabel
is null. I investigated why and it's using the main thread. I put it in a Platform.runLater
that was recommended to me but the message remains. This in the compareFingerprint
method of MainController
.
Can anyone help?
This is how I'm loading the controller from MainApp.java
package huellatorniquete;
import com.digitalpersona.uareu.Reader;
import huellatorniquete.models.HuellaResponse;
import huellatorniquete.models.User;
import huellatorniquete.services.ApiService;
import huellatorniquete.databaseMethods.DataInserter;
import huellatorniquete.controllers.MainController;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class HuellaTorniquete extends Application {
private static final Logger LOGGER = Logger.getLogger(HuellaTorniquete.class.getName());
private static String idSucursal = "4";
private List<User> userData = new ArrayList<>();
@Override
public void start(Stage primaryStage) throws Exception {
consumegetDataUser();
Parent root = FXMLLoader.load(getClass().getResource("/HuellaTorniquete/views/mainview.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("/HuellaTorniquete/css/style.css").toExternalForm());
primaryStage.setTitle("Huella Torniquete");
primaryStage.setScene(scene);
primaryStage.setMaximized(true);
primaryStage.show();
userData = DataInserter.geth2InfoUser();
MainController.convertHuellas(userData);
System.out.println("Lista con fmd desde main: "+userData);
System.out.println("Tamaño de lista de FMD desde main: "+userData.size());
MainController mc = new MainController();
mc.compareFingerprint(userData);
}
public static void main(String[] args) {
processArgs(args);
launch(args);
}
private static void processArgs(String[] args) {
if (args.length > 0) {
try {
URI uri = new URI(args[0]);
String query = uri.getQuery();
if (query != null) {
for (String param : query.split("&")) {
String[] keyValue = param.split("=");
if (keyValue.length > 1 && "idSucursal".equals(keyValue[0])) {
idSucursal = keyValue[1];
break;
}
}
}
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Error processing arguments", ex);
}
}
}
private void consumegetDataUser(){
try {
userData = ApiService.getDataClient(idSucursal);
if (!userData.isEmpty()) {
LOGGER.log(Level.INFO, "Data client: {0}", userData);
System.out.println("tamaño: "+userData.size());
DataInserter.insertData(userData);
} else {
LOGGER.info("No se encontraron huellas");
}
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error al obtener las huellas", e);
}
}
}
MainController.java Overview
package huellatorniquete.controllers;
import com.digitalpersona.uareu.Engine;
import com.digitalpersona.uareu.Fid;
import com.digitalpersona.uareu.Fmd;
import com.digitalpersona.uareu.Reader;
import com.digitalpersona.uareu.Reader.ReaderStatus;
import com.digitalpersona.uareu.ReaderCollection;
import com.digitalpersona.uareu.UareUException;
import com.digitalpersona.uareu.UareUGlobal;
import huellatorniquete.models.User;
import huellatorniquete.services.ApiService;
import huellatorniquete.databaseMethods.DataInserter;
import java.util.ArrayList;
import java.util.List;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import com.fazecast.jSerialComm.*;
import huellatorniquete.HuellaTorniquete;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.layout.VBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.util.Duration;
import javax.swing.SwingWorker;
public class MainController {
List<User> userData = new ArrayList<>();
SerialPort seleccionado = null; // Inicializamos como null
HuellaTorniquete ht = new HuellaTorniquete();
String audioFilePath = "src/huellatorniquete/sounds/Success.mp3"; // Cambia por la ruta de tu archivo
String audioFilePath2 = "src/huellatorniquete/sounds/Error.mp3";
// Cargar el archivo de audio
Media soundSuccess = new Media(Paths.get(audioFilePath).toUri().toString());
Media soundError = new Media(Paths.get(audioFilePath2).toUri().toString());
private MediaPlayer mediaPlayerSuccess;
private MediaPlayer mediaPlayerError;
@FXML
private Label labelMotivacion;
@FXML
private Button botonClick;
String frase = "";
@FXML
private TextField buscarTextField;
@FXML
private Label nameLabel;
@FXML
private Label branchLabel;
@FXML
private Label membershipLabel;
@FXML
private Label durationLabel;
@FXML
private Label startDateLabel;
@FXML
private Label endDateLabel;
@FXML
private Label membershipStatusLabel;
@FXML
private VBox paneleft;
@FXML
private void initialize() {
buscarTextField.textProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue.matches("\\d*")) {
buscarTextField.setText(newValue.replaceAll("[^\\d]", ""));
}
});
getFrase();
labelMotivacion.setText(frase);
userData = DataInserter.geth2InfoUser();
/*System.out.println("Lista de usuarios cargada: " + userData);
System.out.println("Tamaño antes de convertirla a FMD controller: "+userData.size());*/
convertHuellas(userData);
System.out.println("Lista con fmd: "+userData);
System.out.println("Tamaño de lista FMD: "+userData.size());
getPort();
mediaPlayerSuccess = new MediaPlayer(soundSuccess);
mediaPlayerError = new MediaPlayer(soundError);
System.out.println("Ruta de éxito: " + Paths.get(audioFilePath).toUri().toString());
System.out.println("Ruta de error: " + Paths.get(audioFilePath2).toUri().toString());
private void getPort() {
SerialPort[] ports = SerialPort.getCommPorts();
seleccionado = null; // Reset seleccionado antes de buscar
for (SerialPort port : ports) {
System.out.println("names ports: " + port.getDescriptivePortName());
if (port.getDescriptivePortName().contains("USB-SERIAL")) {
seleccionado = port;
System.out.println("seleccionado puerto: " + seleccionado);
break;
}
}
if (seleccionado == null) {
System.out.println("No se encontró ningún puerto USB-SERIAL");
}
}
private void getFrase(){
try {
frase = ApiService.getFrases();
if (!frase.isBlank()) {
System.out.println("Frase obtenida: " + frase);
} else {
System.out.println("No se encontró frase");
}
} catch (Exception e) {
System.out.println("Error al obtener la frase: " + e.getMessage());
e.printStackTrace();
}
}
@FXML
private void getIdToSearch() {
System.out.println("Iniciando búsqueda...");
String numero = buscarTextField.getText();
System.out.println("Número a buscar: " + numero);
boolean encontrado = false;
if (!userData.isEmpty()) {
for (User user : userData) {
if (user.getEstafeta().equals(numero)) {
System.out.println("Usuario encontrado: " + user);
nameLabel.setText(user.getNombre());
branchLabel.setText(user.getSucursal());
membershipLabel.setText(user.getMembresia());
durationLabel.setText(user.getDuracion().toString());
startDateLabel.setText(user.getFechaInicio());
endDateLabel.setText(user.getFechaFin());
encontrado = true;
if(user.getStatus().equalsIgnoreCase("Activo") && user.getDuracion() >= 10){
mediaPlayerSuccess.stop(); // Detener si está reproduciendo
mediaPlayerSuccess.seek(Duration.ZERO); // Reiniciar al inicio
mediaPlayerSuccess.play();
membershipStatusLabel.setText("Membresia Activa");
//enviarSeñalApertura("COM7");
//enviarATodasLosPuertos();
if (seleccionado != null) {
enviarSeñalApertura(seleccionado.getSystemPortName());
enviarDato(seleccionado, 1);
System.out.println("Puerto: " + seleccionado);
} else {
System.out.println("El puerto seleccionado es nulo. Verifica la selección del puerto.");
}
paneleft.setStyle("-fx-background-color: #98ff96;");
} else if(user.getStatus().equalsIgnoreCase("Activo") && user.getDuracion() == 1) {
mediaPlayerSuccess.stop();
mediaPlayerSuccess.seek(Duration.ZERO);
mediaPlayerSuccess.play();
membershipStatusLabel.setText("Activo - Hoy finaliza la membresia");
paneleft.setStyle("-fx-background-color: yellow;");
enviarSeñalApertura(seleccionado.getSystemPortName()); // Aquí envías la señal
enviarDato(seleccionado,1);
} else if(user.getStatus().equalsIgnoreCase("Desactivado")){
mediaPlayerError.stop();
mediaPlayerError.seek(Duration.ZERO);
mediaPlayerError.play();
membershipStatusLabel.setText("Sin membresia");
paneleft.setStyle("-fx-background-color: red;");
}
break;
}
}
if (!encontrado) {
System.out.println("Usuario no encontrado");
nameLabel.setText("No encontrado");
branchLabel.setText("No encontrado");
membershipLabel.setText("No encontrado");
durationLabel.setText("No encontrado");
startDateLabel.setText("No encontrado");
endDateLabel.setText("No encontrado");
}
} else {
System.out.println("La lista de usuarios está vacía");
}
}
public static void enviarSeñalApertura(String portName) {
SerialPort port = SerialPort.getCommPort(portName);
if (port.openPort()) {
try {
port.setComPortParameters(9600, 8, 1, SerialPort.NO_PARITY);
port.setComPortTimeouts(SerialPort.TIMEOUT_WRITE_BLOCKING, 0, 0);
byte[] signal = "1".getBytes();
int bytesWritten = port.writeBytes(signal, signal.length);
if (bytesWritten == signal.length) {
System.out.println("Señal enviada exitosamente");
} else {
System.out.println("Error al enviar la señal");
}
} finally {
port.closePort();
}
} else {
System.out.println("No se pudo abrir el puerto " + portName);
}
}
public void enviarATodasLosPuertos() {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() {
SerialPort[] ports = SerialPort.getCommPorts();
System.out.println("Puertos disponibles: " + ports.length);
for (SerialPort port : ports) {
System.out.println("Intentando enviar señal al puerto: " + port.getSystemPortName());
enviarSeñalApertura(port.getSystemPortName());
}
return null;
}
};
// Ejecutar el task en un hilo separado
new Thread(task).start();
}
public static void enviarDato(SerialPort puerto, int dato) {
if (puerto == null) {
System.out.println("Error: El puerto es nulo.");
return;
}
if (!puerto.isOpen() && !puerto.openPort()) {
System.out.println("Error: No se pudo abrir el puerto " + puerto.getSystemPortName());
return;
}
try {
puerto.setComPortParameters(9600, 8, 1, SerialPort.NO_PARITY);
puerto.setComPortTimeouts(SerialPort.TIMEOUT_WRITE_BLOCKING, 0, 0);
byte[] data = {(byte) dato}; // Convertimos el entero a un byte
int bytesWritten = puerto.writeBytes(data, data.length);
if (bytesWritten == data.length) {
System.out.println("Dato enviado exitosamente: " + dato);
} else {
System.out.println("No se pudo enviar el dato completo. Bytes enviados: " + bytesWritten + " de " + data.length);
}
Thread.sleep(100); // Pequeña pausa para asegurar que el dato se envíe completamente
puerto.flushIOBuffers(); // Correcto
} catch (Exception e) {
System.out.println("Error al enviar el dato: " + e.getMessage());
e.printStackTrace();
} finally {
if (puerto.isOpen()) {
puerto.closePort();
System.out.println("Puerto cerrado.");
}
}
}
//FINGERPRINT READER
public static Reader getReaders(){
Reader reader = null;
try {
// Crear una instancia de ReaderCollection
ReaderCollection readers = UareUGlobal.GetReaderCollection();
// Actualizar la lista de lectores
readers.GetReaders();
// Asegurarse de que hay al menos un lector
if (readers.size() > 0) {
// Obtener el primer lector
System.out.println("Hay lectores disponibles");
reader = readers.get(0);
String Lector = reader.GetDescription().name;
System.out.println("El lector es: "+Lector);
reader.Open(Reader.Priority.EXCLUSIVE);
}else {
System.out.println("No se encontraron lectores");
}
}
catch (UareUException e) {
e.printStackTrace();
}
return reader;
}
public void compareFingerprint(List<User> userData) {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
try {
ReaderCollection readers = UareUGlobal.GetReaderCollection();
readers.GetReaders();
if (readers.size() > 0) {
Reader reader = readers.get(0);
reader.Open(Reader.Priority.EXCLUSIVE);
while (!isCancelled()) {
try {
Fmd capturedFmd = capturarHuella(reader);
System.out.println("capturada: " + capturedFmd);
if (capturedFmd != null) {
boolean huellaEncontrada = false;
for (User user : userData) {
if (user.getHuellaFmd() != null) {
try {
int score = UareUGlobal.GetEngine().Compare(capturedFmd, 0, user.getHuellaFmd(), 0);
int threshold = 100000;
if (score < threshold) {
System.out.println("Se encontró una huella coincidente para el usuario: " + user.getNombre());
huellaEncontrada = true;
System.out.println("Valor de Sucursal: " + user.getSucursal());
//branchLabel.setText(user.getSucursal());
// Primero actualizamos el puerto
Platform.runLater(() -> {
actualizarDatosUsuario(user);
});
//Thread.sleep(2000);
break;
}
} catch (UareUException e) {
System.err.println("Error al comparar huellas: " + e.getMessage());
}
}
}
if (!huellaEncontrada) {
System.out.println("No se encontró ninguna huella coincidente.");
Thread.sleep(1000);
}
} else {
System.out.println("No se pudo capturar la huella.");
Thread.sleep(500);
}
} catch (Exception e) {
System.err.println("Error en el ciclo de captura: " + e.getMessage());
Thread.sleep(1000);
}
}
reader.Close();
} else {
System.out.println("No se encontraron lectores de huellas dactilares.");
}
} catch (UareUException e) {
System.err.println("Error: " + e.getMessage());
}
return null;
}
};
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
// Método para actualizar los datos del usuario y gestionar el puerto
private void actualizarDatosUsuario(User user) {
try {
getPort(); // Actualiza el puerto seleccionado
if (seleccionado != null && user.getStatus().equalsIgnoreCase("Activo")) {
System.out.println("Es activo pasa");
enviarSeñalApertura(seleccionado.getSystemPortName());
enviarDato(seleccionado, 1);
System.out.println("Es activo pasa la señal");
// Actualiza los labels con la información del usuario
nameLabel.setText(user.getNombre());
nameLabel.applyCss();
nameLabel.layout();
System.out.println("Es activo pasa el nombre");
branchLabel.setText(user.getSucursal());
membershipLabel.setText(user.getMembresia());
durationLabel.setText(user.getDuracion().toString());
startDateLabel.setText(user.getFechaInicio());
endDateLabel.setText(user.getFechaFin());
membershipStatusLabel.setText("Membresía Activa");
// Cambia el color del panel si la membresía es válida
paneleft.setStyle("-fx-background-color: #98ff96;");
mediaPlayerSuccess.stop();
mediaPlayerSuccess.seek(Duration.ZERO);
mediaPlayerSuccess.play();
} else {
System.out.println("Usuario no activo o puerto no disponible.");
membershipStatusLabel.setText("Usuario desactivado");
paneleft.setStyle("-fx-background-color: red;");
mediaPlayerError.stop();
mediaPlayerError.seek(Duration.ZERO);
mediaPlayerError.play();
}
} catch (Exception e) {
System.err.println("Error al actualizar datos del usuario: " + e.getMessage());
e.printStackTrace();
}
}
public static Fmd capturarHuella(Reader reader) {
try {
Reader.CaptureResult captureResult = reader.Capture(
Fid.Format.ANSI_381_2004,
Reader.ImageProcessing.IMG_PROC_DEFAULT,
500,
-1
);
if (captureResult != null && captureResult.quality == Reader.CaptureQuality.GOOD) {
return UareUGlobal.GetEngine().CreateFmd(
captureResult.image,
Fmd.Format.ANSI_378_2004
);
}
} catch (UareUException e) {
System.err.println("Error al capturar la huella: " + e.getMessage());
}
return null;
}
public static void convertHuellas(List<User> dataUser) {
if (!dataUser.isEmpty()) {
for (User u : dataUser) {
String huella = u.getHuella();
if (!"".equals(huella)) {
try {
// Usar el método decodificarFMD para convertir la huella
Fmd fmd = decodificarFMD(huella);
if (fmd != null) {
// Establecer el Fmd en el objeto User
u.setHuella(fmd);
System.out.println("Huella a fmd jijiji: "+u.getHuellaFmd());
} else {
System.err.println("No se pudo decodificar la huella para el usuario " + u.getNombre());
}
} catch (Exception e) {
System.err.println("Error al convertir la huella para el usuario " + u.getNombre() + ": " + e.getMessage());
}
}
}
}
}
public static Fmd decodificarFMD(String base64) {
try {
// Decodificar la cadena Base64 a un arreglo de bytes
byte[] data = Base64.getDecoder().decode(base64);
// Crear un objeto FMD a partir del arreglo de bytes
Fmd fmd = UareUGlobal.GetImporter().ImportFmd(data, Fmd.Format.ANSI_378_2004, Fmd.Format.ANSI_378_2004);
return fmd;
} catch (Exception e) {
e.printStackTrace();
return null; // En caso de error, retornar null
}
}
}
Overview of mainview.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.geometry.Insets?>
<GridPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/17"
fx:controller="huellatorniquete.controllers.MainController" alignment="CENTER">
<columnConstraints>
<ColumnConstraints percentWidth="50" />
<ColumnConstraints percentWidth="50" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="ALWAYS" />
</rowConstraints>
<children>
<!-- Panel izquierdo (50% de ancho) -->
<VBox GridPane.columnIndex="0" styleClass="left-pane" spacing="20" alignment="TOP_CENTER" fx:id="paneleft">
<padding>
<Insets top="20" right="30" bottom="30" left="30" />
</padding>
<HBox spacing="10" alignment="CENTER">
<TextField fx:id="buscarTextField" promptText="Buscar" HBox.hgrow="ALWAYS" />
<Button text="Buscar" styleClass="search-button" onAction="#getIdToSearch"/>
<Button text="Actualizar" styleClass="update-button" />
</HBox>
<Region VBox.vgrow="ALWAYS" minHeight="20" maxHeight="50" />
<ImageView fx:id="userPhotoImageView" fitWidth="150" fitHeight="150" preserveRatio="true">
<Image url="/huellatorniquete/images/logo.jpeg" />
</ImageView>
<Label fx:id="idLabel" text="1" styleClass="id-label" />
<GridPane vgap="20" hgap="30" alignment="CENTER">
<columnConstraints>
<ColumnConstraints percentWidth="50" />
<ColumnConstraints percentWidth="50" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES" minHeight="30" />
<RowConstraints vgrow="SOMETIMES" minHeight="30" />
<RowConstraints vgrow="SOMETIMES" minHeight="30" />
</rowConstraints>
<VBox GridPane.columnIndex="0" GridPane.rowIndex="0" alignment="CENTER">
<Label text="Nombre:" styleClass="info-label" />
<Label fx:id="nameLabel" text="---" styleClass="info-value" />
</VBox>
<VBox GridPane.columnIndex="1" GridPane.rowIndex="0" alignment="CENTER">
<Label text="Sucursal:" styleClass="info-label" />
<Label fx:id="branchLabel" text="---" styleClass="info-value" />
</VBox>
<VBox GridPane.columnIndex="0" GridPane.rowIndex="1" alignment="CENTER">
<Label text="Membresía:" styleClass="info-label" />
<Label fx:id="membershipLabel" text="---" styleClass="info-value" />
</VBox>
<VBox GridPane.columnIndex="1" GridPane.rowIndex="1" alignment="CENTER">
<Label text="Duración:" styleClass="info-label" />
<Label fx:id="durationLabel" text="---" styleClass="info-value" />
</VBox>
<VBox GridPane.columnIndex="0" GridPane.rowIndex="2" alignment="CENTER">
<Label text="Fecha Inicio:" styleClass="info-label" />
<Label fx:id="startDateLabel" text="---" styleClass="info-value" />
</VBox>
<VBox GridPane.columnIndex="1" GridPane.rowIndex="2" alignment="CENTER">
<Label text="Fecha Fin:" styleClass="info-label" />
<Label fx:id="endDateLabel" text="---" styleClass="info-value" />
</VBox>
</GridPane>
<Label fx:id="membershipStatusLabel" text="---" styleClass="membership-status" />
</VBox>
<!-- Panel derecho (50% de ancho) -->
</children>
</GridPane>
Console:
Error al actualizar datos del usuario: Cannot invoke "javafx.scene.control.Label.setText(String)" because "this.nameLabel" is null
java.lang.NullPointerException: Cannot invoke "javafx.scene.control.Label.setText(String)" because "this.nameLabel" is null
at huellatorniquete.controllers.MainController.actualizarDatosUsuario(MainController.java:642)
at huellatorniquete.controllers.MainController$2.lambda$call$0(MainController.java:563)
at javafx.graphics@23.0.1/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:456)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
at javafx.graphics@23.0.1/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:455)
at javafx.graphics@23.0.1/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at javafx.graphics@23.0.1/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at java.base/java.lang.Thread.run(Thread.java:1583)
Your problem is that you create a MainController
instance in method start
of class HuellaTorniquete
, i.e.
MainController mc = new MainController();
You need to use the MainController
instance that is created and initialized by FXMLLoader
. You can obtain that instance via method getController
as follows (in method start
of class HuellaTorniquete
):
java.net.URL url = getClass().getResource("/HuellaTorniquete/views/mainview.fxml");
FXMLLoader loader = new FXMLLoader(url);
Parent root = loader.load();
MainController mc = loader.getController();
Do the above rather than what you are currently doing, i.e.
Parent root = FXMLLoader.load(getClass().getResource("/HuellaTorniquete/views/mainview.fxml"));