Issue
so as the title of the post states I'm asking for a way that I can say a file I uploaded using FileChooser to a directory in my project.
More specifically, I'm uploading images and I want to save them in my project so I can use them in the future. Here is an example code:
Controller:
package org.example;
import javafx.fxml.FXML;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.FileChooser;
import java.io.File;
public class PrimaryController {
@FXML
private ImageView image;
@FXML
void handleUploadImage() {
FileChooser fileChooser = new FileChooser();
fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Image Files", "*.jpg", "*.jpeg", "*.png", "*.svg"));
File file = fileChooser.showOpenDialog(null);
if (file != null) {
image.setImage(new Image(String.valueOf(file)));
} else {
System.out.println("It's null");
}
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.Cursor?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: linear-gradient(to right, #1c92d2, #f2fcfe);" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.PrimaryController">
<children>
<ImageView fx:id="image" fitHeight="200.0" fitWidth="200.0" layoutX="200.0" layoutY="100.0" onMouseClicked="#handleUploadImage" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/upload.png" />
</image>
<cursor>
<Cursor fx:constant="HAND" />
</cursor>
</ImageView>
</children>
</AnchorPane>
My directories:
I would like to save the image in /resources/images
. How can I do so?
Solution
Background Info
The file is not being "uploaded". That term is usually for an operation such as sending a file to a hosted cloud service or FTP site. Your code chooses a file available through the local machine's file system. Then you want to do something with it. Probably copy it to another location locally. I guess you might choose to cause that process "uploaded", but, to me, that term doesn't really seem to fit well.
Normally, when a JavaFX application is deployed, all of its code and resources will be packaged together, e.g. in a jar file, installer, or jlink image. The resources directory of your project source code will not be there, but the contents of it will have been copied into the package. To add additional resources to that location at runtime is an undefined operation as you don't really know where the resources will go.
Suggested Alternative: Save to known local file location
What you probably want to do is to copy the file to a well-known location on the machine and access it from there. One way to do that would be to create a directory (e.g. .myapp
) under the user's home directory, copy the chosen file into that directory and then read and write the file from there as needed. You can determine the user's home directory using java system properties.
Alternative: using the Preferences API
Rather than using a custom app directory under the user home directory, you can use the Java Preferences API.
This package allows applications to store and retrieve user and system preference and configuration data. This data is stored persistently in an implementation-dependent backing store. There are two separate trees of preference nodes, one for user preferences and one for system preferences.
Detailed discussion of how to do this is out of scope for this answer, but there are numerous tutorials on the web around it.
An example of using the Java Preferences API from a JavaFX application is in the makery tutorial.
FAQ
It doesn't really make any sense. What I must do then if I'm willing to allow the user to upload an image and then save the image somewhere on my pc or in my project?
Presumably, your "project" is the source code directory you show in your question. As noted, that directory is not available at runtime outside a local development environment. So you can't save the file to the project at runtime because it won't exist. Instead, you need to "save the image somewhere on my pc". One way to do that is by following the suggestion I provided regarding placing the image in an app-specific sub-directory that your application creates in the user's home directory.
Does this solution works globally? like the project isn't just for me meaning it'll be sent to some fellows so they won't have the directory I saved the file to.
They will have a user directory because that is how OSes work. That is available from the system properties I linked. If you go the route of your app creating and using an app-specific sub-directory under the user directory as needed, then you can be assured that the required directory will exist. There are other conventions on storage in OSes for different locations, but I'd really advise just using the app-specific directory under the user directory unless you need to do something else. There are other options such as uploading to a cloud hosting image service but don't do that unless you need it.
Plus, in the project I place all images I use in a directory that's existed in the directory which is for example /resources/images and I need to save the images in it.
As explained, you can't do that dynamically at runtime. You need to place the dynamically added images somewhere else (e.g. under the user local directory, cloud service, shared database, etc). And you need to read those files from the same place. If you place them under the user directory, then you can read and write the files using file URLs or file API rather than getResource.
Example App
This application uses the user's home directory to store a chosen avatar image.
The application will use a default avatar from the application resources shipped with the application. The default avatar can be changed if the user selects a new image to be used.
The current avatar being used is coped to a file stored at <user.home>/.avatar/avatar.png
.
The custom icon image used for the default image is provided in this answer:
This solution may be different than what you need, perhaps you need a file upload service for arbitrary sets of images, like a photo album rather than a single image like an avatar. But hopefully, the info shown here provides you with a concrete example of how you might proceed to solve your exact problem.
The uploadCustomAvatar
method in the AvatarService
takes an InputStream
as an argument. So the method could be adopted to source the custom image from another location (e.g. a web URL) other than a local image file if desired.
default avatar
uploaded custom avatar
AvatarApp.java
import javafx.application.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
public class AvatarApp extends Application {
private static final String APP_DIR = ".avatar";
private AvatarService avatarService;
@Override
public void init() {
LocalStorage localStorage = new LocalStorage(APP_DIR);
avatarService = new AvatarService(localStorage);
}
@Override
public void start(Stage stage) throws Exception {
ImageView avatar = new ImageView(avatarService.getAvatarImage());
avatar.imageProperty().bind(avatarService.avatarImageProperty());
avatar.setFitWidth(AvatarService.DIMENSIONS);
avatar.setFitHeight(AvatarService.DIMENSIONS);
avatar.setPreserveRatio(true);
Button changeAvatarButton = new Button("Change...");
changeAvatarButton.setOnAction(e -> uploadCustomAvatar(stage));
VBox layout = new VBox(10, avatar, changeAvatarButton);
layout.setStyle("-fx-background-color: linen; -fx-font-size: 16px");
layout.setAlignment(Pos.CENTER);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
private void uploadCustomAvatar(Stage owner) {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Choose your avatar");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter(
"PNG Image Files", "*.png"
)
);
File selectedFile = fileChooser.showOpenDialog(owner);
if (selectedFile != null) {
try (
InputStream inputStream = new BufferedInputStream(
Files.newInputStream(selectedFile.toPath())
)
) {
avatarService.uploadCustomAvatar(inputStream);
} catch (IOException ex) {
System.out.println("Unable to upload avatar");
ex.printStackTrace();
}
}
}
public static void main(String[] args) {
launch(args);
}
}
AvatarService.java
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.scene.image.Image;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Objects;
class AvatarService {
public static final double DIMENSIONS = 96;
private static final String DEFAULT_AVATAR_RESOURCE = "Dragon-icon.png";
private static final String AVATAR_FILENAME = "avatar.png";
private final Path avatarPath;
private final ReadOnlyObjectWrapper<Image> avatarImage = new ReadOnlyObjectWrapper<>();
public AvatarService(LocalStorage localStorage) {
avatarPath = localStorage.getLocalStoragePath().resolve(AVATAR_FILENAME);
if (!Files.exists(avatarPath)) {
copyDefaultAvatar();
}
refreshImage();
}
private void copyDefaultAvatar() {
try {
Files.copy(
Objects.requireNonNull(
AvatarApp.class.getResourceAsStream(
DEFAULT_AVATAR_RESOURCE
)
),
avatarPath
);
} catch (IOException e) {
System.out.println("Unable to initialize default avatar");
e.printStackTrace();
Platform.exit();
}
}
public void uploadCustomAvatar(InputStream inputStream) {
try {
Files.copy(
inputStream,
avatarPath,
StandardCopyOption.REPLACE_EXISTING
);
refreshImage();
} catch (IOException e) {
System.out.println("Unable to upload custom avatar");
e.printStackTrace();
}
}
public Image getAvatarImage() {
return avatarImage.get();
}
public ReadOnlyObjectProperty<Image> avatarImageProperty() {
return avatarImage.getReadOnlyProperty();
}
private void refreshImage() {
avatarImage.set(
new Image(
"file:" + avatarPath.toAbsolutePath()
)
);
}
}
LocalStorage.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
class LocalStorage {
private Path localStoragePath;
public LocalStorage(String name) {
try {
String userDir = System.getProperty("user.home");
localStoragePath = Paths.get(userDir, name);
if (!Files.isDirectory(localStoragePath)) {
Files.createDirectory(localStoragePath);
}
} catch (IOException ex) {
System.out.println("Unable to initialize local storage");
ex.printStackTrace();
Platform.exit();
}
}
public Path getLocalStoragePath() {
return localStoragePath;
}
}
Answered By - jewelsea
Answer Checked By - Katrina (JavaFixing Volunteer)