reader/android: fix the problem where device set list is not updated in the initial screen.

When the app launches for the first time, the device set list was not
being updated, because the adapter was being created before the DB is
fully initialized, in which case the adapter fails to monitor the
watch changes coming from Syncbase.

This is fixed by introducing onInitialized() method in DB, which
returns a ListenableFuture which completes when the initialization is
fully performed.

Change-Id: I48d3e5376bc1c834ef37bcb51d041832081dbe43
diff --git a/android/app/src/main/java/io/v/android/apps/reader/DeviceSetChooserActivity.java b/android/app/src/main/java/io/v/android/apps/reader/DeviceSetChooserActivity.java
index 60c79b5..2dfd53e 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/DeviceSetChooserActivity.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/DeviceSetChooserActivity.java
@@ -14,7 +14,9 @@
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.View;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
 
 /**
  * Activity that displays all the active device sets of this user.
@@ -47,14 +49,11 @@
 
         // Add device set FAB initialization
         mButtonAddDeviceSet = (FloatingActionButton) findViewById(R.id.button_add_device_set);
-        mButtonAddDeviceSet.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
-                intent.setType(Constants.PDF_MIME_TYPE);
-                if (intent.resolveActivity(getPackageManager()) != null) {
-                    startActivityForResult(intent, CHOOSE_PDF_FILE_REQUEST);
-                }
+        mButtonAddDeviceSet.setOnClickListener(view -> {
+            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+            intent.setType(Constants.PDF_MIME_TYPE);
+            if (intent.resolveActivity(getPackageManager()) != null) {
+                startActivityForResult(intent, CHOOSE_PDF_FILE_REQUEST);
             }
         });
     }
@@ -63,21 +62,31 @@
     protected void onStart() {
         super.onStart();
 
-        // The adapter for the recycler view
-        mAdapter = new DeviceSetListAdapter(this);
+        // Set the adapter only after the DB is initialized.
+        Futures.addCallback(getDB().onInitialized(), new FutureCallback<Void>() {
 
-        // When a file is clicked from the list, start the PdfViewerActivity.
-        mAdapter.setOnDeviceSetClickListener(new DeviceSetListAdapter.OnDeviceSetClickListener() {
             @Override
-            public void onDeviceSetClick(DeviceSetListAdapter adapter, View v, int position) {
-                Intent intent = PdfViewerActivity.createIntent(
-                        getApplicationContext(),
-                        adapter.getDeviceSetId(position));
-                startActivity(intent);
-            }
-        });
+            public void onSuccess(Void result) {
+                // The adapter for the recycler view
+                mAdapter = new DeviceSetListAdapter(DeviceSetChooserActivity.this);
 
-        mRecyclerView.setAdapter(mAdapter);
+                // When a file is clicked from the list, start the PdfViewerActivity.
+                mAdapter.setOnDeviceSetClickListener((adapter, v, position) -> {
+                    Intent intent = PdfViewerActivity.createIntent(
+                            getApplicationContext(),
+                            adapter.getDeviceSetId(position));
+                    startActivity(intent);
+                });
+
+                mRecyclerView.setAdapter(mAdapter);
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                Log.e(TAG, "Could not initialize the database.", t);
+            }
+
+        }, Utils.mainThreadExecutor());
 
         // ItemTouchHelper for handling the swipe action.
         ItemTouchHelper.SimpleCallback touchCallback;
@@ -109,6 +118,10 @@
             mAdapter.stop();
         }
         mAdapter = null;
+
+        if (mRecyclerView != null) {
+            mRecyclerView.setAdapter(null);
+        }
     }
 
     @Override
diff --git a/android/app/src/main/java/io/v/android/apps/reader/DeviceSetListAdapter.java b/android/app/src/main/java/io/v/android/apps/reader/DeviceSetListAdapter.java
index cd5ed62..a8e3a0f 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/DeviceSetListAdapter.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/DeviceSetListAdapter.java
@@ -49,13 +49,10 @@
             mTextViewId = (TextView) mCardView.findViewById(R.id.device_set_list_item_id);
             mTextViewDevices = (TextView) mCardView.findViewById(R.id.device_set_list_item_devices);
 
-            mCardView.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    if (mClickListener != null) {
-                        mClickListener.onDeviceSetClick(
-                                DeviceSetListAdapter.this, v, getLayoutPosition());
-                    }
+            mCardView.setOnClickListener(view -> {
+                if (mClickListener != null) {
+                    mClickListener.onDeviceSetClick(
+                            DeviceSetListAdapter.this, view, getLayoutPosition());
                 }
             });
         }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/Utils.java b/android/app/src/main/java/io/v/android/apps/reader/Utils.java
index ae9d2cd..ba8f93a 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/Utils.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/Utils.java
@@ -8,12 +8,15 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
 import android.support.v4.content.ContextCompat;
 
 import java.io.File;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Locale;
+import java.util.concurrent.Executor;
 
 /**
  * Utility class which contains useful static methods.
@@ -54,4 +57,20 @@
         return formatter.format(new Date());
     }
 
+    /**
+     * Returns an {@link Executor} which runs the provided task in the main thread.
+     */
+    public static MainThreadExecutor mainThreadExecutor() {
+        return new MainThreadExecutor();
+    }
+
+    private static class MainThreadExecutor implements Executor {
+        private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+        @Override
+        public void execute(Runnable command) {
+            mHandler.post(command);
+        }
+    }
+
 }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/DB.java b/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
index 30a6ec2..4175fe9 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
@@ -7,6 +7,8 @@
 import android.app.Activity;
 import android.content.Context;
 
+import com.google.common.util.concurrent.ListenableFuture;
+
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
@@ -59,6 +61,11 @@
     void init(Activity activity);
 
     /**
+     * Returns a ListenableFuture that completes once the database initialization is finished.
+     */
+    ListenableFuture<Void> onInitialized();
+
+    /**
      * Tells if initialization process is completed.
      *
      * @return true if the initialization process is completed.
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java b/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
index faeca92..0e6be15 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
@@ -8,6 +8,9 @@
 import android.content.Context;
 import android.util.Log;
 
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
 import java.io.ByteArrayOutputStream;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -214,6 +217,11 @@
     }
 
     @Override
+    public ListenableFuture<Void> onInitialized() {
+        return Futures.immediateFuture(null);
+    }
+
+    @Override
     public boolean isInitialized() {
         return true;
     }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java b/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
index 5a97e0b..e8ece47 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
@@ -17,6 +17,7 @@
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
 
 import org.apache.commons.io.FileUtils;
 
@@ -99,7 +100,8 @@
     private Context mContext;
     private VContext mVContext;
     private SyncbaseHierarchy mLocalSB;
-    private boolean mInitialized;
+
+    private SettableFuture<Void> mInitialized;
 
     private String mUsername;
     private String mSyncgroupName;
@@ -110,11 +112,13 @@
 
     @Override
     public void init(Activity activity) {
-        if (isInitialized()) {
-            // Already initialized.
+        // Make sure that the initialization logic runs at most once.
+        if (mInitialized != null) {
             return;
         }
 
+        mInitialized = SettableFuture.create();
+
         if (mVContext == null) {
             if (activity instanceof VAndroidContextMixin) {
                 // In case of the activity inherits from one of the baku-toolkit's base activities,
@@ -160,10 +164,15 @@
     }
 
     @Override
-    public boolean isInitialized() {
+    public ListenableFuture<Void> onInitialized() {
         return mInitialized;
     }
 
+    @Override
+    public boolean isInitialized() {
+        return mInitialized != null && mInitialized.isDone();
+    }
+
     private void getBlessings() {
         ListenableFuture<Blessings> blessingsFuture = BlessingsManager
                 .getBlessings(mContext, "VanadiumBlessings", true);
@@ -325,7 +334,7 @@
             return;
         }
 
-        mInitialized = true;
+        mInitialized.set(null);
 
         // When successfully joined the syncgroup, first register the device information.
         registerDevice();
@@ -424,7 +433,7 @@
 
     @Override
     public DBList<File> getFileList() {
-        if (!mInitialized) {
+        if (!isInitialized()) {
             return new EmptyList<>();
         }
 
@@ -433,7 +442,7 @@
 
     @Override
     public DBList<Device> getDeviceList() {
-        if (!mInitialized) {
+        if (!isInitialized()) {
             return new EmptyList<>();
         }
 
@@ -442,7 +451,7 @@
 
     @Override
     public DBList<DeviceSet> getDeviceSetList() {
-        if (!mInitialized) {
+        if (!isInitialized()) {
             return new EmptyList<>();
         }