I am writing a custom event and would like some help please. Most of what I am about to talk about is based on the help provided at Custom event listener on Android app
So here is my issue. I am writing an app that needs to download updated images from the web, store the images on the phone, then later display those images. Basically, I download any needed images during a splash screen. Then when the images are downloaded and stored, the splash screen clears and any necessary (newly downloaded) images are displayed on the screen. Here is the problem: the download process is done via an asynctask so the part where the images are loaded on to the screen can't be done inside the asynctask. It has to be done on the main UI thread. I would like to create an event and a custom event listener for the main thread to listen for that basically tells the main UI thread that it is safe to start loading the downloaded images from memory.
According to the discussion from the link above, I came up with this so far... a download listener interace
public interface DataDownloadListener {
void onDownloadStarted();
void onDownloadFinished();
}
an event class...
public class DataDownloadEvent {
ArrayList<DataDownloadListener> listeners = new ArrayList<DataDownloadListener>();
public void setOnDownload(DataDownloadListener listener){
this.listeners.add(listener);
}
}
My problem is that I don't understand where to put the last two steps in those instructions. I thought I would have to put the listener and event inside the class that actually initiates the downloads. But where? Here is my function that initiates the download and saves it to the device:
public String download(String sourceLocation) {
String filename = "";
String path = "";
try {
File externalStorageDirectory = Environment
.getExternalStorageDirectory();
URL urlTmp = new URL(sourceLocation);
filename = urlTmp.getFile()
.substring(filename.lastIndexOf("/") + 1);
path = externalStorageDirectory + PATH;
// check if the path exists
File f = new File(path);
if (!f.exists()) {
f.mkdirs();
}
filename = path + filename;
f = new File(filename);
//only perform the download if the file doesn't already exist
if (!f.exists()) {
Bitmap bitmap = BitmapFactory.decodeStream(urlTmp.openStream());
FileOutputStream fileOutputStream = new FileOutputStream(
filename);
if (bitmap != null) {
bitmap.compress(getFormat(filename), 50, fileOutputStream);
Log.d(TAG, "Saved image " + filename);
return filename;
}
}
else{
Log.d(TAG, "Image already exists: " + filename + " Not re-downloading file.");
}
} catch (MalformedURLException e) {
//bad url
} catch (IOException e) {
//save error
}
return null;
}
And the last step about registering the listener, where do I put that? The instructions say to put that somewhere during initialization. Does that mean in the onCreate method of my main activity? outside the class in the import section of the main activity? Never done a custom event before, so any help would be appreciated.
According to the discussion from the link above, I came up with this so far... a download listener interace
public interface DataDownloadListener {
void onDownloadStarted();
void onDownloadFinished();
}
an event class...
public class DataDownloadEvent {
ArrayList<DataDownloadListener> listeners = new ArrayList<DataDownloadListener>();
public void setOnDownload(DataDownloadListener listener){
this.listeners.add(listener);
}
}
Ok...
Now in your download procedure, at the start of the download, cycle all the elements on the listeners ArrayList and invoke the onDownloadStarted event to inform all your listeners that the download is just started (in this event i presume you'll need to open the splashscreen).
Always in your download procedure, at the and of the download, cycle all the elements on the listeners ArrayList and invoke the onDownloadFinished event to inform all your listeners that the download is finished (now close the splashscreen).
How to cycle listeners on download completed
foreach(DataDownloadListener downloadListener: listeners){
downloadListener.onDownloadFinished();
}
How to cycle listeners on download started
foreach(DataDownloadListener downloadListener: listeners){
downloadListener.onDownloadStarted();
}
Don't make it static if possible... In the class that you'll use to download your files, simply add what you put in your DataDownloadEvent class (listeners arrayList and facility methods for adding and removing). You have no immediate need to use a class in that way (static members I mean).
Example
public class DownloadFileClassExample{
private ArrayList<DataDownloadListener> listeners = new ArrayList<DataDownloadListener>();
public DownloadFileClassExample(){
}
public void addDownloadListener(DataDownloadListener listener){
listeners.add(listener);
}
public void removeDownloadListener(DataDownloadListener listener){
listeners.remove(listener);
}
//this is your download procedure
public void downloadFile(){...}
}
Then access you class in this way
DownloadFileClassExample example = new DownloadFileClassExample();
example.addDownloadListener(this); // if your class is implementing the **DataDownloadListener**
or use
example.addDownloadListener( new DataDownloadListener{...})
Related
I have a main GUI See image that is used to load and display (thumbnail) images.
I want to make it possible to drag&drop images on my Gui as well.
So I copied an example and put it in a class
public static class FileDragDropListener implements DropTargetListener {
#Override
public void drop(DropTargetDropEvent event) {
DragDropListener DDL = new DragDropListener();
// Accept copy drops
event.acceptDrop(DnDConstants.ACTION_COPY);
// Get the transfer which can provide the dropped item data
Transferable transferable = event.getTransferable();
// Get the data formats of the dropped item
DataFlavor[] flavors = transferable.getTransferDataFlavors();
// Loop through the flavors
for (DataFlavor flavor : flavors) {
try {
// If the drop items are files
if (flavor.isFlavorJavaFileListType()) {
// Get all of the dropped files
List <File> files = (List) transferable.getTransferData(flavor);
//logger.info("length of list {}", files.size());
File[] imgs = new File[files.size()];
int counter = 0;
// Loop them through
for (File file : files) {
// Print out the file path
logger.info("File path is: {}", file.getPath());
imgs[ counter ] = file;
counter++;
}
MyVariables.setSelectedFiles(imgs);
}
} catch (Exception e) {
// Print out the error stack
e.printStackTrace();
}
}
// Inform that the drop is complete
event.dropComplete(true);
}
}
This drag&drop as such works fine. The code:
// Print out the file path
logger.info("File path is: {}", file.getPath());
lists a row of file paths, which I dragged upon my Gui, in the console so drag&drop as such works fine with the files.
I use a setter/getter MyVariables.setSelectedFiles(imgs); to make this File[] available "everywhere".
My problem is now to get this File[] with image paths back in my Gui main class immediately after the drop, so I can update the left panel in my Gui. For that updating I have a method public void LoadImages that is used from many parts in my program, which also touches multiple Gui elements, so I can't make that one static. For that I created the setter/getter, but how do I listen in my main Gui thread for the event.dropComplete(true); to act on it.
I tried many things like Observers, listeners etc., but I always get the non static method cannot be be referenced from a static context.
I do understand that, but how do I get my Gui notified after the drop event has finished, so that it can pick up the data using the getter?
I finally solved it.
The above mentioned FileDragDropListener in my first post is not necessary at all.
When I start my Gui in the main method, I call the below mentioned method rootPanelDropListener.
Note: rootPanel is the name of my entire main screen JPanel.
MyVariables.setSelectedFiles(droppedFilesArray); is a setter I use to be able, in a later stage, to retrieve the data "everywhere" in my program.
loadImages("dropped files"); is the method that loads the images (obvious) and has 3 options: by directory, by selecting (multiple) files, or by dropping the files onto the Gui. Inside the loadimages I check on the parameter "dropped files", then using the getter for the dropped files like files = MyVariables.getSelectedFiles();
public void rootPanelDropListener() {
//Listen to drop events
rootPanel.setDropTarget(new DropTarget() {
public synchronized void drop(DropTargetDropEvent evt) {
try {
evt.acceptDrop(DnDConstants.ACTION_COPY);
List<File> droppedFiles = (List<File>)
evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
for (File file : droppedFiles) {
logger.debug("File path is: {}", file.getPath());
}
File[] droppedFilesArray = (File[]) droppedFiles.toArray(new File[droppedFiles.size()]);
MyVariables.setSelectedFiles(droppedFilesArray);
loadImages("dropped files");
} catch (Exception ex) {
ex.printStackTrace();
logger.error("Drag drop on rootpanel error {}", ex);
}
}
});
}
I hope someone here can help. I am just trying to wrap my head around the Observer Design Pattern, Threading and how I can use both for a project I am doing.
I currently need to implement the both of them on a Media Player I am building using Java FX.
I need to use both of them to update my listView(Populated by a getNames function of files in my directory. I need any changes to my folder of songs to reflect straight away on the GUI.
Is it possible, to have a running thread constantly calling my getNames function(returns an items variable), and if there are any changes to the items variable then I can use the Observer pattern to notify my GUI class to update its list.
I know it's possible to have a thread constantly hitting the function, but I just need some advice on if its then possible to use the Observer pattern to notify on if the items have changed!
I have no code to show, as I am still trying to figure out how to implement this.
Any ideas?
Appreciate any advice at all! Thanks :)
UPDATE
After quite a long time, Got this working with threads and observer patterm. Didn't need WatchService. Used my thread to constantly call a check for change method, then if method returned through then Observer kicked in to update GUI.
Its possible to use this pattern , you need to run a thread to keep watch on folder for files update and to make this thread safe use eventQueue to run your thread
e.g.
java.awt.EventQueue.invokeLater or invokeAndWait
Once change is detected by the thread, then your observer pattern will update GUI
Hope this helps!!
The best approach to this (IMO) would be:
Consider this Oracle tutorial on the WatchService.
As you are using JavaFX, wrap "the basic steps required to implement a watch service" from that tutorial in a JavaFX Task.
Perhaps following the pattern from the Task javadoc "A Task Which Returns Partial Results" to feedback into your view any changes detected by the watch service.
As you note "unfortunately our lecturer won't let us use WatchService", then you can use a method like in the sample code below which is an active poll of the FileSystem. The use of the WatchService is definitely preferred as it can, internally within the JDK implementation, make use of OS provided file watch services. Those OS services can provide a notification of a file change event, so that the Java code does not need to actively poll the file system for changes. Nevertheless, the following inefficient job will likely suffice to do the job in this case...
What the code does is spawn a JavaFX task on a thread which polls the file system and modifies the observable list backing the ListView to match the files on the file system. The list modification is done within a Platform.runLater call to ensure that the modifications to the list backing the list view occur on the JavaFX application thread, so that the active scene graph is not modified off of the JavaFX application thread.
import javafx.application.*;
import javafx.collections.*;
import javafx.collections.transformation.SortedList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;
import java.io.File;
import java.nio.file.*;
import java.util.Arrays;
import java.util.Comparator;
public class FileWatcher extends Application {
private static final Path WATCH_DIR = Paths.get(System.getProperty("user.dir"));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
ObservableList<File> songFileList = FXCollections.observableArrayList();
SortedList<File> sortedSongFileList = new SortedList<>(
songFileList,
Comparator.comparing(File::getName)
);
ListView<File> songListView = new ListView<>();
songListView.setItems(sortedSongFileList);
songListView.setCellFactory(param -> new ListCell<File>() {
#Override
protected void updateItem(File item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
return;
}
setText(item.getName());
}
});
SongWatcher watcher = new SongWatcher(
WATCH_DIR, songFileList
);
Thread watcherThread = new Thread(watcher, "song-watcher");
watcherThread.setDaemon(true);
watcherThread.start();
Scene scene = new Scene(songListView);
stage.setScene(scene);
stage.show();
}
class SongWatcher extends Task<Void> {
private static final String SONG_EXTENSION = "mp3";
private static final long POLL_INTERVAL_MILLIS = 200;
private final Path directory;
private final ObservableList<File> songFiles;
SongWatcher(Path directory, ObservableList<File> songFiles) {
this.directory = directory;
this.songFiles = songFiles;
}
#Override
protected Void call() {
System.out.println("Started watching " + directory + " for song file changes.");
while (!isCancelled()) {
try {
Thread.sleep(POLL_INTERVAL_MILLIS);
} catch (InterruptedException e) {
if (isCancelled()) {
break;
}
Thread.currentThread().interrupt();
}
try {
if (!Files.isDirectory(directory)) {
throw new Exception("Watched directory " + directory + " is not a directory.");
}
File[] foundFiles =
directory
.toFile()
.listFiles(
(dir, name) -> name.endsWith(SONG_EXTENSION)
);
if (foundFiles == null) {
throw new Exception("Watched directory " + directory + " find files returned null (this is not expected).");
}
Platform.runLater(() -> {
// remove files from the song list which are no longer on the disk.
songFiles.removeIf(checkedFile ->
Arrays.binarySearch(foundFiles, checkedFile) < 0
);
// add any files which are on the disk which are not in the song list.
for (File file: foundFiles) {
if (!songFiles.contains(file)) {
songFiles.add(file);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void succeeded() {
System.out.println("Stopped watching " + directory + " for song file changes.");
}
#Override
protected void cancelled() {
System.out.println("Cancelled watching " + directory + " for song file changes.");
}
#Override
protected void failed() {
System.out.println("Failed watching " + directory + " for song file changes.");
if (getException() != null) {
getException().printStackTrace();
}
}
}
}
I am trying to provide capability to upload file in my vaadin application
protected Upload questionImageUpload = new Upload("Upload question", questionReceiver);
questionImageUpload.addFinishedListener(new Upload.FinishedListener() {
#Override
public void uploadFinished(Upload.FinishedEvent event) {
boolean hasLock = VaadinSession.getCurrent().hasLock();
button.setEnabled(false);
}
});
But, in my FinishListener.uploadFinished(), if I modify some UI element (in above, I disable a button), the modification does not get applied.
I assumed that this method may be invoked in a non UI thread so I checked whether VaadinSession is available by putting a breakpoint in uploadFinished above. But, VaadinSession.getCurrent() didn't return null. Also hasLock is also true.
What could be the reason?
I am running this vaadin application on Google App Engine (still running locally inside IntelliJ IDEA). Could that be the reason behind this?
File upload is done as a POST request to the server, containing the file data. When the upload is complete, Upload.FinishedListeners are called at the end of that POST request. While all thread locals are set up correctly, this is not a UI update request (or UIDL request) and the response which is sent to the browser only contains a text that informs the browser that the upload finished. Any UI updates done will be queued until another request asks for them.
Because of this, you need to either use #Push, so that the UI changes are pushed immediately to the client through the push channel, or enable polling at the latest when starting the upload, so that the poll request will pick up the UI changes.
I actually accomplish what you want to do with a SuccedListener. I have a code which updates an embedded component with picture uploaded. You can look at the code and take a cue from it. It can also disable the button. You can correct it not optimised but it works
public class PicUploader extends Upload implements SucceededListener, Receiver {
private static final long serialVersionUID = 1L;
File file;
public String fileName;
final String LOCATION = "/home/###";
Embedded image = new Embedded();
TextField field;
public PicUploader(Embedded image, String caption) {
this.image = image;
this.addSucceededListener(this);
this.setReceiver(this);
this.setCaption(caption);
this.setIcon(FontAwesome.UPLOAD);;
}
public PicUploader(Embedded image, TextField field) {
this.image = image;
this.addSucceededListener(this);
this.setReceiver(this);
this.field = field;
this.setButtonCaption(""+FontAwesome.UPLOAD);
this.setIcon(FontAwesome.UPLOAD);;
}
#Override
public OutputStream receiveUpload(String filename, String mimeType) {
// TODO Auto-generated method stub
FileOutputStream stream = null;
try {
file = new File(LOCATION
+ "/Pictures/"
+ System.currentTimeMillis()
+ filename.substring(filename.length() - 4,
filename.length()));
fileName = file.getName();
System.out.println("This is the file name " + filename);
stream = new FileOutputStream(file);
} catch (FileNotFoundException ex) {
ex.printStackTrace();
}
return stream;
}
#Override
public void uploadSucceeded(SucceededEvent event) {
// TODO Auto-generated method stub
image.setSource(new FileResource(file));
image.setVisible(true);
image.setWidth("150");
image.setHeight("200");
// float ratio = image.getHeight()/image.getWidth();
// image.setWidth(""+image.getWidth());
// image.setHeight(""+image.getHeight());
// field.setValue(getFileName());
this.setEnabled(false);
}
public String getFileName() {
return fileName;
}
}
I have an android activity, wherein a folder gets generated within a button click, along with some files within the folder. Now I am trying to programmatically get the count of the number of files in that folder. My algorithm is somewhat like this:
onClick()
{
createFolder();
createFilesWithinFolder();
countFilesInFolder();
}
In this case, when I try to count the files using countFilesInFolder(), it gives zero in spite of the fact that the folder and the files are actually successfully created.
But if I do this instead and press the second button after the first,
onClick1()
{
createFolder();
createFilesWithinFolder();
}
onClick2()
{
countFilesInFolder();
}
It counts the number of files properly. Later when I checked my countFilesInFolder() function, I came to know that it was not detecting the creation of the folder itself, and hence everything was giving zero.
I tried using the following media scanner class to overcome this issue, but it did not work:
class SingleMediaScanner implements MediaScannerConnectionClient
{
private MediaScannerConnection mMs;
private String mPath;
SingleMediaScanner(Context context, String f)
{
mPath = f;
mMs = new MediaScannerConnection(context, this);
System.out.println("Commencing connection");
mMs.connect();
}
#Override
public void onMediaScannerConnected()
{
//mMs.scanFile(mPath, null);
mMs.scanFile(mPath, null);
Log.i("SCANNER","Scanning :"+mPath);
}
#Override
public void onScanCompleted(String arg0, Uri arg1) {
// TODO Auto-generated method stub
Log.i("SCANNER","Media Scan Completed");
mMs.disconnect();
}
}
I require that the file creation and counting must both occur in the Same button click as I have illustrated in my first algorithm. How should I do this? Please assume that the pseudo-functions I have mentioned above are all correct, as I have seen it working properly in my second algorithm.
suppose we have this URL called
"mysite.com"
in this site we have a directory called images that has about 200 .PNG pictures
I want to make a program that scans through these pictures one by one(you can predict the picture's URL if you know the previous picture's URL) and then I want to display this image on my JFrame
what I initially thought of doing was, since I know the URL, why don't I just scan through all the different image urls, and then do this?
Image image = ImageIO.read(url); hasImage.setIcon(new
ImageIcon(image));
now
hasImage
is a JLabel where I use the image that I just downloaded from the URL
and
url
is an object of class URL
so, everytime in a loop I find the new URL, and I call the function that has the 2 lines of code that I just posted above, in order to update the image on my label
note that these 2 lines are inside a button ActionListener, so that everytime I click on the button, the next image will be displayed
there is 1 major problem here.
when I want to display the next image, it takes some time to create the new url object, download the image, and then display it on my label, which is kind of annoying especially if you're in a hurry and want to display the images really fast...
now, I thought of another implementation, why not just download all the images, save them somewhere locally and then scan through the directory where you stored the images and display them each time the button is clicked?
ok I did this, but the problem is that it takes more than a minute to download all the images
after that it works smoothly, really fast
so here the big problem is that it takes so much time to download the images, so it's basically the previous implementation, but in this one instead of waiting a little bit when I press the button, I kind of wait for everything to get downloaded, which takes the same time...
my question is, how can I make it be faster? if it would download all the images in less than 5 seconds I would be satisfied
here is the function I'm using in order to save the images
private void saveImages() {
Image image;
int ID = 1;
String destFile = "destFolder" + ID + ".png";
try {
image = ImageIO.read(url);
ImageIO.write((RenderedImage) image, "png", new File(destFile));
} catch (IOException ex) {
}
while (ID < 200) {
try {
String path = url.getPath();
String[] token = path.split("-");
String[] finalToken = token[2].split("\\.");
ID = Integer.parseInt(finalToken[0]);
url = new URL("http://somesite/images/" + (ID + 1) + ".png");
destFile = "C:\\...\\destFolder\\" + ID + ".png";
image = ImageIO.read(url);
ImageIO.write((RenderedImage) image, "png", new File(destFile));
} catch (MalformedURLException ex) {
JOptionPane.showMessageDialog(null,
"URL is not in the correct form", "Malformed URL",
JOptionPane.ERROR_MESSAGE);
} catch (IOException ex) {
}
}
JOptionPane.showMessageDialog(null, "Images were loaded successfully",
"Success", JOptionPane.INFORMATION_MESSAGE);
}
EDIT: Btw I'm really sorry about the code, it's kind of messy, it's just the first thing I typed.... I will change it later for the better but I hope you get the idea of what problem I'm facing right now :)
Neither Java or the implementation is the issue, it's the speed of your connection. Either you download all images that the application requires (which takes some time, and is pointless if the images aren't viewed) or you load them as they're clicked.
If you want to make it seem a little quicker, you can start loading the images into a local database or the filesystem (like a cache). That obviously has its drawbacks as it will only make loading times faster once a picture has been loaded once, and it's often ideal to not have a very large cache.
You can also load the five or so next and previous images when just viewing one image, which will make it seem faster to the user.
Downloading 200 images from the web is always going to take some time. What you need to do to speed this up is
To avoid downloading the images in the event dispatch thread: it blocks the UI while downloading. The images should be downloaded in a separate, background thread.
To have two threads downloading images simultaneously. You could download more in parallel, but most of the web servers refuse more than 2 concurrent connections from the same host.
I'd suggest loading images in the background and having them appear as soon as they are loaded, that way your UI will be responsive. When you go to a particular image you can automatically load the next ones in anticipation as well.
Here's a rather green dry-coded (not actually tried to compile) attempt at this, that should demonstrate the idea. (Forgive the tight coupling and rather mashed up responsibilities in the classes. Also, it rather cavalier about the unboundedness of the imageBuffer, and there is no offload to disk).
class ImageManager {
private ExecutorService asyncLoader = Executors.newFixedThreadPool(2);
private HashMap<Integer, Image> imageBuffer = new HashMap<Integer, Image>();
private final int PRELOAD = 2;
private String baseUrl;
public ImageManager(String baseUrl) {
this.baseUrl = baseUrl;
}
public void loadImageAndUpdateLabel(final int idx, final JLabel label) {
asyncLoader.execute(new Runnable() {
public void run() {
loadSync(idx);
SwingUtilities.invokeLater(new Runnable() {
label.setIcon(new ImageIcon(imageBuffer.get(idx)));
label.setText(null);
});
}
});
triggerAsyncPreload(idx);
}
public void triggerAsyncPreload(int idx) {
for (int i=idx-PRELOAD; i<idx+PRELOAD; i++) {
if (i>0) {
loadAsync(i);
}
}
}
public synchronized void loadSync(int idx) {
if (imageBuffer.get(idx) == null) {
imageBuffer.put(idx, ImageIO.read(new URL(baseUrl, idx+".png")));
}
}
public void loadAsync(final int idx) {
asyncLoader.execute(new Runnable() {
public void run() {
loadSync(idx);
}
});
}
}
class ... {
...
private int currentImageIdx = 0;
private ImageManager imageManager = new ImageManager("http://site.com/images/");
private JLabel label = new JLabel();
private JButton nextImageButton = new JButton(new AbstractAction("Next image") {
public void actionPerformed(ActionEvent e) {
currentImageIdx++;
label.setIcon(null);
label.setText("Loading image "+currentImageIdx);
imageManager.loadImageAndUpdateLabel(currentImageIdx, label);
}
});
...
}