no direct interface to ObservableList's value
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Sun, 24 Mar 2019 16:08:13 +0000 (18:08 +0200)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Mon, 25 Mar 2019 06:17:36 +0000 (06:17 +0000)
13 files changed:
app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTask.java
app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTaskParams.java
app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java
app/src/main/java/net/ktnx/mobileledger/model/Data.java
app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java
app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryViewModel.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfilesRecyclerViewAdapter.java
app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java

index 1213647..8f8c1da 100644 (file)
@@ -38,16 +38,18 @@ public class CommitAccountsTask
             SQLiteDatabase db = MLDB.getDatabase();
             db.beginTransaction();
             try {
-                for (LedgerAccount acc : params[0].accountList) {
-                    Log.d("CAT", String.format("Setting %s to %s", acc.getName(),
-                            acc.isHiddenByStarToBe() ? "hidden" : "starred"));
-                    db.execSQL("UPDATE accounts SET hidden=? WHERE profile=? AND name=?",
-                            new Object[]{acc.isHiddenByStarToBe() ? 1 : 0, profile, acc.getName()});
-
-                    acc.setHiddenByStar(acc.isHiddenByStarToBe());
-                    if (!params[0].showOnlyStarred || !acc.isHiddenByStar()) newList.add(acc);
+                    for (int i = 0; i < params[0].accountList.size(); i++ ){
+                        LedgerAccount acc = params[0].accountList.get(i);
+                        Log.d("CAT", String.format("Setting %s to %s", acc.getName(),
+                                acc.isHiddenByStarToBe() ? "hidden" : "starred"));
+                        db.execSQL("UPDATE accounts SET hidden=? WHERE profile=? AND name=?",
+                                new Object[]{acc.isHiddenByStarToBe() ? 1 : 0, profile, acc.getName()
+                                });
+
+                        acc.setHiddenByStar(acc.isHiddenByStarToBe());
+                        if (!params[0].showOnlyStarred || !acc.isHiddenByStar()) newList.add(acc);
+                    db.setTransactionSuccessful();
                 }
-                db.setTransactionSuccessful();
             }
             finally {
                 db.endTransaction();
index fe374c7..ae3a0b9 100644 (file)
 package net.ktnx.mobileledger.async;
 
 import net.ktnx.mobileledger.model.LedgerAccount;
-
-import java.util.List;
+import net.ktnx.mobileledger.utils.ObservableList;
 
 public class CommitAccountsTaskParams {
-    List<LedgerAccount> accountList;
+    ObservableList<LedgerAccount> accountList;
     boolean showOnlyStarred;
-    public CommitAccountsTaskParams(List<LedgerAccount> accountList, boolean showOnlyStarred) {
+    public CommitAccountsTaskParams(ObservableList<LedgerAccount> accountList, boolean showOnlyStarred) {
         this.accountList = accountList;
         this.showOnlyStarred = showOnlyStarred;
     }
index f9297f0..6a2700f 100644 (file)
@@ -164,7 +164,7 @@ public class RetrieveTransactionsTask
                                     // state of the database
                                     db.setTransactionSuccessful();
                                     db.endTransaction();
-                                    Data.accounts.set(accountList);
+                                    Data.accounts.setList(accountList);
                                     db.beginTransaction();
                                     continue;
                                 }
@@ -390,6 +390,8 @@ public class RetrieveTransactionsTask
         }
         publishProgress(progress);
         SQLiteDatabase db = MLDB.getDatabase();
+        ArrayList<LedgerAccount> accountList = new ArrayList<>();
+        boolean listFilledOK = false;
         try (InputStream resp = http.getInputStream()) {
             if (http.getResponseCode() != 200)
                 throw new IOException(String.format("HTTP error %d", http.getResponseCode()));
@@ -399,7 +401,6 @@ public class RetrieveTransactionsTask
                 profile.markAccountsAsNotPresent(db);
 
                 AccountListParser parser = new AccountListParser(resp);
-                ArrayList<LedgerAccount> accountList = new ArrayList<>();
 
                 LedgerAccount prevAccount = null;
 
@@ -432,12 +433,16 @@ public class RetrieveTransactionsTask
                 profile.deleteNotPresentAccounts(db);
                 throwIfCancelled();
                 db.setTransactionSuccessful();
-                Data.accounts.set(accountList);
+                listFilledOK = true;
             }
             finally {
                 db.endTransaction();
             }
         }
+        // should not be set in the DB transaction, because of a possible deadlock
+        // with the main and DbOpQueueRunner threads
+        if (listFilledOK) Data.accounts.setList(accountList);
+
         return true;
     }
     private boolean retrieveTransactionList(MobileLedgerProfile profile)
@@ -456,62 +461,61 @@ public class RetrieveTransactionsTask
             default:
                 throw new HTTPException(http.getResponseCode(), http.getResponseMessage());
         }
-        try (SQLiteDatabase db = MLDB.getDatabase()) {
-            try (InputStream resp = http.getInputStream()) {
-                if (http.getResponseCode() != 200)
-                    throw new IOException(String.format("HTTP error %d", http.getResponseCode()));
-                throwIfCancelled();
-                db.beginTransaction();
-                try {
-                    profile.markTransactionsAsNotPresent(db);
+        SQLiteDatabase db = MLDB.getDatabase();
+        try (InputStream resp = http.getInputStream()) {
+            if (http.getResponseCode() != 200)
+                throw new IOException(String.format("HTTP error %d", http.getResponseCode()));
+            throwIfCancelled();
+            db.beginTransaction();
+            try {
+                profile.markTransactionsAsNotPresent(db);
 
-                    int matchedTransactionsCount = 0;
-                    TransactionListParser parser = new TransactionListParser(resp);
+                int matchedTransactionsCount = 0;
+                TransactionListParser parser = new TransactionListParser(resp);
 
-                    int processedTransactionCount = 0;
+                int processedTransactionCount = 0;
 
-                    while (true) {
-                        throwIfCancelled();
-                        ParsedLedgerTransaction parsedTransaction = parser.nextTransaction();
-                        throwIfCancelled();
-                        if (parsedTransaction == null) break;
-                        LedgerTransaction transaction = parsedTransaction.asLedgerTransaction();
-                        if (transaction.existsInDb(db)) {
-                            profile.markTransactionAsPresent(db, transaction);
-                            matchedTransactionsCount++;
-
-                            if (matchedTransactionsCount == MATCHING_TRANSACTIONS_LIMIT) {
-                                profile.markTransactionsBeforeTransactionAsPresent(db, transaction);
-                                progress.setTotal(progress.getProgress());
-                                publishProgress(progress);
-                                db.setTransactionSuccessful();
-                                profile.setLastUpdateStamp();
-                                return true;
-                            }
-                        }
-                        else {
-                            profile.storeTransaction(db, transaction);
-                            matchedTransactionsCount = 0;
-                            progress.setTotal(maxTransactionId);
+                while (true) {
+                    throwIfCancelled();
+                    ParsedLedgerTransaction parsedTransaction = parser.nextTransaction();
+                    throwIfCancelled();
+                    if (parsedTransaction == null) break;
+                    LedgerTransaction transaction = parsedTransaction.asLedgerTransaction();
+                    if (transaction.existsInDb(db)) {
+                        profile.markTransactionAsPresent(db, transaction);
+                        matchedTransactionsCount++;
+
+                        if (matchedTransactionsCount == MATCHING_TRANSACTIONS_LIMIT) {
+                            profile.markTransactionsBeforeTransactionAsPresent(db, transaction);
+                            progress.setTotal(progress.getProgress());
+                            publishProgress(progress);
+                            db.setTransactionSuccessful();
+                            profile.setLastUpdateStamp();
+                            return true;
                         }
-
-                        if ((progress.getTotal() == Progress.INDETERMINATE) ||
-                            (progress.getTotal() < transaction.getId()))
-                            progress.setTotal(transaction.getId());
-
-                        progress.setProgress(++processedTransactionCount);
-                        publishProgress(progress);
+                    }
+                    else {
+                        profile.storeTransaction(db, transaction);
+                        matchedTransactionsCount = 0;
+                        progress.setTotal(maxTransactionId);
                     }
 
-                    throwIfCancelled();
-                    profile.deleteNotPresentTransactions(db);
-                    throwIfCancelled();
-                    db.setTransactionSuccessful();
-                    profile.setLastUpdateStamp();
-                }
-                finally {
-                    db.endTransaction();
+                    if ((progress.getTotal() == Progress.INDETERMINATE) ||
+                        (progress.getTotal() < transaction.getId()))
+                        progress.setTotal(transaction.getId());
+
+                    progress.setProgress(++processedTransactionCount);
+                    publishProgress(progress);
                 }
+
+                throwIfCancelled();
+                profile.deleteNotPresentTransactions(db);
+                throwIfCancelled();
+                db.setTransactionSuccessful();
+                profile.setLastUpdateStamp();
+            }
+            finally {
+                db.endTransaction();
             }
         }
 
index dbfe0b3..5965d27 100644 (file)
@@ -28,8 +28,8 @@ import java.util.List;
 
 public final class Data {
     public static TransactionList transactions = new TransactionList();
-    public static ObservableValue<ArrayList<LedgerAccount>> accounts =
-            new ObservableValue<>(new ArrayList<>());
+    public static ObservableList<LedgerAccount> accounts =
+            new ObservableList<>(new ArrayList<>());
     public static ObservableAtomicInteger backgroundTaskCount = new ObservableAtomicInteger(0);
     public static ObservableValue<Date> lastUpdateDate = new ObservableValue<>();
     public static ObservableValue<MobileLedgerProfile> profile = new ObservableValue<>();
@@ -41,11 +41,10 @@ public final class Data {
         profile.set(newProfile);
     }
     public static int getProfileIndex(MobileLedgerProfile profile) {
-        List<MobileLedgerProfile> list = profiles.getList();
+            for (int i = 0; i < profiles.size(); i++) {
+                MobileLedgerProfile p = profiles.get(i);
+                if (p.equals(profile)) return i;
 
-        for (int i = 0; i < list.size(); i++) {
-            MobileLedgerProfile p = list.get(i);
-            if (p.equals(profile)) return i;
         }
 
         return -1;
index c2ad2c2..b0593f9 100644 (file)
@@ -68,9 +68,9 @@ public class LedgerAccount {
 
         if (level == 0) return true;
 
-        return isVisible(Data.accounts.get());
+        return isVisible(Data.accounts);
     }
-    public boolean isVisible(ArrayList<LedgerAccount> list) {
+    public boolean isVisible(List<LedgerAccount> list) {
         for (LedgerAccount acc : list) {
             if (acc.isParentOf(this)) {
                 if (!acc.isExpanded()) return false;
index 9d87d0c..1971ca2 100644 (file)
@@ -101,11 +101,12 @@ public final class MobileLedgerProfile {
         db.beginTransaction();
         try {
             int orderNo = 0;
-            for (MobileLedgerProfile p : Data.profiles.getList()) {
-                db.execSQL("update profiles set order_no=? where uuid=?",
-                        new Object[]{orderNo, p.getUuid()});
-                p.orderNo = orderNo;
-                orderNo++;
+                for (int i = 0; i < Data.profiles.size(); i++) {
+                    MobileLedgerProfile p = Data.profiles.get(i);
+                    db.execSQL("update profiles set order_no=? where uuid=?",
+                            new Object[]{orderNo, p.getUuid()});
+                    p.orderNo = orderNo;
+                    orderNo++;
             }
             db.setTransactionSuccessful();
         }
index d9df87d..3778552 100644 (file)
@@ -32,8 +32,6 @@ import net.ktnx.mobileledger.R;
 import net.ktnx.mobileledger.model.Data;
 import net.ktnx.mobileledger.model.LedgerAccount;
 
-import java.util.List;
-
 import androidx.annotation.NonNull;
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.recyclerview.widget.RecyclerView;
@@ -47,9 +45,8 @@ public class AccountSummaryAdapter
     }
 
     public void onBindViewHolder(@NonNull LedgerRowHolder holder, int position) {
-        List<LedgerAccount> accounts = Data.accounts.get();
-        if (position < accounts.size()) {
-            LedgerAccount acc = accounts.get(position);
+        if (position < Data.accounts.size()) {
+            LedgerAccount acc = Data.accounts.get(position);
             Context ctx = holder.row.getContext();
             Resources rm = ctx.getResources();
 
@@ -96,10 +93,13 @@ public class AccountSummaryAdapter
 
     @Override
     public int getItemCount() {
-        return Data.accounts.get().size() + 1;
+        return Data.accounts.size() + 1;
     }
     public void startSelection() {
-        for (LedgerAccount acc : Data.accounts.get()) acc.setHiddenByStarToBe(acc.isHiddenByStar());
+        for (int i = 0; i < Data.accounts.size(); i++ ) {
+            LedgerAccount acc = Data.accounts.get(i);
+            acc.setHiddenByStarToBe(acc.isHiddenByStar());
+        }
         this.selectionActive = true;
         notifyDataSetChanged();
     }
@@ -114,14 +114,15 @@ public class AccountSummaryAdapter
     }
 
     public void selectItem(int position) {
-        LedgerAccount acc = Data.accounts.get().get(position);
+        LedgerAccount acc = Data.accounts.get(position);
         acc.toggleHiddenToBe();
         toggleChildrenOf(acc, acc.isHiddenByStarToBe(), position);
         notifyItemChanged(position);
     }
     void toggleChildrenOf(LedgerAccount parent, boolean hiddenToBe, int parentPosition) {
         int i = parentPosition + 1;
-        for (LedgerAccount acc : Data.accounts.get()) {
+        for (int j = 0; j < Data.accounts.size(); j++) {
+            LedgerAccount acc = Data.accounts.get(j);
             if (acc.getName().startsWith(parent.getName() + ":")) {
                 acc.setHiddenByStarToBe(hiddenToBe);
                 notifyItemChanged(i);
index 7a7164f..92b6b8d 100644 (file)
@@ -35,7 +35,7 @@ public class AccountSummaryViewModel extends ViewModel {
     static void commitSelections(Context context) {
         CAT task = new CAT();
         task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
-                new CommitAccountsTaskParams(Data.accounts.get(), Data.optShowOnlyStarred.get()));
+                new CommitAccountsTaskParams(Data.accounts, Data.optShowOnlyStarred.get()));
     }
     static public void scheduleAccountListReload() {
         if (Data.profile.get() == null) return;
@@ -51,7 +51,7 @@ public class AccountSummaryViewModel extends ViewModel {
             super.onPostExecute(list);
             if (list != null) {
                 Log.d("acc", "setting updated account list");
-                Data.accounts.set(list);
+                Data.accounts.setList(list);
             }
         }
     }
@@ -62,7 +62,7 @@ public class AccountSummaryViewModel extends ViewModel {
             super.onPostExecute(list);
             if (list != null) {
                 Log.d("acc", "setting new account list");
-                Data.accounts.set(list);
+                Data.accounts.setList(list);
             }
         }
     }
index fbfb9f5..0f668ce 100644 (file)
@@ -54,7 +54,6 @@ import net.ktnx.mobileledger.utils.MLDB;
 
 import java.lang.ref.WeakReference;
 import java.text.DateFormat;
-import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Observable;
@@ -324,7 +323,7 @@ public class MainActivity extends ProfileThemedActivity {
 
         profile = MobileLedgerProfile.loadAllFromDB(profileUUID);
 
-        if (Data.profiles.getList().isEmpty()) {
+        if (Data.profiles.isEmpty()) {
             findViewById(R.id.no_profiles_layout).setVisibility(View.VISIBLE);
             findViewById(R.id.pager_layout).setVisibility(View.GONE);
             return;
@@ -589,46 +588,49 @@ public class MainActivity extends ProfileThemedActivity {
 
                     // removing all child accounts from the view
                     int start = -1, count = 0;
-                    int i = 0;
-                    final ArrayList<LedgerAccount> accountList = Data.accounts.get();
-                    for (LedgerAccount a : accountList) {
-                        if (acc.isParentOf(a)) {
-                            if (start == -1) {
-                                start = i;
+                        for (int i = 0; i < Data.accounts.size(); i++) {
+                            if (acc.isParentOf(Data.accounts.get(i))) {
+//                                Log.d("accounts", String.format("Found a child '%s' at position %d",
+//                                        Data.accounts.get(i).getName(), i));
+                                if (start == -1) {
+                                    start = i;
+                                }
+                                count++;
                             }
-                            count++;
-                        }
-                        else {
-                            if (start != -1) {
-                                break;
+                            else {
+                                if (start != -1) {
+//                                    Log.d("accounts",
+//                                            String.format("Found a non-child '%s' at position %d",
+//                                                    Data.accounts.get(i).getName(), i));
+                                    break;
+                                }
                             }
                         }
-                        i++;
-                    }
 
-                    if (start != -1) {
-                        for (int j = 0; j < count; j++) {
-                            Log.d("accounts", String.format("Removing item %d: %s", start + j,
-                                    accountList.get(start).getName()));
-                            accountList.remove(start);
-                        }
+                        if (start != -1) {
+                            for (int j = 0; j < count; j++) {
+//                                Log.d("accounts", String.format("Removing item %d: %s", start + j,
+//                                        Data.accounts.get(start).getName()));
+                                Data.accounts.removeQuietly(start);
+                            }
 
-                        mAccountSummaryFragment.modelAdapter.notifyItemRangeRemoved(start, count);
+                            mAccountSummaryFragment.modelAdapter
+                                    .notifyItemRangeRemoved(start, count);
                     }
                 }
                 else {
                     Log.d("accounts", String.format("Expanding account '%s'", acc.getName()));
                     arrow.setRotation(180);
                     animator.rotationBy(-180);
-                    ArrayList<LedgerAccount> accounts = Data.accounts.get();
                     List<LedgerAccount> children =
                             Data.profile.get().loadVisibleChildAccountsOf(acc);
-                    int parentPos = accounts.indexOf(acc);
-                    if (parentPos == -1) throw new RuntimeException(
-                            "Can't find index of clicked account " + acc.getName());
-                    accounts.addAll(parentPos + 1, children);
-                    mAccountSummaryFragment.modelAdapter
-                            .notifyItemRangeInserted(parentPos + 1, children.size());
+                        int parentPos = Data.accounts.indexOf(acc);
+                        if (parentPos != -1) {
+                            // may have disappeared in a concurrent refresh operation
+                            Data.accounts.addAllQuietly(parentPos + 1, children);
+                            mAccountSummaryFragment.modelAdapter
+                                    .notifyItemRangeInserted(parentPos + 1, children.size());
+                        }
                 }
                 break;
             case R.id.account_row_acc_amounts:
index 135969d..b7770dc 100644 (file)
@@ -491,20 +491,21 @@ public class NewTransactionActivity extends ProfileThemedActivity
 
             String profileUUID = c.getString(0);
             int transactionId = c.getInt(1);
-            List<MobileLedgerProfile> profiles = Data.profiles.getList();
-            MobileLedgerProfile profile = null;
-            for (int i = 0; i < profiles.size(); i++) {
-                MobileLedgerProfile p = profiles.get(i);
-                if (p.getUuid().equals(profileUUID)) {
-                    profile = p;
-                    break;
+            LedgerTransaction tr;
+                MobileLedgerProfile profile = null;
+                for (int i = 0; i < Data.profiles.size(); i++) {
+                    MobileLedgerProfile p = Data.profiles.get(i);
+                    if (p.getUuid().equals(profileUUID)) {
+                        profile = p;
+                        break;
                 }
             }
-            if (profile == null) throw new RuntimeException(String.format(
-                    "Unable to find profile %s, which is supposed to contain " +
-                    "transaction %d with description %s", profileUUID, transactionId, description));
+                if (profile == null) throw new RuntimeException(String.format(
+                        "Unable to find profile %s, which is supposed to contain " +
+                        "transaction %d with description %s", profileUUID, transactionId,
+                        description));
 
-            LedgerTransaction tr = profile.loadTransaction(transactionId);
+                tr = profile.loadTransaction(transactionId);
             int i = 0;
             table = findViewById(R.id.new_transaction_accounts_table);
             ArrayList<LedgerTransactionAccount> accounts = tr.getAccounts();
index 6f15fa7..57fe6b9 100644 (file)
@@ -158,7 +158,7 @@ public class ProfileDetailFragment extends Fragment implements HueRingDialog.Hue
                 MobileLedgerProfile.storeProfilesOrder();
 
                 // first profile ever?
-                if (Data.profiles.getList().size() == 1) Data.profile.set(mProfile);
+                if (Data.profiles.size() == 1) Data.profile.set(mProfile);
             }
 
             Activity activity = getActivity();
index 10b67db..dfffc8f 100644 (file)
@@ -74,7 +74,7 @@ public class ProfilesRecyclerViewAdapter
             public boolean onMove(@NonNull RecyclerView recyclerView,
                                   @NonNull RecyclerView.ViewHolder viewHolder,
                                   @NonNull RecyclerView.ViewHolder target) {
-                Collections.swap(Data.profiles.getList(), viewHolder.getAdapterPosition(),
+                Collections.swap(Data.profiles, viewHolder.getAdapterPosition(),
                         target.getAdapterPosition());
                 MobileLedgerProfile.storeProfilesOrder();
                 notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
index e74271a..9e44fae 100644 (file)
@@ -18,9 +18,6 @@
 package net.ktnx.mobileledger.utils;
 
 import android.os.Build;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import android.util.Log;
 
 import java.util.Collection;
@@ -35,7 +32,11 @@ import java.util.function.Predicate;
 import java.util.function.UnaryOperator;
 import java.util.stream.Stream;
 
-public class ObservableList<T> extends Observable {
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+public class ObservableList<T> extends Observable implements List<T> {
     private List<T> list;
     public ObservableList(List<T> list) {
         this.list = list;
@@ -57,6 +58,7 @@ public class ObservableList<T> extends Observable {
     public boolean contains(@Nullable Object o) {
         return list.contains(o);
     }
+    @NonNull
     public Iterator<T> iterator() {
         return list.iterator();
     }
@@ -76,6 +78,9 @@ public class ObservableList<T> extends Observable {
         if (result) forceNotify();
         return result;
     }
+    public T removeQuietly(int index) {
+        return list.remove(index);
+    }
     public boolean containsAll(@NonNull Collection<?> c) {
         return list.containsAll(c);
     }
@@ -89,6 +94,9 @@ public class ObservableList<T> extends Observable {
         if (result) forceNotify();
         return result;
     }
+    public boolean addAllQuietly(int index, Collection<? extends T> c) {
+        return list.addAll(index, c);
+    }
     public boolean removeAll(@NonNull Collection<?> c) {
         boolean result = list.removeAll(c);
         if (result) forceNotify();