Skip to main content

MAF 2.0 : Loading Images in a Background Process - Part I

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.


No WIFI connection active           WIFI connection active



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

   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"/>  


DnD

Now you can deploy the app and see how the images are loaded all at once if you open the app.

allOtOnce

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 &lt; 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 &lt; 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

Popular posts from this blog

ADF 12.1.3 : Implementing Default Table Filter Values

In one of my projects I ran into a requirement where the end user needs to be presented with default values in the table filters. This sounds like it is a common requirement, which is easy to implement. However it proved to be not so common, as it is not in the documentation nor are there any Blogpost to be found that talk about this feature. In this blogpost I describe how to implement this. The Use Case Explained Users of the application would typically enter today's date in a table filter in order to get all data that is valid for today. They do this each and every time. In order to facilitate them I want to have the table filter pre-filled with today's date (at the moment of writing July 31st 2015). So whenever the page is displayed, it should display 'today' in the table filter and execute the query accordingly. The problem is to get the value in the filter without the user typing it. Lets first take a look at how the ADF Search and Filters are implemented by...

ADF 11g Quicky 3 : Adding Error, Info and Warning messages

How can we add a message programatically ? Last week I got this question for the second time in a months time. I decided to write a short blogpost on how this works. Adding messages is very easy, you just need to know how it works. You can add a message to your faces context by creating a new FacesMessage. Set the severity (ERROR, WARNING, INFO or FATAL ), set the message text, and if nessecary a message detail. The fragment below shows the code for an ERROR message. 1: public void setMessagesErr(ActionEvent actionEvent) { 2: String msg = "This is a message"; 3: AdfFacesContext adfFacesContext = null; 4: adfFacesContext = AdfFacesContext.getCurrentInstance(); 5: FacesContext ctx = FacesContext.getCurrentInstance(); 6: FacesMessage fm = 7: new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, ""); 8: ctx.addMessage(null, fm); 9: } I created a simple page with a couple of buttons to show the result of setting the message. When the but...

ADF 12c : Using Jasper Reports en JasperSoft Studio 6.1; What Libraries do you need?

Over the last couple of years, or better in the last decade I have implemented several reporting solutions with Jasper Reports in ADF. I did that in ADF 10g, ADF 11.1.1.x, ADF 11.1.2.x and ADF 12.1.x I also used several version of Jasper Reports. There is a whole lot of documentation, blogposts and presentations available. So when today I got a request from one of my customers to make a setup for the implementation of Jasper Reports 6.1 in ADF 12.1.3 I did not expect any problems. Boy was I wrong. Here is the Story With all the knowledge from the past, I decided to follow the known steps. 1) Download iReport Designer, 2) Build a report in iReport 3) Create an ADF application 4) Add the necessary libraries to use the report 5) Call the report from a button via a Managed Bean Step 1 In the past I used iReport designer to build the reports. When you go to the download site of iReport designer you now see an interesting message. So I took this serious and decided not to u...