How to access device camera and save a photo in MAF -Part 2

INTRODUCTION

In part 1 of this blog, we learnt how to create a simple Oracle MAF application to access device features. We learnt how to access the device camera and capture a photo. We also learnt how to access a pre-existing photo from the device photo gallery to display on a MAF page. We will now see how to save the captured image to the local device database. The application will now be able to display the newly captured image across application session.
To do this, we will store the image to the local sqlite database. We will enhance the methods “getPhotoFromCamera” and “getPhotoFromLibrary” to persist the image string to the device database. To verify that the newly selected image is indeed saved to the database, we will use a second page “view2” to display the contents of our local database.
 
Steps:

  1. Connect to the local DB store
  2. Create a local database file on application startup, if it does not exist
  3. Initialize the database with data that you might need for the application
  4. When “view1” is opened, display an existing image from the database if it exists
  5. Save the selected image from gallery or a new photo from camera if the user takes a new photo to the database.

STEP 1

Create a new java class called FADBConnectionFactory. This class will maintain the database connection and check if the database file exists.
Add the method dbExists() to the class. This checks the application’s path for the existence of the application database file and returns the appropriate boolean value.

public static boolean dbExists() {
        boolean exists = false;
        String docRoot = AdfmfJavaUtilities.getDirectoryPathRoot(AdfmfJavaUtilities.ApplicationDirectory);
        String dbName = docRoot + "/fa.db";
        File dbFile = new File(dbName);
        if (dbFile.exists())
            exists = true;
        return exists;
    }

The next method getConnection() checks if the connection variable is valid and if not, loads the SQLite jdbc drive and establishes a connection. The autocommit option is turned off to help us manage the transaction.

public static Connection getConnection() throws Exception {
        if (conn == null || conn.isClosed()) {
            try {
                // create a database connection
                String Dir = AdfmfJavaUtilities.getDirectoryPathRoot(AdfmfJavaUtilities.ApplicationDirectory);
                String connStr = "jdbc:sqlite:" + Dir + "/fa.db";
                Class.forName("SQLite.JDBCDriver");
                conn = DriverManager.getConnection(connStr);
                conn.setAutoCommit(false);
                
            } catch (SQLException e) {
                // if the error message is "out of memory",
                // it probably means no database file is found
                System.err.println(e.getClass().getName() + ": " + e.getMessage() );
                e.printStackTrace();
            }
        }
        return conn;
    }

STEP 2

We will leverage the MAF LifeCycleListener to initialize the application database and establish a connection. When the application is installed for the first time, the local db may not exist. We test whether the database file exists in the applications storage directory. We create the database file and create the required tables, initialize any lookup data if the file does not exist. If it does, we simply establish a connection and return.
Open the ApplicationController’s LifeCycleListenerImpl and update it with the code below:

public void start()
  {
      try {
          if (FADBConnectionFactory.dbExists()) {
              System.out.println("*** FAPhotoApp::: ##############Existing DB found -App Start Complete");
              return;
          }
          Statement stat = FADBConnectionFactory.getConnection().createStatement();
          ResultSet rs = stat.executeQuery("SELECT PROP FROM FABLOG WHERE PROP = -1;");
      } catch (SQLException e) {
          System.out.println("*** FAPhotoApp: FATR does not exist ......creating it!");
            try {
                InitDB();
            } catch (Exception f) {
                System.out.println("*** FAPhotoApp: INTIDB -FATR does not exist ......##############Exception:  " + e.getMessage());
            }
        } catch (Exception e) {
          System.out.println("*** FAPhotoApp: FATR does not exist ......##############Exception:  " + e.getMessage());
      }
  }

The code above first checks if the application database exists and returns if it does. Next we confirm that the database does not exist by firing a sql. We could avoid this call as we have already tested the database file existence. The important call here is “InitDB()” which sets up our database.

STEP 3

We package the SQL script that seeds the local database with the application code. The SQL file should be placed in the /.adf/META-INF directory. Go ahead and create a file called fa.db in this folder and paste the following script:

CREATE TABLE FABLOG
(
 PROP VARCHAR(20) NOT NULL,
 VALUE VARCHAR(2000),
 CONSTRAINT PEOPLE_PK PRIMARY KEY (PROP)
);

COMMIT;

REM INSERTING into FABLOG
 
Insert into FABLOG (PROP, VALUE) values ("FA", "DUMMY");

COMMIT;

The InitDB() method reads the SQL script and executes each command to seed the database. Here, we will create one table (“FABLOG”) and insert a dummy record to ensure the database is ready to accept our use captured photo. Place the following method in the LifeCycleListener class.

 private void InitDB() throws Exception {
        Connection sqConn = null;
            try {               
                sqConn = FADBConnectionFactory.getConnection();
                ClassLoader cl = Thread.currentThread().getContextClassLoader();
                InputStream is = cl.getResourceAsStream(".adf/META-INF/fa.sql");
                if (is == null) {
                    System.out.println("*** FAPhotoApp::: INITDB  : ##############Could not look up : /META-INF/fa.sql  ....");
                    return;
                }

                BufferedReader bReader = new BufferedReader(new InputStreamReader(is));
                List<String> stmts = new ArrayList<String>();
                String strstmt = "";
                String ln = bReader.readLine();
                while (ln != null) {
                    if (ln.startsWith("REM") || ln.startsWith("COMMIT")) {
                        ln = bReader.readLine();
                        continue;
                    }
                    strstmt = strstmt + ln;
                    if (strstmt.endsWith(";")) {
                        stmts.add(strstmt);
                        strstmt = "";
                        ln = bReader.readLine();
                        continue;
                    }
                    ln = bReader.readLine();
                }

                for (int i = 0; i < stmts.size(); i++) {
                    Statement pStmt = sqConn.createStatement();
                    String ii = stmts.get(i);
                    pStmt.executeUpdate(ii);
                }
            } finally {
                        if (sqConn != null) {
                            sqConn.commit();
                            sqConn.close();
                    }
            }
   }

STEP 4

We want to display a photo from the local database if the user had selected/captured one in their previous session. To recap, this was the UI code on our “view1” page:

<amx:image id="i1"
                                           source="#{(bindings.photo.inputValue != null)? bindings.photo.inputValue :&quot;/images/90.png&quot;}"
                                           styleClass="#{deviceScope.hardware.screen.diagonalSize gt 7 ? 'springboard-avatar' : 'springboard-avatar-phone'}"
                                           inlineStyle="width: 128px; height: 128px"
                                           shortDesc="Avatar Image"/>

The “photo” binding should be pre-populated with the image from our local device database. If this is the first time the user is opening the app, the binding will point to an included placeholder image.
Lets update the getPhoto() method in the PhotoUtility to load an existing image from the database.

public String getPhoto() {
        if (photo == null || photo.isEmpty()) {
            getFromDB();
        }
        return photo;
    }

Very simply, if the photo variable is null, this method calls the getFromDB() method to look in in the local database and check if an image exists.

public void getFromDB() {
        try {
            Statement stat = FADBConnectionFactory.getConnection().createStatement();
            ResultSet rs = stat.executeQuery("SELECT PROP, VALUE FROM FABLOG;");
            while (rs.next()) {
                String photo = rs.getString("VALUE");
                String prop = rs.getString("PROP");
                if (prop.equals("PHOTO"))
                    this.setPhoto(photo);
            }
        } catch (SQLException e) {
            throw new AdfException("Error getFromDB photo1:", AdfException.ERROR);
        } catch (Exception e) {
            throw new AdfException("Error getFromDB photo2:", AdfException.ERROR);
        }        
    }

The above method queries the local database for a property called “PHOTO”. If a record is found, it sets the local class variable with its value. Very Simple!

STEP 5

The last, but most important step is to save the captured image to the local db. We will enhance our PhotoUtility methods “getPhotoFromCamera” and “getPhotoFromLibrary” to save the image file as soon as it’s available. Add the following method called “addUpdate()” to the PhotoUtility class:

public void addUpdate()  {
        Connection sqConn = null;
        try {
            sqConn = FADBConnectionFactory.getConnection();        
            Statement stat = sqConn.createStatement();
            ResultSet rs = stat.executeQuery("SELECT PROP, VALUE FROM FABLOG where prop='PHOTO';");
            if (!rs.next()) {
                System.out.println(METHOD_NAME+ "::: inserting existing photo...");
                String insertSql = "INSERT INTO FABLOG(PROP, VALUE ) VALUES(?,?)";                
                PreparedStatement pstmt = sqConn.prepareStatement(insertSql);
                pstmt.setString(1, "PHOTO");
                pstmt.setString(2, this.photo);
                boolean aa = pstmt.execute();
            } else {
                String photo = rs.getString("VALUE");
                String prop = rs.getString("PROP");
                System.out.println(METHOD_NAME+ "::: photo prop="+ prop);
                System.out.println(METHOD_NAME+ "::: photo photo="+ photo);
                String updateSql = "UPDATE FABLOG SET VALUE=? WHERE PROP=?";                
                PreparedStatement pstmt = sqConn.prepareStatement(updateSql);
                pstmt.setString(1, this.photo);
                pstmt.setString(2, "PHOTO");
                pstmt.executeUpdate();
            }
        } catch (SQLException e) {
            throw new AdfException("Error saving profile photo1:", AdfException.ERROR);
        } catch (Exception e) {
                    throw new AdfException("Error saving profile photo2:", AdfException.ERROR);
        } finally {
                   if (sqConn != null) {
                        try {
                            sqConn.commit();
                            sqConn.close();
                        } catch (SQLException e) {
                             throw new AdfException("Error saving profile photo3:", AdfException.ERROR);
                        }
                    }
            }
    }

And update both “getPhotoFromCamera” and “getPhotoFromLibrary” by adding the following like after setPhoto(photo1). The method should look like:

public void getPhotoFromCamera() {
        String photo1 =
            DeviceManagerFactory.getDeviceManager().getPicture(40, DeviceManager.CAMERA_DESTINATIONTYPE_DATA_URL ,
                                                               DeviceManager.CAMERA_SOURCETYPE_CAMERA, false,
                                                               DeviceManager.CAMERA_ENCODINGTYPE_JPEG, 1000, 1000);
        photo1 = "data:image/jpeg;base64," + photo1;
        Utility.ApplicationLogger.logp(Level.FINE, this.getClass().getSimpleName(), "*** FAPhotoApp:: PhotoUtility:: getPhotoFromCamera()","::: photo1DEVICE="+photo1);
        setPhoto(photo1);
        addUpdate();        
    }

Thats it! We’re done with our coding. Now, we can verify if the photo was updated to the database by displaying the database contents on “view2” in our taskflow. I’ll document the updates here, but it should be fairly easy to understand.
Create a java class called FABlog. Add two properties and generate their accessors:

private String prop;
    private String value;

Create a java class called FABlogDC and add the following method:

public FABlog[] getFromDB() {
        Connection conn = null;
        List returnValue = new ArrayList();

        try {
            conn = FADBConnectionFactory.getConnection();

            Statement stmt = conn.createStatement();
            ResultSet result = stmt.executeQuery("SELECT PROP, VALUE FROM FABLOG;");
            while (result.next()) {
                FABlog fa = new FABlog();
                fa.setProp(result.getString("PROP"));
                fa.setValue(result.getString("VALUE"));                
                returnValue.add(fa);
            }

        } catch (Exception ex) {
            Utility.ApplicationLogger.severe(ex.getMessage());
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
        //Collections.sort(returnValue);
        return (FABlog[]) returnValue.toArray(new FABlog[returnValue.size()]);
    }

The method reads in all records from the FABlog table and and returns an array of FABlog objects. Convert the FABlogDC class to a datacontrol and drop this method as a list on the view2 page. Remember to update the binding generated to include both the prop and value properties in the Display Attributes. When a photo has been added, you should see something like this on your screen:

 
Screenshot_20170628-114700.png
 
Screenshot_20170628-114706.png
 

 
The complete sample application can be downloaded from here:
PhotoApp.zip

REFERENCES

Javadoc for DeviceManager
Javadoc for DeviceDataControl

vivek