Search code examples
javajavafximageviewfilechooser

JavaFX: Image not displaying in ImageView after moving file using Filechooser


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?


Solution

  • 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.