lateinit property xxx has not been initialized with TestFX in Kotlin - javafx

I am testing JavaFX with TestFX. Here are my codes:
MainTest:
package com.sample;
+import xx;
public class MainTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("mainwindow.fxml"));
primaryStage.setTitle("Test");
primaryStage.setScene(new Scene(root, 909, 621));
primaryStage.show();
primaryStage.toFront();
}
#Before
public void setUp() throws Exception {
}
#After
public void tearDown() throws Exception {
}
}
ControllerTest:
package com.sample
+import xx
class ControllerKTest {
#FXML
private lateinit var root: AnchorPane
private lateinit var snackBar: JFXSnackbar
#FXML
fun initialize() {
snackBarInitialize()
}
private fun snackBarInitialize() {
snackBar = JFXSnackbar(root)
snackBar.styleClass.add("jfx-snackbar-action")
snackBar.stylesheets.add("css/jfoenix-components.css")
}
#Before
fun setUp() {
FxToolkit.registerPrimaryStage()
FxToolkit.setupApplication(MainTest::class.java)
initialize()
}
private var count = 0
#Test
fun onStartButtonMouseClicked() {
if (count++ % 2 == 0) {
snackBar.fireEvent(JFXSnackbar.SnackbarEvent("Toast Message $count"))
} else {
if (count % 4 == 0) {
snackBar.fireEvent(JFXSnackbar.SnackbarEvent(
"Snackbar Message Persistant $count",
"CLOSE",
3000,
true
) { b -> snackBar.close() })
} else {
snackBar.fireEvent(JFXSnackbar.SnackbarEvent(
"Snackbar Message $count",
"UNDO",
3000,
false
) { b -> })
}
}
}
#After
fun tearDown() {
FxToolkit.cleanupStages()
}
}
However, it gives me this:
kotlin.UninitializedPropertyAccessException: lateinit property root has not been initialized
at com.sample.ControllerKTest.snackBarInitialize(ControllerKTest.kt:55)
at com.sample.ControllerKTest.initialize(ControllerKTest.kt:51)
at com.sample.ControllerKTest.setUp(ControllerKTest.kt:64)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Then, how could I make this right?

Your setUp method calls initialize directly, which is wrong: it should only be called by the FXMLLoader.load method because then it runs after it gets the chance to initialize the annotated fields.

Related

Issue with passing constructor parameter via controller factory

I've got an issue with passing parameters to controller. The main program uses a controller factory to pass a stage object to my controller. The controller factory prints the address of the stage object, but the controller gets Null as stage object. Why ?
Though I've reduced my application to a simple dialogue, I can't find my fault. I hope you can help. Thanks!
The main program:
public class Main extends Application {
private final Logger logger = Logger.getLogger(this.getClass().getName());
private final String FXML_SIMPLE_DIALOG = "testDialog.fxml";
private MyHandler myHandler = new MyHandler();
#Override
public void init() {
try{
// Thread.currentThread is the FX-Launcher thread:
Thread.currentThread().setUncaughtExceptionHandler(myHandler);
try {
logger.addHandler(new FileHandler("java.myLOG"));
}
catch (IOException e) {
throw new IllegalStateException("IOException when adding File Handler");
}
}
catch (Exception ex) {
myHandler.uncaughtException(Thread.currentThread(), ex);
throw(ex);
}
}
#Override
public void start(Stage primaryStage) {
try{
logger.info("Test Application started");
Thread.currentThread().setUncaughtExceptionHandler(myHandler);
try{
URL location = new URL(this.getClass().getResource("resources/fxml/" + FXML_SIMPLE_DIALOG).toString());
FXMLLoader loader = new FXMLLoader(location);
loader.setControllerFactory(new SimpleControllerFactory(primaryStage));
Pane root = loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("*** TEST App ***");
primaryStage.show();
}
catch(IOException ex) {
ex.printStackTrace();
throw new IllegalStateException("check program source code!");
}
}
catch(Exception ex) {
myHandler.uncaughtException(Thread.currentThread(), ex);
throw(ex);
}
}
public static void main(String[] args) {
launch(args);
}
class MyHandler implements Thread.UncaughtExceptionHandler{
#Override
public void uncaughtException(Thread thread, Throwable throwable) {
logger.log(Level.SEVERE, "** STOPP ** due to uncaught exception", throwable);
Platform.exit();
}
}
}
The code of the controller factory:
public class SimpleControllerFactory implements Callback<Class<?>,Object> {
private static final Logger logger = Logger.getLogger(SimpleControllerFactory.class.getName());
private final Stage primaryStage;
public SimpleControllerFactory(Stage stage) {
this.primaryStage = stage;
System.out.println("controller factory: value of stage: " + this.primaryStage);
}
public SimpleControllerFactory() { this(null); }
#Override
public Object call(Class<?> type) {
try {
for (var c : type.getConstructors()) {
switch(c.getParameterCount()) {
case 0 -> {}
case 1 -> {
if ( c.getParameterTypes()[0] == Stage.class) {
return c.newInstance(primaryStage) ;
}
else;
}
default -> {}
}
}
return type.getDeclaredConstructor().newInstance();
}
catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException ex) {
logger.log(Level.SEVERE,ex.toString(),ex);
throw new RuntimeException(ex);
}
}
}
This are the FXML-file and the simple controller:
<VBox prefHeight="150.0" prefWidth="250.0" spacing="5.0" style="-fx-padding: 5 5 5 5;-fx-font-size: 11px"
xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="TestSimpleDialog.DialogController">
<children>
<ButtonBar buttonOrder="UL_HE+FBIX_NCYOA_R" prefHeight="40.0">
<buttons>
<Button alignment="CENTER" mnemonicParsing="false" onAction="#time" text="time ?">
</Button>
</buttons>
</ButtonBar>
<StackPane VBox.vgrow="ALWAYS">
<children>
<Label fx:id="textTime" alignment="CENTER" text="" />
</children>
</StackPane>
</children>
simple controller:
public class DialogController implements Initializable {
private static final Logger logger = Logger.getLogger(DialogController.class.getName());
private final Stage recentStage;
DialogController(Stage stage) {
this.recentStage = stage;
}
DialogController() { this(null); }
#FXML private Label textTime;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
System.out.println("Controller started, value of recentStage: " + this.recentStage);
}
#FXML
private void time(ActionEvent event) {
textTime.setText(LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy kk:mm:ss")));
}
}
The Class.getConstructors() method
Returns an array containing Constructor objects reflecting all the public constructors of the class represented by this Class object.
(my emphasis).
Since your constructors are not declared public, the for loop in the factory's call(...) method iterates zero times, and it defaults to the call to the no-arg constructor.
Just declare the constructors as public and it will work:
public class DialogController implements Initializable {
private final Stage recentStage;
public DialogController(Stage stage) {
this.recentStage = stage;
}
public DialogController() { this(null); }
// ...
}
If you really want to keep the constructors non-public, you can us the getDeclaredConstructor(...) method to retrieve a specific constructor, which does not have to be public:
#Override
public Object call(Class<?> type) {
try {
System.out.println("Number of constructors found: "+type.getConstructors().length);
try {
Constructor<?> c = type.getDeclaredConstructor(Stage.class);
return c.newInstance(primaryStage);
} catch (NoSuchMethodException e) {
return type.getDeclaredConstructor().newInstance();
}
}
catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException ex) {
logger.log(Level.SEVERE,ex.toString(),ex);
throw new RuntimeException(ex);
}
}

Got InvocationTargetException when using Guice 4 's createInjector()

As a new one to Google Guice, I try to use it in my JavaFX project,the purpose is to inject service to a controller class. But java.lang.reflect.InvocationTargetException always appears. The main part is here:
public class App extends Application {
private static Scene scene;
private static Injector injector;
#Override
public void start(Stage stage) throws IOException {
scene = new Scene(loadFXML("ui"), 640, 480);
stage.setScene(scene);
stage.show();
}
private static Parent loadFXML(String fxml) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
fxmlLoader.setControllerFactory(initilizedClass->{
return injector.getInstance(initilizedClass);
});
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
public void init() {
App.injector = Guice.createInjector(new DiModule());
}
//...
}
//
public class DiModule extends AbstractModule {
#Override
protected void configure() {
bind(IUserListService.class).to(BroadcastUserListService.class);
}
}
//
public interface IUserListService {}
public class BroadcastUserListService implements IUserListService {}
//
public class UIController {
#FXML
private ListView listView;
#Inject
private IUserListService us;
public void initialize() {
//listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
us.sendOnlineMessage();
}
}
I use modules and the error details are :
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1051)
Caused by: com.google.common.util.concurrent.ExecutionError: java.lang.ExceptionInInitializerError
at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2199)
at com.google.common.cache.LocalCache.get(LocalCache.java:3934)
at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3938)
at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4821)
at com.google.common.cache.LocalCache$LocalLoadingCache.getUnchecked(LocalCache.java:4827)
at guice#4.0/com.google.inject.internal.FailableCache.get(FailableCache.java:48)
at guice#4.0/com.google.inject.internal.ConstructorInjectorStore.get(ConstructorInjectorStore.java:50)
at guice#4.0/com.google.inject.internal.ConstructorBindingImpl.initialize(ConstructorBindingImpl.java:136)
at guice#4.0/com.google.inject.internal.InjectorImpl.initializeJitBinding(InjectorImpl.java:547)
at guice#4.0/com.google.inject.internal.InjectorImpl.createJustInTimeBinding(InjectorImpl.java:884)
at guice#4.0/com.google.inject.internal.InjectorImpl.createJustInTimeBindingRecursive(InjectorImpl.java:805)
at guice#4.0/com.google.inject.internal.InjectorImpl.getJustInTimeBinding(InjectorImpl.java:282)
at guice#4.0/com.google.inject.internal.InjectorImpl.getBindingOrThrow(InjectorImpl.java:214)
at guice#4.0/com.google.inject.internal.InjectorImpl.getInternalFactory(InjectorImpl.java:890)
at guice#4.0/com.google.inject.internal.FactoryProxy.notify(FactoryProxy.java:46)
at guice#4.0/com.google.inject.internal.ProcessedBindingData.runCreationListeners(ProcessedBindingData.java:50)
at guice#4.0/com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:134)
at guice#4.0/com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:107)
at guice#4.0/com.google.inject.Guice.createInjector(Guice.java:96)
at guice#4.0/com.google.inject.Guice.createInjector(Guice.java:73)
at guice#4.0/com.google.inject.Guice.createInjector(Guice.java:62)
at com.freelance.ChatterBox/com.freelance.ChatterBox.App.main(App.java:43)
... 11 more
Caused by: java.lang.ExceptionInInitializerError
at guice#4.0/com.google.inject.internal.cglib.reflect.$FastClassEmitter.<init>(FastClassEmitter.java:67)
at guice#4.0/com.google.inject.internal.cglib.reflect.$FastClass$Generator.generateClass(FastClass.java:72)
at guice#4.0/com.google.inject.internal.cglib.core.$DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at guice#4.0/com.google.inject.internal.cglib.core.$AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at guice#4.0/com.google.inject.internal.cglib.reflect.$FastClass$Generator.create(FastClass.java:64)
at guice#4.0/com.google.inject.internal.BytecodeGen.newFastClass(BytecodeGen.java:204)
at guice#4.0/com.google.inject.internal.DefaultConstructionProxyFactory.create(DefaultConstructionProxyFactory.java:54)
at guice#4.0/com.google.inject.internal.ProxyFactory.create(ProxyFactory.java:159)
at guice#4.0/com.google.inject.internal.ConstructorInjectorStore.createConstructor(ConstructorInjectorStore.java:90)
at guice#4.0/com.google.inject.internal.ConstructorInjectorStore.access$000(ConstructorInjectorStore.java:29)
at guice#4.0/com.google.inject.internal.ConstructorInjectorStore$1.create(ConstructorInjectorStore.java:37)
at guice#4.0/com.google.inject.internal.ConstructorInjectorStore$1.create(ConstructorInjectorStore.java:33)
at guice#4.0/com.google.inject.internal.FailableCache$1.load(FailableCache.java:37)
at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3524)
at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2317)
at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2280)
at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2195)
... 32 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to module guice
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:340)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:280)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:198)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:192)
at guice#4.0/com.google.inject.internal.cglib.core.$ReflectUtils$2.run(ReflectUtils.java:56)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at guice#4.0/com.google.inject.internal.cglib.core.$ReflectUtils.<clinit>(ReflectUtils.java:46)
... 49 more
Exception running application com.freelance.ChatterBox.App

JavaFx unable to execute javascript function

I am using JavaFX to embed browser. I am trying to run a javascript function addnum() from java class WebScale, but i am getting error.If i execute document.write() from webengine.executeScript() it is possible. But i cant call my function.
My code is as follow:
public class WebScale extends JApplet {
static ZoomingPane zoomingPane;
private static JFXPanel fxContainer;
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
final JFrame frame = new JFrame("Area Configurator");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JApplet applet = new WebScale();
applet.init();
frame.setContentPane(applet.getContentPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
applet.start();
frame.addComponentListener(new java.awt.event.ComponentAdapter() {
#Override
public void componentResized(java.awt.event.ComponentEvent evt) {
if (zoomingPane != null) {
zoomingPane.setZoomFactors((double)(frame.getWidth()/ 1280.0), (double)(frame.getHeight() / 800.0));
}
}
});
}
});
}
#Override
public void init() {
fxContainer = new JFXPanel();
fxContainer.setPreferredSize(new Dimension(800, 700));
add(fxContainer, BorderLayout.CENTER);
// create JavaFX scene
Platform.runLater(new Runnable() {
#Override
public void run() {
createScene();
}
});
}
private void createScene() {
WebView webView = new WebView();
zoomingPane = new ZoomingPane(webView);
BorderPane bp = new BorderPane();
bp.setCenter(zoomingPane);
fxContainer.setScene(new Scene(bp));
String strpath ;
strpath="file:///C:/Users/Priyanka/Desktop/FDASH/StationV3/Main.html";
final WebEngine engine = webView.getEngine();
engine.load(strpath);
engine.executeScript("addNum()");
}
private class ZoomingPane extends Pane {
Node content;
private final DoubleProperty zoomFactor = new SimpleDoubleProperty(1);
private double zoomFactory = 1.0;
private ZoomingPane(Node content) {
this.content = content;
getChildren().add(content);
final Scale scale = new Scale(1, 1);
content.getTransforms().add(scale);
zoomFactor.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
scale.setX(newValue.doubleValue());
scale.setY(zoomFactory);
requestLayout();
}
});
}
#Override
protected void layoutChildren() {
Pos pos = Pos.TOP_LEFT;
double width = getWidth();
double height = getHeight();
double top = getInsets().getTop();
double right = getInsets().getRight();
double left = getInsets().getLeft();
double bottom = getInsets().getBottom();
double contentWidth = (width - left - right)/zoomFactor.get();
double contentHeight = (height - top - bottom)/zoomFactory;
layoutInArea(content, left, top,
contentWidth, contentHeight,
0, null,
pos.getHpos(),
pos.getVpos());
}
public final Double getZoomFactor() {
return zoomFactor.get();
}
public final void setZoomFactor(Double zoomFactor) {
this.zoomFactor.set(zoomFactor);
}
public final void setZoomFactors(Double zoomFactorx, Double Zoomfactory) {
this.zoomFactory = Zoomfactory;
this.zoomFactor.set(zoomFactorx);
}
public final DoubleProperty zoomFactorProperty() {
return zoomFactor;
}
}
}
I am getting the following error.
Exception in thread "JavaFX Application Thread" netscape.javascript.JSException: ReferenceError: Can't find variable: addNum
at com.sun.webkit.dom.JSObject.fwkMakeException(JSObject.java:128)
at com.sun.webkit.WebPage.twkExecuteScript(Native Method)
at com.sun.webkit.WebPage.executeScript(WebPage.java:1439)
at javafx.scene.web.WebEngine.executeScript(WebEngine.java:982)
at c.WebScale.createScene(WebScale.java:97)
at c.WebScale.access$0(WebScale.java:83)
at c.WebScale$2.run(WebScale.java:78)
at com.sun.javafx.application.PlatformImpl.lambda$null$174(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$175(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$149(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
Assuming addNum() is defined in Main.html, the javascript hasn't been loaded at the time that you're calling it. You should add a listener so you can call your javascript once the page is fully loaded:
final WebEngine engine = webView.getEngine();
engine.getLoadWorker().stateProperty().addListener(
new ChangeListener<State>() {
public void changed(ObservableValue ov, State oldState, State newState) {
if (newState == State.SUCCEEDED) {
engine.executeScript("addNum()");
}
}
});
engine.load(strpath);

How to wait cancellation of task after Service#cancel?

Look at this example:
package sample;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.stage.Stage;
public class Main extends Application {
//NOTICE: This is class in **other file** (here is just for example)
private static class MyService extends Service {
#Override
protected Task createTask() {
return new Task() {
#Override
protected Object call() throws Exception {
System.out.println("Service: START");
while(true) {
System.out.println("Service: ITERATION");
// Thread.sleep(3000); // This raise InterruptedException after cancel, but how about such code (it won't raise exception):
for(long i = 0; i < 1_000_000_000; i++) {
}
if (isCancelled())
break;
}
System.out.println("Service: END");
return null;
}
};
}
}
#Override
public void start(Stage primaryStage) throws Exception {
MyService myService = new MyService();
myService.start();
Thread.sleep(5000);
myService.cancel();
System.out.println(myService.getState()); // Here is `CANCELLED` already but task isn't finished yet.
// <--- How to wait cancellation of Task here?
System.out.println("This command must be called after `Service: END`");
Platform.exit();
}
public static void main(String[] args) {
launch(args);
}
}
As you known call of Service#cancel doesn't wait cancellation of Task. So, I want to block main thread and await cancellation of Task. How can I do it?
P.S.
Looks like Service doesn't provide any callback/event handler to check real cancellation of Task. Is it right?
By default, Service.cancel() interrupts the Task. So an InterruptedException must be raised and your task will be terminated (forcefully).
One thing you could do is to store the created task in a global variable in your MyService class and override the cancel method like this:
class MyService extends Service {
private Task t;
#Override
public boolean cancel() {
if (t != null) {
return t.cancel(false);
} else {
return false;
}
}
#Override
protected Task createTask() {
t = new Task() { /* ... */ };
return t;
}
}
The rest will be easy. Add a change listener to the service state property (or use setOnCanceled() method) and do whatever you want to do after the state change, in the callback.
Never block the FX Application Thread.
The Service class does indeed define a setOnCancelled(...) method, which you use to register a callback:
myService.setOnCancelled(event -> {
System.out.println("Service was cancelled");
});
Note that when you cancel a Service, it will interrupt the thread if it is blocked. So if you don't catch the InterruptedException it will not exit the call method normally. This is why you don't see the "END" message.
Full example code:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ServiceCancellationTest extends Application {
//NOTICE: This is class in **other file** (here is just for example)
private static class MyService extends Service<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
System.out.println("Service: START");
while(! isCancelled()) {
System.out.println("Service: ITERATION");
try {
Thread.sleep(3000);
} catch (InterruptedException interrupted) {
System.out.println("Task interrupted");
}
if (isCancelled())
break;
}
System.out.println("Service: END");
return null;
}
};
}
}
#Override
public void start(Stage primaryStage) throws Exception {
MyService myService = new MyService();
myService.start();
myService.setOnCancelled(event -> {
System.out.println("In cancelled callback: "+myService.getState()); // Here is `CANCELLED` already but task isn't finished yet.
});
// You should never block the FX Application Thread. To effect a pause,
// use a pause transition and execute the code you want in its
// onFinished handler:
PauseTransition pause = new PauseTransition(Duration.seconds(5));
pause.setOnFinished(event -> {
myService.cancel();
System.out.println("After calling cancel: "+myService.getState());
System.out.println("This command must be called after `Service: END`");
Platform.exit();
});
pause.play();
}
public static void main(String[] args) {
launch(args);
}
}

java.lang.NullPointerException for Multi Screens JavaFX/FXML

So, I'm doing a JavaFX multiview GUI application.
Below is the code so that you can refer to it, don't read through it to find an error yet, I'll explain the problem underneath first ;)
I have a main - which starts the application
public class MyFXMLMain extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Wireframe.fxml"));
stage.setTitle("My Fitness App");
Scene mainScene = new Scene(root,805,809);
stage.setScene(mainScene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
ScreensController - that controls loading/setting screens.
public class ScreensController extends StackPane {
private HashMap<String, Node> screens = new HashMap<>();
public ScreensController() {
super();
}
public void addScreen(String name, Node screen) {
screens.put(name, screen);
}
public Node getScreen(String name) {
return screens.get(name);
}
public boolean loadScreen(String name, String resource) {
try {
FXMLLoader myLoader = new FXMLLoader(getClass().getResource(resource));
Parent loadScreen = (Parent) myLoader.load();
ControlledScreen myScreenController = ((ControlledScreen) myLoader.getController());
myScreenController.setScreenParent(this);
addScreen(name, loadScreen);
return true;
}catch(Exception e) {
System.out.println(e.getMessage());
return false;
}
}
public boolean setScreen(final String name) {
if (screens.get(name) != null) { //screen loaded
final DoubleProperty opacity = opacityProperty();
if (!getChildren().isEmpty()) { //if there is more than one screen
Timeline fade = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(opacity, 1.0)),
new KeyFrame(new Duration(1000), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
getChildren().remove(0); //remove the displayed screen
getChildren().add(0, screens.get(name)); //add the screen
Timeline fadeIn = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(opacity, 0.0)),
new KeyFrame(new Duration(800), new KeyValue(opacity, 1.0)));
fadeIn.play();
}
}, new KeyValue(opacity, 0.0)));
fade.play();
} else {
setOpacity(0.0);
getChildren().add(screens.get(name)); //no one else been displayed, then just show
Timeline fadeIn = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(opacity, 0.0)),
new KeyFrame(new Duration(2500), new KeyValue(opacity, 1.0)));
fadeIn.play();
}
return true;
} else {
System.out.println("screen hasn't been loaded!!! \n");
return false;
}
}
public boolean unloadScreen(String name) {
if (screens.remove(name) == null) {
System.out.println("Screen didn't exist");
return false;
} else {
return true;
}
}
}
A screen framework - that links the screens to the FXML files.
public class ScreensFramework extends Application {
public static String MAIN_SCREEN = "MyFXMLController";
public static String MAIN_SCREEN_FXML = "Wireframe.fxml";
public static String calendarScreen = "CalendarscreenController";
public static String calendarScreenFXML = "Calendarscreen.fxml";
public static String guideScreen = "GuideScreenController";
public static String guideScreenFXML ="Guidescreen.fxml";
#Override
public void start(Stage primaryStage) {
ScreensController mainContainer = new ScreensController();
mainContainer.loadScreen(ScreensFramework.MAIN_SCREEN, ScreensFramework.MAIN_SCREEN_FXML);
mainContainer.loadScreen(ScreensFramework.calendarScreen,ScreensFramework.calendarScreenFXML);
mainContainer.loadScreen(ScreensFramework.guideScreen,ScreensFramework.guideScreenFXML);
mainContainer.setScreen(ScreensFramework.MAIN_SCREEN);
Group root = new Group();
root.getChildren().addAll(mainContainer);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
An FXML controller - which links to the FXML files and tells FXML what to do when something is clicked.
public class MyFXMLController implements ControlledScreen{
#FXML private TextField givenName;
#FXML private Text targetText;
//#FXML private static TableView<Mass> theTable;
//#FXML private static TableColumn<Mass, String> dateCol;
//#FXML private static TableColumn<Mass, String> massCol;
#FXML private static LineChart<Number,Number> weightChart;
#FXML private static NumberAxis axisX;
#FXML private static NumberAxis axisY;
#FXML private static Label myLabel;
//private static User theUser = new User();
private static ScreensController myController;
#Override
public void setScreenParent(ScreensController screenPage) {
myController = screenPage;
}
#FXML protected void handlePressedCalendarButtonAction(ActionEvent event){
System.out.println("Hello");
}
#FXML protected void mouseclickedcal(MouseEvent mec){
myController.setScreen(ScreensFramework.calendarScreen);
}
}
A controlledscreen - that does this:
public interface ControlledScreen {
public void setScreenParent(ScreensController screenPage);
}
And a CalendarScreenController - that controls one of the multi screens
public class CalendarScreenController implements Initializable, ControlledScreen {
ScreensController myController;
#Override
public void initialize(URL url, ResourceBundle rb) {
}
public void setScreenParent(ScreensController screenParent){
myController = screenParent;
}
#FXML
private void goToMain(ActionEvent event){
myController.setScreen(ScreensFramework.MAIN_SCREEN);
}
}
PROBLEM BELOW
When I run my program it works fine, but then if I click on a button that activates the calendar onclick code here:
#FXML protected void mouseclickedcal(MouseEvent mec){
myController.setScreen(ScreensFramework.calendarScreen);
}
which should set the screen to CalendarScreen.fxml,one of my multi screens but instead, it causes an error below:
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
at javafx.event.Event.fireEvent(Unknown Source)
at javafx.scene.Scene$ClickGenerator.postProcess(Unknown Source)
at javafx.scene.Scene$ClickGenerator.access$8600(Unknown Source)
at javafx.scene.Scene$MouseHandler.process(Unknown Source)
at javafx.scene.Scene$MouseHandler.process(Unknown Source)
at javafx.scene.Scene$MouseHandler.access$1900(Unknown Source)
at javafx.scene.Scene.impl_processMouseEvent(Unknown Source)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Unknown Source)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(Unknown Source)
at com.sun.glass.ui.View.handleMouseEvent(Unknown Source)
at com.sun.glass.ui.View.notifyMouse(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.access$100(Unknown Source)
at com.sun.glass.ui.win.WinApplication$3$1.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.reflect.misc.Trampoline.invoke(Unknown Source)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
... 31 more
Caused by: java.lang.NullPointerException
at myfxml.MyFXMLController.mouseclickedcal(MyFXMLController.java:125)
... 40 more
Line 125 is the code:
#FXML protected void mouseclickedcal(MouseEvent mec){
myController.setScreen(ScreensFramework.calendarScreen);
}
Thank you so much for having a look at this. I can't seem to find the error ;(
Turns out I had 2 mains and they were conflicting and also in my xml code I was calling something that didn't exist in its controller.

Resources