26 Jan 2018

Forge Viewer Workflow Demo in Android Studio

Default blog image

android demo

Three years ago, I produced a code sample on the workflow of Forge Viewer for Android (at that time, Forge Viewer was called View and Data). The sample is available at https://github.com/Autodesk-Forge/viewer-android-sample.

The sample is programmed by Eclipse + ADT plugin. While this plugin is not supported by as per this announcement. The official IDE is to Android Studio now. There are quite a lot of discussions on the two IDEs such as Android Studio vs. Eclipse: What You Need To Know. As Android website says the Eclipse ADT plugin has many known bugs and potential security bugs that will not be fixed. So I forked the original repository, replaced with the new one, and pulled a request. I am waiting for administrator’s merging.

https://github.com/xiaodongliang/viewer-android-sample

android demo

The Readme tells the details on the steps how to setup and build the project. I’d like to highlight a couple of things when I migrated the code.

Firstly, the old code calls the web service of Forge directly, gets the response and parses them to the structured data. Since we have Forge JAVA SDK now, I imported the libs to the new project of Android Studio. We could build the code of Forge JAVA SDK, and copy all * .jar (except httpcore-4.4.1.jar and httpclient-4.5.jar) in the target>>lib folder to app>>lib folder of this sample.  We will also need to compile a few more modules. These are specified in build.gradle.

Update on May 29, 2018, as of today, Java SDK of Forge has been published to Maven center. We can import it to the project by:

<dependency>
    <groupId>com.autodesk</groupId>
    <artifactId>forge-java-sdk</artifactId>
    <version>1.0.1</version>
</dependency>

Based on the lib, I copied the sections of the demo of Forge JAVA SDK to the new sample such as authentication, upload file and post job etc. However this SDK demo is executed in the same process, with the asynchronous HTTP requests of Forge. In Android, it does not permit to make asynchronous callback in the main thread, so each job will be encapsulated in a class of AsyncTask. This is same to the old code.  

In the past, I simply set the TextView content with the status of http request in AsyncTask:: doInBackground, which would cause an exception :

Only the original thread that created a view hierarchy can touch its views.

This is because we cannot change the UI from a background thread. Fortunately, AsyncTask provides another method onProgressUpdate to listen to the values sent from doInBackground, in which we can change the UI. So, in doInBackground, we just need to call publishProgress with the values. e.g.

protected void onProgressUpdate(String... values) {
        super.onProgressUpdate(values);
        if (values != null && values.length > 0) {

            statusView.setText(values[0]); 
            tokenView.setText(values[1]);
        }
    }

    @Override
    protected Void doInBackground(List<String>... params) {

        String CLIENT_ID = Global.CLIENT_ID;
        String CLIENT_SECRET = Global.CLIENT_SECRET;
        List<String> scopes = new ArrayList<String>();

        scopes.add("data:read");
        scopes.add("data:write");
        scopes.add("bucket:create");
        scopes.add("bucket:read");

         try { 
             Global.oauth2TwoLegged = new OAuth2TwoLegged(CLIENT_ID, CLIENT_SECRET, scopes, true);

             Global.twoLeggedCredentials = Global.oauth2TwoLegged .authenticate();
             String token = Global.twoLeggedCredentials.getAccessToken(); 

             responseStr = "get token Succeeded!";
         }
         catch(ApiException ae){
             responseStr ="Failed to get token" + ae.toString();
         }
         catch(Exception ex){
             responseStr ="Failed to get token" + ex.toString();
         }
         finally{
             String[] values = new String[2];
             values[0]= responseStr;
             values[1]= Global.token;
             publishProgress(values);
          }
        return null;
    }

The sample will list the available models from extension storage. Android Studio provides Android Device Monitor. We can put files easily. Unluckily, Android Device Monitor does not launch at all at my side. After trying with a lot of suggestions, I finally had to put the file by adb in command line.

The last thing is very interesting. Although I put the models in the extension storage, the necessary permissions are also specified in the manifest.xml, Environment.getExternalStorageDirectory() returned nothing.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

After searching on Google, I got in Android 6.0 Marshmallow, application will not be granted any permission at installation time. Instead, application has to ask user for a permission one-by-one at runtime. This blog tells in details. 

So, I have to run once with the permission granting by the method ActivityCompat.requestPermissions. just once, the files can be read out by Environment.getExternalStorageDirectory(). I am not sure if this happens to any Android environment, but I left that codes in Main Activity in case of use.

private void grantPermission(String p)
    {
         int REQUEST_CODE_ASK_PERMISSIONS = 124;


        int hasWriteContactsPermission = ContextCompat.checkSelfPermission(
                getApplicationContext(),
                p);


        //grant this specific permission
        if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[] {p},
                    REQUEST_CODE_ASK_PERMISSIONS);
            return;
        }

    }

I hope you find this new sample is helpful and would appreciate you for fixing/improving anything with your experience of Android. 

Related Article