-1

In my JavaFX GUI, users are allowed to edit their posts which involves changing the existing image to the one uploaded by the user. That's where the function shown below comes in.

postImage is the ImageView that I reference from the FXML file - @FXML private ImageView postImage;

private void uploadImage(ActionEvent actionEvent) {
        // Create fileChooser
        FileChooser fileChooser = new FileChooser();
        FileChooser.ExtensionFilter extFilterImages = new FileChooser.ExtensionFilter("Image files (*.jpg)", "*.JPG", "*.PNG");
        fileChooser.getExtensionFilters().addAll(extFilterImages);

        // open dialog to have user select an image from the file system
        File selectedFile = fileChooser.showOpenDialog(title.getScene().getWindow());

        if (selectedFile != null) {
            System.out.println("File selected:" + selectedFile);
            System.out.println("Selected Image: " + selectedFile.getName());

            Path source = FileSystems.getDefault().getPath(selectedFile.getPath());
            String fileExtension = selectedFile.getName().substring(selectedFile.getName().lastIndexOf("."));
            String imagePath = "newImageUploaded" + fileExtension;
            Path destination = FileSystems.getDefault().getPath("resources/images/" + imagePath);

            System.out.println("Source: " + source);
            System.out.println("Destination: " + destination);

            // moving the uploaded image to the resources folder
            try {
                Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
            } catch (IOException ioException) {
                ioException.printStackTrace();
                System.out.println("Exception Caught");
            }

            System.out.println("Changing image: " + imagePath);

            // Set the uploaded image in the ImageView in GUI.
            postImage.setImage(new Image("/images/" + imagePath)); // error on this line: PostDetailsController.java:326

        }

    }

When this function is run, the image is moved successfully and appears in my resources folder. However, the last line postImage.setImage(new Image("/images/" + imagePath)); which is supposed to update my image gives me the following error:

File selected:/Users/vaishnavinaik/Downloads/shoes.jpg
Selected Image: shoes.jpg
Source: /Users/vaishnavinaik/Downloads/shoes.jpg
Destination: resources/images/newImageUploaded.jpg
Changing image: newImageUploaded.jpg
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Invalid URL: Invalid URL or resource not found
    at javafx.graphics/javafx.scene.image.Image.validateUrl(Image.java:1107)
    at javafx.graphics/javafx.scene.image.Image.<init>(Image.java:617)
    at controller.PostDetailsController.uploadImage(PostDetailsController.java:326)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics/javafx.scene.Node.fireEvent(Node.java:8879)
    at javafx.controls/javafx.scene.control.Button.fire(Button.java:200)
    at javafx.controls/com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:206)
    at javafx.controls/com.sun.javafx.scene.control.inputmap.InputMap.handle(InputMap.java:274)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics/javafx.scene.Scene$MouseHandler.process(Scene.java:3851)
    at javafx.graphics/javafx.scene.Scene$MouseHandler.access$1200(Scene.java:3579)
    at javafx.graphics/javafx.scene.Scene.processMouseEvent(Scene.java:1849)
    at javafx.graphics/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2588)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:397)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:434)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:390)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:433)
    at javafx.graphics/com.sun.glass.ui.View.handleMouseEvent(View.java:556)
    at javafx.graphics/com.sun.glass.ui.View.notifyMouse(View.java:942)
    at javafx.graphics/com.sun.glass.ui.mac.MacView.notifyMouse(MacView.java:127)
Caused by: java.lang.IllegalArgumentException: Invalid URL or resource not found
    at javafx.graphics/javafx.scene.image.Image.validateUrl(Image.java:1099)
    ... 46 more

Is it possible that when the statement postImage.setImage(new Image("/images/" + imagePath)); is executed, the move isn't completed yet? Am I doing something wrong when setting the image to the imageView?

  • You're copying the image file to a location in the *source* hierarchy of your project, which is irrelevant to what's available as a resource at runtime. The IDE or build tool isn't going to "know" to deploy it to the classpath (and how would you expect this to work, e.g. if you were running the application from a jar file?). You can't modify the available resources at runtime. – James_D May 22 '20 at 18:54
  • If you want to make these images persist from one run of the application to another, you either need to copy them to a specific location (e.g. a specific folder in the user's home directory) or store the image data in a database. – James_D May 22 '20 at 18:58
  • @James_D Okay, so it is because the image is added at run time, my program isn't able to access it. And yeah it did display the new image once I close and ran the program again. I'll try using a database to store the image. Thanks for the quick response. :-) – Vaishnavi Naik May 22 '20 at 19:10
  • A specific folder is probably easier: that's how most commercial software manages something like this. I would do something like `Path destination = Paths.get(System.getProperty("user.home"), ".my-application", "newImageUploaded"+fileExtension);`. Then you can load the image with `new Image(destination.toUri().toURL().toExternalForm())`. (You may need to ensure the destination folder exists first.) – James_D May 22 '20 at 19:16
  • Alright James, I'll try this. Thanks. – Vaishnavi Naik May 22 '20 at 19:27
  • @James_D, it worked perfectly. Thanks a lot! – Vaishnavi Naik May 22 '20 at 19:37

1 Answers1

0

The Image constructor is expecting a String representing a URL to be passed to it. As stated in the documentation

If the passed string is not a valid URL, but a path instead, the Image is searched on the classpath in that case.

So what you're effectively trying to do here is to modify the content of the classpath at runtime; this just isn't feasible (e.g. if the application were running from a jar file, which will almost certainly be true in production, you would have to modify the content of that jar file at runtime, which is probably not possible).

Assuming the aim here is to persist the image so that it's available the next time the user runs the application, the usual approach is to copy the image to a folder on the user's system which is designed for storing user-specific data for your application. (Alternative approaches might be to store the image data in a database, either locally or remotely, depending on the application.) A common scheme is just to use a folder whose name begins with a . and contains the name of the application, and is in the user's home directory:

Path applicationDataPath = Paths.get(System.getProperty("user.home"), ".my-application");
Files.createDirectories(applicationDataPath);

Now you can do

Path destination = applicationDataPath.resolve("newImageUploaded"+fileExtension);

try {

    Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);

    System.out.println("Changing image: " + imagePath);

    // Set the uploaded image in the ImageView in GUI.
    postImage.setImage(new Image(destination.toUri().toURL().toExternalForm())); 

} catch (IOException ioException) {
    ioException.printStackTrace();
    System.out.println("Exception Caught");
}

You can also fairly easily, e.g., check if that file exists when the application starts up, and display the image as needed, etc.

James_D
  • 201,275
  • 16
  • 291
  • 322