Images are heavily used in Mobile apps. For instance a list that contains employees usually shows the images of these employees. This works well when you have a WIFI connection, but what if you are using slower connections ? If you look at a mobile twitter app you will see that, depending on connectivity, images are loaded instantaneously or delayed. In this post I explain how you can load the images of a List in a background process, after the other content has been loaded.
As mentioned before, a twitter client is able to defer the loading of images whenever a slow connection is detected. You are able to read the content as soon as it is loaded, and the images will show up with a delay, one at a time.
Implementing the basics
I was tempted to make a working sample of this behavior in an Oracle MAF app. The best way to prove this concept is to simply build an app containing a simple list showing an image of the employee and the employee's name. The data in this list would typically come from a web service but for the simplicity of this example I use a POJO. The POJO is based on the Employee POJO from Oracle's WorkBetter sample app. After I created the properties, I make sure that all getters and setters are generated.
There is one extra method, called setImageById(), that takes the empId and constructs the image name. This will be used and explained later in this post. The actual images are provided as part of the application.
The class also contains a getEmployees() method that returns all the employees. This getter will be used on the listpage that is created next. The creation of the listpage is simple. After creating a data control on the EmployeeService class we can just drag and drop the EmployeesCollection to the AMX page as an MAF List View. Make sure you pick a list style where you can actually see the images. In this example, after creating the list we have to make sure that the Listview knows where the images are located so we need to make a small change.
Because the image path is pics/ this must be changed to:
Now you can deploy the app and see how the images are loaded all at once if you open the app.
Implementing the background loading
Now lets see what we need to do when we want to load the images in the background. First of all we must determine the connection type. When it is WIFI, we can load all data, including the images all at once. Otherwise we use two separate calls. The first one reads the employee data and the second one is started in a background process to load the images of the employees. In this post we do not actually implement it in this way. We don't look at the connection type at all. For now we will simply assume that there is a slow connection and always load the images in the background, as this was the purpose of this post anyway.
To load images in the background we need to create a class that can run as a separate thread.
This is a new class, BackgoundLoader. This class implements the Runnable interface.
Note that when this is running, it will call the loadImage() method on the employeeService class in a background thread. We will use this BackgroundLoader class as the worker class where we create an instance of. This instance is passed to a new thread that is created in the setEmpImage() method, by calling startImageLoader(), when we do not have a fast connection. Once the thread is started, the setEmpImage() returns immediately.
The run method of the BackgoundLoader class then calls the loadImage() method in the EmployeeService class as mentioned before. This executes the loadImage() on a separate thread so the UI is not locked. The data on the screen is already shown, while the loadImage() continues to work in the background to load the images. Note that the UI is not blocked. You can scroll and navigate at your convenience.
Finally the loadImage() method has one more, very important aspect. Once data in the Employee collection changes, that is once an image is retrieved, we need to call AdfmfJavaUtilities.flushDataChangeEvents(). This is necessary because as data is updated in the background thread. Any changes in background theads are not propagated to the main thread until they are explicitly flushed by calling AdfmfJavaUtilities.flushDataChangeEvents().
Now there is no need to make any changes to the UI. It will behave the same as before, with the difference that images are shown not all at once, but on a one by one basis. Simply redeploy the app and run it. You will see the images loading slowly. Note that to mimic slow connections we wait 1 second after every image. This makes the background loading very visible in the UI. The video below shows the image loading behavior.
Summary
In this post you learned how to use a background process to load images to the UI. This sample can be improved by actually calling a webservice to retrieve the data, instead of hardcoding all emps in a POJO. You can, based on connectivity, load the images in the same thread, or start a background process to call the service that returns the images.
Resources
To look at the WorkBetter demo app that is shipped with Oracle MAF,you can unzip the publicSamples.zip and open the workBetter app in Jdeveloper. The public samples also contains the StockTracker app that is used to demo the background process.
If you want to read more on Mobile Application Framework, you can also buy my book which is available from amazon.
This post was also posted at the AMIS Technology Blog
As mentioned before, a twitter client is able to defer the loading of images whenever a slow connection is detected. You are able to read the content as soon as it is loaded, and the images will show up with a delay, one at a time.
Implementing the basics
I was tempted to make a working sample of this behavior in an Oracle MAF app. The best way to prove this concept is to simply build an app containing a simple list showing an image of the employee and the employee's name. The data in this list would typically come from a web service but for the simplicity of this example I use a POJO. The POJO is based on the Employee POJO from Oracle's WorkBetter sample app. After I created the properties, I make sure that all getters and setters are generated.
public class Employee {
private int empId;
private String firstName;
private String lastName;
private boolean active = false;
private String image;
There is one extra method, called setImageById(), that takes the empId and constructs the image name. This will be used and explained later in this post. The actual images are provided as part of the application.
public void setImageById(int id){
Integer i = new Integer(id);
String image = i.toString() + ".png";
setImage(image);
}
As a dataprovider I create a new class called EmployeeService. In the constructor of this a list of Employees is created and once this list is ready, a call to setEmpImage() is made. In this method calls out to out POJO class to set the Employees Image.public class EmployeeService { protected static List s_employees = new ArrayList(); public EmployeeService() { s_employees.add(new Employee(130, "Mary", "Atkinson")); s_employees.add(new Employee(105, "David", "Austin")); s_employees.add(new Employee(116, "Shelli", "Baida")); // .... More setEmpImage(); } public void setEmpImage() { for (int x = 0; x < s_employees.size(); x++) { Employee e = (Employee) s_employees.get(x); e.setImageById(e.getEmpId()); } public Employee[] getEmployees() { return (Employee[]) s_employees.toArray(new Employee[s_employees.size()]); }
The class also contains a getEmployees() method that returns all the employees. This getter will be used on the listpage that is created next. The creation of the listpage is simple. After creating a data control on the EmployeeService class we can just drag and drop the EmployeesCollection to the AMX page as an MAF List View. Make sure you pick a list style where you can actually see the images. In this example, after creating the list we have to make sure that the Listview knows where the images are located so we need to make a small change.
<amx:image source="#{row.image}" id="i2"/>
Because the image path is pics/ this must be changed to:
<amx:image source="pics/#{row.image}" id="i2"/>
Now you can deploy the app and see how the images are loaded all at once if you open the app.
Implementing the background loading
Now lets see what we need to do when we want to load the images in the background. First of all we must determine the connection type. When it is WIFI, we can load all data, including the images all at once. Otherwise we use two separate calls. The first one reads the employee data and the second one is started in a background process to load the images of the employees. In this post we do not actually implement it in this way. We don't look at the connection type at all. For now we will simply assume that there is a slow connection and always load the images in the background, as this was the purpose of this post anyway.
To load images in the background we need to create a class that can run as a separate thread.
This is a new class, BackgoundLoader. This class implements the Runnable interface.
package com.blogspot.lucbors.img.mobile.services;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;
public class BackgroundLoader implements Runnable {
public BackgroundLoader() {
super();
}
EmployeeService empServ = null;
public BackgroundLoader(EmployeeService empServ) {
this.empServ = empServ;
}
boolean go = false;
public void run() {
while (true) {
if (go) {
empServ.loadImage();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
}
Note that when this is running, it will call the loadImage() method on the employeeService class in a background thread. We will use this BackgroundLoader class as the worker class where we create an instance of. This instance is passed to a new thread that is created in the setEmpImage() method, by calling startImageLoader(), when we do not have a fast connection. Once the thread is started, the setEmpImage() returns immediately.
public void setEmpImage() { for (int x = 0; x < s_employees.size(); x++) { Employee e = (Employee) s_employees.get(x); if (1 == 0) { // this is what we do with a fast connection e.setImageById(e.getEmpId()); } if (1 == 1) { // this is what we do with a slow connection (or if 1==1) startImageLoader(); } providerChangeSupport.fireProviderRefresh("employees"); } } private BackgroundLoader loader = new BackgroundLoader(this); private Thread worker = new Thread(loader); /* * Starts the BackgroundLoader thread which invokes the loadImage method to load the images in * a background process. */ public void startImageLoader() { setLoaderStarted(true); loader.go = true; if (!worker.isAlive()) { worker.start(); } setLoaderStarted(loader.go); }
The run method of the BackgoundLoader class then calls the loadImage() method in the EmployeeService class as mentioned before. This executes the loadImage() on a separate thread so the UI is not locked. The data on the screen is already shown, while the loadImage() continues to work in the background to load the images. Note that the UI is not blocked. You can scroll and navigate at your convenience.
Finally the loadImage() method has one more, very important aspect. Once data in the Employee collection changes, that is once an image is retrieved, we need to call AdfmfJavaUtilities.flushDataChangeEvents(). This is necessary because as data is updated in the background thread. Any changes in background theads are not propagated to the main thread until they are explicitly flushed by calling AdfmfJavaUtilities.flushDataChangeEvents().
public synchronized void loadImage() { int i = 0; long time = 1000; while (i < filtered_employees.size()) { Employee e = (Employee) filtered_employees.get(i); int id = e.getEmpId(); e.setImageById(id); try { wait(time); AdfmfJavaUtilities.flushDataChangeEvent(); } catch (InterruptedException f) { //catch exception here } i++; } }
Now there is no need to make any changes to the UI. It will behave the same as before, with the difference that images are shown not all at once, but on a one by one basis. Simply redeploy the app and run it. You will see the images loading slowly. Note that to mimic slow connections we wait 1 second after every image. This makes the background loading very visible in the UI. The video below shows the image loading behavior.
Summary
In this post you learned how to use a background process to load images to the UI. This sample can be improved by actually calling a webservice to retrieve the data, instead of hardcoding all emps in a POJO. You can, based on connectivity, load the images in the same thread, or start a background process to call the service that returns the images.
Resources
To look at the WorkBetter demo app that is shipped with Oracle MAF,you can unzip the publicSamples.zip and open the workBetter app in Jdeveloper. The public samples also contains the StockTracker app that is used to demo the background process.
If you want to read more on Mobile Application Framework, you can also buy my book which is available from amazon.
This post was also posted at the AMIS Technology Blog
Comments