Skip to content

How to change this JavaFX into using a ControllerFactory

I have looked into using ControllerFactory a lot, to allow this code to be instantiated from a database, and have cross-controller compatibility. But with my original setup different from others I found online, I found it extremely hard to follow along, and use what would fit into my program from theirs. Any advice on where to start?

Current Controller creation –

        // get Main Class package name to get correct files path
        String pathRef = mainRef.getClass().getPackage().getName();
        // set FXRouter current route reference
        currentRoute = route;
        // create correct file path.  "/" doesn't affect any OS
        String scenePath = "/" + pathRef + "/" + route.scenePath;




        // Creates controller for route
        Controller_Factory cf = new Controller_Factory();
        Object controller  = cf.CreateController(route.scenePath);


        FXMLLoader loader = new FXMLLoader(controller.getClass().getResource(scenePath));
        loader.setController(controller);


        Parent root = loader.load();



        // set window title from route settings or default setting
        window.setTitle(route.windowTitle);
        // set new route scene
        window.setScene(new Scene(root, route.sceneWidth, route.sceneHeight));
        // show the window
        window.show();


    }

Controller Example-

public class BuyController extends Controller {

    @FXML
    public Button CloseAppButton;
    @FXML public Button SwitchToProfileButton;
    @FXML public Button SwitchToSellButton;
    @FXML public Button SwitchToBuyButton;
    @FXML public Button SwitchToMainButton;

@FXML public TextField BuyText;


String AmountBought;

    public void initialize (URL location, ResourceBundle resources){
        CloseAppButton.setPrefHeight(30);
        CloseAppButton.setPrefWidth(56);

        SwitchToBuyButton.setPrefHeight(30);
        SwitchToBuyButton.setPrefWidth(56);

        SwitchToMainButton.setPrefHeight(30);
        SwitchToMainButton.setPrefWidth(56);

        SwitchToSellButton.setPrefHeight(30);
        SwitchToSellButton.setPrefWidth(56);

        SwitchToProfileButton.setPrefHeight(30);
        SwitchToProfileButton.setPrefWidth(56);
    }
    public void OnBuyButton (ActionEvent event) {
AmountBought = BuyText.getText();
System.out.println("You have bought " + AmountBought + " of crypto");
BuyText.clear();
    }


    @Override
    public void initilize(URL url, ResourceBundle rb) {

    }


}

Current Controller_Factory-

public class Controller_Factory {

    private static final Controller_Factory instance = new Controller_Factory();

public static Controller_Factory getInstance() {
    return instance;
}



public Object CreateController (String routeScenePath) throws IllegalArgumentException, IOException {



    Object controller = null;



    switch (routeScenePath) {
        case  "Buy.fxml":
            controller = new BuyController();
            break;
        case "Error.fxml":
            controller = new ErrorController();
            break;
        case "Home.fxml":
            controller = new HomeController();
            break;
        case "Profile.fxml":
            controller = new ProfileController();
            break;
        case "Sell.fxml":
            controller = new SellController();
            break;
        default:

    }
   System.out.println(routeScenePath);
    return controller;
}


}

How would I pass this info with the said controller? (This is not real code I have, but an example of configuration JSON I want to pass with the controller.)

  "HomePage": {
    "ValidPages": [
      "BuyPage",
      "SellPage"
    ],
    "InternalID": "HP"
  },
  "BuyPage": {
    "ValidPages": [
      "HomePage"
    ],
    "InternalID": "BP",
    "Cryptos": [
      "BTC",
      "LTC"
    ]

Answer

The controller factory is simply a Callback<Class<?>, Object> whose call(Class<?> type) function takes the class defined in the fx:controller attribute in the FXML file and returns the object to be used as the controller. This is invoked by the FXMLLoader at the time the FXML is loaded.

I think your question is asking if you can use a controller factory to automatically populate controllers with data that’s stored in JSON, which will be read at runtime.

You can do something like this:

public class NavigationInfo {

    private final Map<String, PageNavigationInfo> pageInfoPerPage ;

    public NavigationInfo(Map<String, PageNavigationInfo pageInfoPerPage) {
        this.pageInfoPerPage = pageInfoPerPage;
    }

    public PageNavigationInfo getInfoForPage(String page) {
        return pageInfoPerPage.get(page);
    }
}
public class PageNavigationInfo {

    private final String internalID ;

    private final List<String> validPages ;

    private final List<String> cryptos ;

    // .... etc
}
public class NavigationControllerFactory implements Callback<Class<?>, Object> {

    private final NavigationInfo navigationInfo ;

    public NavigationControllerFactory() {
        // read and parse JSON and create NavigationInfo instance
    }

    @Override
    public Object call(Class<?> type) {
        try {
            for (Constructor<?> c : type.getConstructors()) {
                if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(NavigationInfo.class)) {
                    return c.newInstance(navigationInfo);
                }
            }
            // no suitable constructor, just use default constructor as fallabck
            return type.getConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

Now just define the fx:controller attribute in each FXML in the usual way. E.g. for Buy.fxml do

<BorderPane ... fx:controller="com.yourcompany.yourproject.BuyController">

    <!-- ... -->
</BorderPane>

Then

public class BuyController {

    private final PageNavigationInfo navInfo ;

    public BuyController(NavigationInfo navigationInfo) {
        this.navInfo = navigationInfo.getInfoForPage("BuyPage");
    }

    @FXML
    private void initialize() {
        // do whatever you need with navInfo
    }
}