0

I'm making an app using JavaFX and JFoenix through the scene builder in Java but I have some trouble with the initialization of my class member variables. Here is my code

public class MessageApp extends Application {

    private AnchorPane mainLayout;
    private Stage primaryStage;
    private int i = 5;

    public void init() {
        i = 0;
    }

    public void start(Stage primaryStage) {
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(PeerApp.class.getResource("PeerApp.fxml"));
            mainLayout = loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }

        this.primaryStage = primaryStage;
        this.primaryStage.setTitle("Test");
        JFXDecorator decorator = new JFXDecorator(primaryStage, mainLayout);
        decorator.setCustomMaximize(true);
        Scene scene = new Scene(decorator, 600, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    @FXML
    private void clickSendMessage() {
        System.out.println("i = " + i);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

The problem here is that even if I reset I to 0 in the init function or the start function when I click on my send button that triggers the clickSendMessage function, my println prints "i = 5". The only way to reset it is to put i = 0 in the clickSendMessage function itself.

So, in this case, it is not a big problem, but if I don't initialize a class member variable during its declaration, even if I initialize it in the init or start function, my app crashes when I click my send button because at this stage it is still considered as null but I don't know why.

Any clue of why ? Where do I have to initialize my class member variables?

EDIT:

Ok so I tried to create a dedicated controller for my view and my first problem seems to be fixed, everything is initialised as expected but now my ListView is not updated each time that I add a Contact to my list but it was the case before. I don't know what I broke by creating a separated controller, any idea ?

Here's the full code

PeerApp.java

public class PeerApp extends Application {

    private AnchorPane mainLayout;
    private Stage primaryStage;

    public void start(Stage primaryStage) {
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(PeerApp.class.getResource("PeerApp.fxml"));
            mainLayout = loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }

        this.primaryStage = primaryStage;
        this.primaryStage.setTitle("Peer Messenger");
        JFXDecorator decorator = new JFXDecorator(primaryStage, mainLayout);
        decorator.setCustomMaximize(true);
        Scene scene = new Scene(decorator, 600, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

PeerAppController.java

public class PeerAppController {

    private PeerApplication peer = new PeerApplication("1234");
    private ObservableList<Contact> contacts;
    private ChangeListener<Contact> listener;
    private int i = 5;

    @FXML
    private JFXListView<Contact> contactList;
    @FXML
    private JFXButton addContactButton;
    @FXML
    private JFXTextArea messageTextArea;
    @FXML
    private GridPane chatGridPane;
    @FXML
    private JFXButton sendButton;

    @FXML
    public void initialize() {
        initPeer();
        initRootLayout();
        contacts = FXCollections.observableArrayList(Contact.extractor());

        contactList.setItems(contacts);

        listener = new ChangeListener<Contact>() {
            @Override
            public void changed(ObservableValue<? extends Contact> observable, Contact oldValue, Contact newValue) {
                chatGridPane.getChildren().clear();
                List<Message> conversation = contacts.get(contactList.getSelectionModel().getSelectedIndex()).getConversation();
                int i;

                i = 0;
                while (conversation.size() >= i + 1) {
                    Label messageLabel = new Label(conversation.get(i).getContent());
                    chatGridPane.addRow(i, messageLabel);
                    i++;
                }
            }
        };
        contactList.getSelectionModel().selectedItemProperty().addListener(listener);

        i = 0;
    }

    private void initPeer() {
        peer.init(EncryptionType.NONE, CompressionType.NONE);
        peer.getPeerDaemonPlug().setComListener(new ICommunicationListener() {
            @Override
            public void onDataReceived(String s) {
                Label labelMessage = new Label(s);
                GridPane.setHalignment(labelMessage, HPos.RIGHT);
                chatGridPane.addRow(getRowCount(chatGridPane) + 1, labelMessage);
                contacts.get(contactList.getSelectionModel().getSelectedIndex()).getConversation().add(new Message(s, false));
            }

            @Override
            public void onStreamPacketCompleted(OutputStream outputStream) {
            }
        });
    }

    private void initRootLayout() {
        // Init the layout
        ColumnConstraints c1 = new ColumnConstraints();
        c1.setPercentWidth(100);

        sendButton = new JFXButton();
        contactList = new JFXListView<Contact>();
        chatGridPane = new GridPane();
        chatGridPane.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
        chatGridPane.getColumnConstraints().add(c1);
    }

    @FXML
    private void clickAddContact() {
        TextInputDialog dialog = new TextInputDialog("");
        dialog.setTitle("New Contact");
        dialog.setHeaderText("Add a new contact");
        dialog.setContentText("Enter the ID of your contact:");

        Optional<String> result = dialog.showAndWait();
        result.ifPresent(id -> {
            Contact c = new Contact(id);
            contacts.add(c);
            System.out.println(contactList.getItems().size());
        });
    }

    @FXML
    private void clickSendMessage() {
        // Not to be there

        String message = messageTextArea.getText();
        Label labelMessage = new Label(message);

        try {
            peer.sendRequest(sendDataToNode, new SendDataToNodeParam(contactList.getSelectionModel().getSelectedItem().toString(), message));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            i = 0;
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            i = 1;
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            i = 2;
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            i = 3;
            e.printStackTrace();
        } catch (BadPaddingException e) {
            i = 4;
            e.printStackTrace();
        } catch (IOException e) {
            i = 5;
            e.printStackTrace();
        }

        messageTextArea.setText("");
        GridPane.setHalignment(labelMessage, HPos.LEFT);
        chatGridPane.addRow(getRowCount(chatGridPane) + 1, labelMessage);
        contacts.get(contactList.getSelectionModel().getSelectedIndex()).getConversation().add(new Message(message, true));
        System.out.println("i = " + i);
    }

    private int getRowCount(GridPane pane) {
        int numRows = pane.getRowConstraints().size();
        for (int i = 0; i < pane.getChildren().size(); i++) {
            Node child = pane.getChildren().get(i);
            if (child.isManaged()) {
                Integer rowIndex = GridPane.getRowIndex(child);
                if(rowIndex != null){
                    numRows = Math.max(numRows,rowIndex+1);
                }
            }
        }
        return numRows;
    }

}

Contact.java

public class Contact {
    private StringProperty id;
    private List<Message> conversation;

    public Contact(String id) {
        this.id = new SimpleStringProperty(id);
        this.conversation = FXCollections.observableArrayList();
    }

    public static Callback<Contact, Observable[]> extractor() {
        return new Callback<Contact, Observable[]>() {
            @Override
            public Observable[] call(Contact param) {
                return new Observable[]{param.id};
            }
        };
    }

    @Override
    public String toString() {
        return String.format("%s", id.get());
    }

    public StringProperty getId() {
        return this.id;
    }

    public void setId(StringProperty id) {
        this.id = id;
    }

    public List<Message> getConversation() {
        return this.conversation;
    }
}

PeerApp.fxml

<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="PeerAppController">
    <children>
        <SplitPane dividerPositions="0.29797979797979796" layoutX="200.0" layoutY="120.0" prefHeight="400.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
            <items>
                <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
                    <children>
                  <JFXListView fx:id="contactList" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
                        <JFXButton fx:id="addContactButton" buttonType="RAISED" layoutX="133.0" layoutY="357.0" onAction="#clickAddContact" ripplerFill="#10ae07" text="+" textAlignment="CENTER" AnchorPane.bottomAnchor="14.0" AnchorPane.rightAnchor="14.0" />
                    </children>
                </AnchorPane>
                <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
                    <children>
                        <JFXTextArea fx:id="messageTextArea" focusColor="#40a85c" layoutX="15.0" layoutY="237.0" maxHeight="50.0" minHeight="10.0" prefHeight="50.0" prefWidth="339.0" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="72.0" />
                        <ScrollPane layoutX="12.0" layoutY="12.0" prefHeight="276.0" prefWidth="394.0" AnchorPane.bottomAnchor="65.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0">
                     <content>
                        <GridPane fx:id="chatGridPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308">
                          <columnConstraints>
                            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                          </columnConstraints>
                          <rowConstraints>
                            <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                            <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                            <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                          </rowConstraints>
                        </GridPane>
                     </content></ScrollPane>
                  <JFXButton fx:id="sendButton" layoutX="350.0" layoutY="338.0" onAction="#clickSendMessage" prefHeight="50.0" prefWidth="64.0" ripplerFill="#18cd00" text="SEND" textAlignment="CENTER" textFill="#0dae04" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="344.0" AnchorPane.rightAnchor="5.0" />
                    </children>
                </AnchorPane>
            </items>
        </SplitPane>
    </children>
</AnchorPane>
YoanGJ
  • 479
  • 5
  • 25
  • If you do not initialize variables they have a default value. For any `Object`s it is `null`, for `int` it is 0. And you never call `init()` so why do you assume i to be 0? – Stefan Warminski Jun 21 '17 at 09:01
  • IMHO I initialize as many fields as possible directly at declaration. If you expect `i` to be = 0, declare it as `private int i = 0;`. So you can reduce your code by three lines of your method `init` – Stefan Warminski Jun 21 '17 at 09:03
  • @StefanWarminski in JavaFX I it is call automatically just as the start function, and the result is the same if I do I = 0 in the start function. – YoanGJ Jun 21 '17 at 09:20
  • @StefanWarminski I know but sometimes I can't, some complex objects have to be initialized through there own init method – YoanGJ Jun 21 '17 at 09:21
  • 2
    Please have a look at the following questions: [Can application class be the controller class](https://stackoverflow.com/questions/33303167/javafx-can-application-class-be-the-controller-class), [Constructor vs. initialize](https://stackoverflow.com/questions/34785417/javafx-fxml-controller-constructor-vs-initialize-method/) - they should help clear out some things. – Itai Jun 21 '17 at 10:20
  • 1
    `init()` is only called on the instance created by `Application.launch()`: it is not called on the controller. `clickSendMessage()` is invoked on the controller, hence `i` is `0`. **Never** use the `Application` class as the controller class. – James_D Jun 21 '17 at 12:00

2 Answers2

3

In my opinion it is bad practice to mix the class which responsability is to start your application and load your .fxml file(s) with the class which is your Controller, that cares about controlling your UI, for example in your case you have the clickSendMessage() method who have to be in a controller class.

So i suggest that you write a separate class that is your Controller , you attach your controller to the .fxml file and then you can handle much easier all interactions between UI and the logic part. I can show you a simple example:

package stackoverflow;

import javafx.fxml.Initializable;

import java.net.URL;
import java.util.ResourceBundle;

public class TestController implements Initializable {

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // here are  all stuffs initialized
    }
}

then the .fxml file looks like this:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="500.0" prefWidth="700.0" xmlns="http://javafx.com/javafx/8.0.91" xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="stackoverflow.TestController">
// here you can fill it with content
</AnchorPane>

So now you have separate a controller and an .fxml so you can load this .fxml as you did in the main class then you can add all stuffs in your controller that you want to control your application including that method. This is a so simple and clear way to build a javafx application. I think this is understandable but if i missed something feel free to ask me.

Sunflame
  • 2,993
  • 4
  • 24
  • 48
  • 1
    Since Java 8 there is no real need to implement `Initializable` - you can have a no-arg method named `initialize` which is either publicly-accessible, or else private/protected but has the `@FXML` annotation. See [the documentation](https://docs.oracle.com/javase/8/javafx/api/javafx/fxml/Initializable.html). – Itai Jun 21 '17 at 10:55
  • Yes, you are right, but if you use `Initializable`, and you see there the method initialize with `@Override` you can identify much easier where are the data initialized. If you put there just a method without any additional information then it can be considered a like a simple method. That is also a bad practice to don't use the `@Override` annotation when something is overriden. – Sunflame Jun 21 '17 at 11:21
  • I didn't suggest not annotating an overridden method with `@Override`. I guess it comes down to style, but I personally prefer the newer approach as I never use the arguments of the `initialize` method defined in `Initializable`, and the `@FXML` annotation is enough for me to signify it is an infrastructure method (same argument can be made for event handlers, which may appear to be methods which are never called, if it wasn't for the `@FXML` annotation). Neither approach is wrong (I think), I was merely bringing this issue to attention. – Itai Jun 21 '17 at 11:25
  • I agree, everyone has preferences, and each developer will use the best way that is clear for he/she. For example I don't prefer using `@FXML` for methods, but only for fields. So I think this answer solves the question, and I am aware of that there are a few ways to solve it, i just wrote that I prefer the most. – Sunflame Jun 21 '17 at 11:36
  • Ok so I've made some changes to my code as suggested, and it seems to work, thank's a lot ! But now I got a new problem as explained in my edit with some more code... PS: sorry for the waiting, I was not able to reach my laptop – YoanGJ Jun 26 '17 at 17:03
  • I had a look on your code and I didnt find any bug, maybe if i copy all of that code and try with debug then i can find it. Here is my simple example that works fine for me: https://pastebin.com/X5wnrCXJ – Sunflame Jun 26 '17 at 19:33
  • I see now I cannot test it as you have because I dont have the `JFX` things, and I don't even know what are them maybe from javafx-2(?), but I don't think this is the problem. – Sunflame Jun 26 '17 at 19:39
  • 1
    I think I got your problem, in the `initRootLayout` method you can delete those 3 like where you instantiate the components, you dont have ti instantiate them if you annotate them with `@FXML` so remove these lines: `sendButton = new JFXButton(); contactList = new JFXListView(); chatGridPane = new GridPane();` – Sunflame Jun 26 '17 at 19:48
  • @Sunflame Exact ! Thank's a lot for your time guys ! And btw JFX things are from JFoenix, a JavaFX material design library. – YoanGJ Jun 27 '17 at 03:19
1

my friend, do you set your PeerApp.fxml's controller to MessageApp too?

if not, when you click send button, it won't trigger the clickSendMessage().

but if you set your PeerApp.fxml's controller to MessageApp, javafx automatically create another instance of MessageApp, so if you click send button, it's actually trigger another instance of MessageApp's clickSendMessage(), and it prints its i variable.

or can you post your PeerApp.fxml code to facilitate us to check?

init() method is override from Application class, it will run automatically when we start our javafx application.

yab
  • 267
  • 3
  • 13