Search code examples
clojureclojure-java-interop

Clojure is not generating a class


I created a new Clojure app with clojure -Tclj-new app :name myname/myapp and added the javafx deps to deps.edn

org.openjfx/javafx-controls {:mvn/version "17.0.2"}
org.openjfx/javafx-base {:mvn/version "17.0.2"}
org.openjfx/javafx-graphics {:mvn/version "17.0.2"}
org.openjfx/javafx-media {:mvn/version "17.0.2"}
org.openjfx/javafx-web {:mvn/version "17.0.2"}

I copied the initialization of a UI using JavaFX, extending the class Application.

(ns myname.myapp
  (:import
   (javafx.application Application)
   (javafx.scene Scene)
   (javafx.scene.layout VBox)
   (javafx.scene.control Label)
   (javafx.stage Stage))
  (:gen-class :extends javafx.application.Application))

(def app (atom nil))

(defn -start [this stage]
  (let [root (VBox.)]
    (reset! app this)
    (.setTitle stage "Hello From Clojure!")
    (.add (.getChildren root) (Label. "Hello World"))
    (.show stage)))

(defn -main [& args]
  (Application/launch myname.myapp args))

I cannot run the project. With clj -M:run-m it returns

Syntax error (ClassNotFoundException) compiling at (myname/myapp.clj:20:3). myname.myapp

It seems that Clojure is not generating any class since no folder target is created. I'm using Clojure 1.11.1 on macOS 12.4


Solution

  • I think you really have to create uberjar- you can also use this command:

    lein uberjar

    and then use java -jar your-app-standalone.jar in the folder with the created jar (probably target/uberjar/).

    Also, if you want to display your Label, you have to create Scene and add that label there. This works:

    (ns myapp.core
      (:import
        (javafx.application Application)
        (javafx.scene Scene)
        (javafx.scene.layout VBox)
        (javafx.scene.control Label))
      (:gen-class :extends javafx.application.Application))
    
    (def app (atom nil))
    
    (defn -start [this stage]
      (let [vbox (VBox.)
            scene (Scene. vbox 200 200)]
        (reset! app this)
        (.add (.getChildren vbox) (Label. "Hello World"))
        (doto stage
          (.setTitle "Hello From Clojure!")
          (.setScene scene)
          (.show))))
    
    (defn -main [& args]
      (Application/launch myapp.core (into-array String [])))
    

    Note that there's a wrapper over JavaFX called Cljfx, which would allow you to -among other things- test your project in REPL, without creating any uberjars.

    Minimal example:

    Dependency: [cljfx "1.7.21"]

    Core:

    (ns myapp.core
      (:require [cljfx.api :as fx])
      (:gen-class))
    
    (fx/on-fx-thread
      (fx/create-component
        {:fx/type :stage
         :showing true
         :title "Cljfx example"
         :width 300
         :height 100
         :scene {:fx/type :scene
                 :root {:fx/type :v-box
                        :alignment :center
                        :children [{:fx/type :label
                                    :text "Hello world"}]}}}))