The Griffon team just posted the latest release of Griffon 2 (released as 2.0.0.BETA3). In this post I'm going to showcase the new capabilities of the framework, specifically targeting JavaFX as main UI toolkit and Java as the main programming language.
First, we must ensure we have the right tools and environment in order to get started with Griffon 2.0. The following are the minimum requirements
- JDK8
- Lazybones
- Gradle 2.0
Fortunately you can use GVM to install the last two items. Open a terminal prompt and issue the following commands
$ curl -s get.gvmtool.net | bash
$ gvm install lazybones
$ gvm install gradle
Now for a bit of configuration. We have to instruct Lazybones from where the Griffon projects can be downloaded. This requires editing the ~/.lazybones/config.groovy
file (create it if it doesn't exist) and pasting the following
We're ready to get started. We'll use Lazybones to initialize a project that conforms to the Griffon conventions. If you invoke lazybones list
you'll be able to see all available templates, for example
Alright so griffon-javafx-java
is the one we're after. Issue the create
command with the following options; you'll be prompted to pick some values, let all defaults except for the final question: className
Now change into the generated directory (sample-javafx-java
). The directory structure should look very similar to this one
This last snippet shows the standard structure of a Griffon project. It also shows all the files we're going to touch in this post. There are a few other more files, such as gradle helper scripts, icons, even a Maven build file in case you're curious. Our goal is to build an application that prompts for a name; calls a text transformation service and outputs the transformed text back to the UI. The application should be aware of different locales too, so when run using the EN
locale results in
where as running in the ES
locale results in
Open up the project using your favorite IDE or text editor. Griffon makes little assumptions about your development IDE however it can configure sensible defaults for both Intellij IDEA and Eclipse. NetBeans has great Gradle support so it shouldn't be any different. Let's familiarize ourselves with the build file first
Here's what's important about this file
- There are some useful Gradle plugins configured for this build, the most important being
gradle-griffon-plugin
. - There's a
griffon
extension block with some default settings. - Only a few dependencies are declared. Interestingly the Griffon core dependencies are missing (but are they?).
Griffon projects follow a common structure. This assures that once you've worked with one Griffon application you can find your place around other Griffon projects. So what about those missing core dependencies? Well it turns out that the gradle-griffon-plugin
performs two responsibilities, which are
- configure the build according to the Griffon conventions (sourceSets, processing resources).
- configure default Griffon dependencies according to
griffon.version
andgriffon.toolkit
.
The second responsibility is the one taking care of resolving the missing core dependencies. Don't believe me? Have a look at the dependencies report (invoke gradle dependencies
) and you'll see the following for
Ah, there we see griffon-core
, griffon-javafx
and griffon-core-test
. The plugin creates two additional compile only configurations. You can think of these configurations as the equivalents of the provided
scope found in Maven projects.
These additional dependencies are required to collect and generate compile-time metadata on each artifact. Now that we know where the dependencies are and how they get mixed in we're ready to start looking at some code. Each application requires a basic configuration file that instructs the application about the possible MVC groups that can be instantiated. An MVC group is nothing more that the logical grouping of Model, View and Controller artifacts. There are several combinations that can be applied to MVC groups, the default project template defines the most simple and straight forward one, as witnessed in griffon-app/conf/Config.java
Here we see one of those little gems found in the Griffon runtime: utility methods to create Maps and Lists using build methods, closely resembling native collection syntax found in other languages. But of course you can create Maps and Lists in the old fashioned way, these methods are just syntactic sugar. By the way, it's possible to supply the configuration using a properties files instead of a Java class in case you're wondering. The next pice of code we'll look at defines the starting point of our application. Remember back in build.gradle
there was a value specified for the javafx.mainClass
property? It points to the class that will be used to launch the application. This class is located in src/main/java/org/example/Launcher.java
The class JavaFXGriffonApplication
follows the standard bootstrap mechanism of a Griffon application. The Launcher
class can be used to setup and configure the running environment before the Griffon application is launched, for example, setting up the logger or additional JVM settings.
Moving on. The Model
is where the fun begins. Models in Griffon can be simple as data holders, or presentation models if you will. They are not domain classes like the ones you find in Grails. Recall that the application requires an input from the user and will output the transformed text. We'll define two observable properties in our Model that conform to this requirement. The file griffon-app/models/org/example/SampleModel.java
should contain the following definition
As you can appreciate there are 2 Javafx properties and nothing more. Well, almost. Pay close attention to the @ArtifactProviderFor
annotation. This is used as a hint for the compile-time metadata generation process (using the standard APT: Annotation Processing tool). Oh and we can see that the Model
participates in dependency injection thanks to JSR-330 annotations (@Inject
). The next artifact we'll look at is the Controller
, located at griffon-app/controllers/org/example/SampleController.java
This class follows what we've learned from the Model
: it uses the @ArtifactProviderFor
to generate metadata and can also participate in dependency injection using @Inject
. Notice that it has a public method named sayHello
; this name is important as it will be used via convention to instantiate a JavaFX action: a helper class that contains metadata about behavior, very much akin to Swing's Action
class. The controller actions makes use of lambda expressions because the project relies on JDK8, great! Two more things, we can appreciate the call to the transformation service (SampleService
) and that the update of the model happens inside the UI thread. What's this? It turns out that Griffon is aware of the threading semantics required by the UI toolkit, which is that long running computations must be executed outside of the UI framework, view updates should happen inside. And so Griffon automatically executes controller actions outside of the UI thread (this behavior is configurable). Imagine that there's an implementation of the SampleService.sayHello
method that opened a network connection or issue a database call. That would be a big no-no if the action were to be executed inside the UI thread by default. Luckily Griffon has your back and caught that bullet for you.
Next in the puzzle is the SampleService
class located at griffon-app/services/org/example/SampleService.java
Yet another artifact (thus @ArtifactProviderFor
is applied to the class definition). Here we can appreciate a trivial implementation of the service method used in the controller
. You may be thinking, if it's so trivial why wouldn't we put the code inside the action and be done with it? And you'll be right, for this trivial case, but for more complex cases you'd want to offload controllers from performing too much logic. Besides, services are supposed to be all about logic and no interaction with views (which controller can have). The service attempts resolving messages from a resource bundle. This application has 2 resource bundles, one for EN
(the default locale in my setup) and another one for ES
. The files are located at griffon-app/i18n/messages.properties
and griffon-app/i18n/messages_es.properties
respectively
Ah, remember we said something about the sayHello
action name? Here we see one of the benefits of constructing a JavaFX action abstraction. All values of said action can be configured via i18n aware message bundles, like these ones; we only need to follow a naming convention to locate the target and specify the properties. In this case we're simply changing the display name of the action; this name will be used by the button in the view. Speaking of which, let's look at it. JavaFX applications usually rely on FXML files to define how the views look (and behave if fx:controller
is specified). Griffon views are no exception. You can use FXML or the JavaFX API to programmatically create Views, it's up to you to decide what option suits your needs best. This application uses a simple FXML, located at griffon-app/resources/org/example/sample.fxml
There are 3 child components. Make a mental note of the fx:id
attributes of each one of them, they will be used to inject the JavaFX controls into the View
, locate at griffon-app/views/org/example/SampleView.java
Funny thing, is that the View
instance is the one that's actually set as the controller on the loaded FXML, even though a different setting was specified in that file. Why you ask? To keep things simple of course. View components belong to the View
, not the Controller
; thus we make us of the @FXML
annotation on the view fields, their names matching the ids found in the FXML. The properties we defined in the Model
are bound to their respective UI components. The configuration of the button's properties is performed using the JavaFX action defined in the controller.
We're almost done, however you can run the application for the first time now. Execute the following command in a command prompt (or click the run action on your IDE)
$ gradle run
A window very much like the first screenshot will pop up after a few seconds. Great! So what about changing the locale? Execute the following command
$ gradle -Plocale=es run
A few more seconds and a window pops up. But what's this? It still uses the EN
locale Well this is because we never instructed the application to handle the incoming arguments. We can perform this feat by tweaking
Launcher.java
, by setting Locale.default
to the right value. Or we could use a lifecycle event too. Let's go with the second choice as it showcases two additional features found in Griffon.
Applications can trigger events at specific times: when they switch lifecycle phases, when an MVC group is instantiated and destroyed, when a window becomes visible or hidden, etc. You can think of lifecycle phases as logic steps that an application must follow in order to work properly. We'd like to hook into the earliest phase possible, so that the whole application benefits from the update Locale value. We can do this by hooking into the BootstrapStart
event, performed during the Initialize
phase of the application. We'll create a class in src/main/java/org/example/ApplicationEventHandler.java
The code grabs the value of the startupArgs
that were passed to the application and sets a property on it. This property understands the syntax for locale literals. The final step will be to indicate the application tat we'd like to use this event handler. This is achieve by defining a Module
. Modules are responsible for defining injection bindings. Module bindings are quite similar to the ones found in Google Guice however this API has no direct dependencies to Guice. You do need a JSR-330 compliant container for which Guice is used as default. That's just a coincidence
The module we needs is defined in src/main/java/org/example/ApplicationModule.java
Here we found another compile-time annotation (@ServiceProviderFor
), again keeping the metadata files up to date whenever the code is compiled. Now that we have all things wired up we can again invoke the last command with the ES
locale as argument
$ gradle -Plocale=es run
The window pops up again. Notice any difference now? Of course, the Spanish locale is now in effect and all texts have changed. Sweet!
Remember I mentioned the Lazybones project template create a Maven build file too? Why don't you give a try to
$ mvn compile exec:java
A window will popup after a flurry of text scrolls on your command prompt (Maven can be quite verbose by default). You may use the alternate command too
$ mvn compile jfx:run
Unfortunately there's no Maven specific plugin (yet) so the resulting pom.xml
can be very verbose. The Griffon team is looking into this matter and may release a plugin in the future.
The Griffon team welcomes any feedback you may have regarding the state of the latest 2.0.0.BETA3 release. We're now in bugfix mode only, getting ready for 2.0.0.Final to come out very soon. So let us know what you think, drop us a line at the mailing lists, report a bug in our tracker, send us a pull request, tweet us at @theaviary.
And there you have it. Writing JavaFX applications with Griffon and Java is not a difficult task. But if you think there should be a way to write less code, a groovier way, then you're on the right track. I'll show the Groovy version of this application in another post.
Keep on Groovying!
PS: all code shown in this post can be found at https://gist.github.com/aalmiray/3ece9c75886d77a1eece