From: Damyan Ivanov Date: Sun, 24 Mar 2019 16:08:13 +0000 (+0200) Subject: no direct interface to ObservableList's value X-Git-Tag: v0.8.1~13 X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=commitdiff_plain;h=83cac114e375728080194fb09758b49c50a8119b no direct interface to ObservableList's value --- diff --git a/app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTask.java b/app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTask.java index 12136478..8f8c1dac 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTask.java @@ -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(); diff --git a/app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTaskParams.java b/app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTaskParams.java index fe374c70..ae3a0b9a 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTaskParams.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTaskParams.java @@ -18,13 +18,12 @@ 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 accountList; + ObservableList accountList; boolean showOnlyStarred; - public CommitAccountsTaskParams(List accountList, boolean showOnlyStarred) { + public CommitAccountsTaskParams(ObservableList accountList, boolean showOnlyStarred) { this.accountList = accountList; this.showOnlyStarred = showOnlyStarred; } diff --git a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java index f9297f00..6a2700f3 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java @@ -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 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 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(); } } diff --git a/app/src/main/java/net/ktnx/mobileledger/model/Data.java b/app/src/main/java/net/ktnx/mobileledger/model/Data.java index dbfe0b3b..5965d27d 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/Data.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/Data.java @@ -28,8 +28,8 @@ import java.util.List; public final class Data { public static TransactionList transactions = new TransactionList(); - public static ObservableValue> accounts = - new ObservableValue<>(new ArrayList<>()); + public static ObservableList accounts = + new ObservableList<>(new ArrayList<>()); public static ObservableAtomicInteger backgroundTaskCount = new ObservableAtomicInteger(0); public static ObservableValue lastUpdateDate = new ObservableValue<>(); public static ObservableValue profile = new ObservableValue<>(); @@ -41,11 +41,10 @@ public final class Data { profile.set(newProfile); } public static int getProfileIndex(MobileLedgerProfile profile) { - List 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; diff --git a/app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java b/app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java index c2ad2c2f..b0593f99 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java @@ -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 list) { + public boolean isVisible(List list) { for (LedgerAccount acc : list) { if (acc.isParentOf(this)) { if (!acc.isExpanded()) return false; diff --git a/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java b/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java index 9d87d0c8..1971ca26 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java @@ -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(); } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java index d9df87d5..37785525 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java @@ -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 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); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryViewModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryViewModel.java index 7a7164f3..92b6b8d7 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryViewModel.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryViewModel.java @@ -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); } } } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java index fbfb9f59..0f668cee 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java @@ -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 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 accounts = Data.accounts.get(); List 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: diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java index 135969dc..b7770dcc 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java @@ -491,20 +491,21 @@ public class NewTransactionActivity extends ProfileThemedActivity String profileUUID = c.getString(0); int transactionId = c.getInt(1); - List 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 accounts = tr.getAccounts(); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java index 6f15fa7f..57fe6b99 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java @@ -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(); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfilesRecyclerViewAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfilesRecyclerViewAdapter.java index 10b67dbc..dfffc8f2 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfilesRecyclerViewAdapter.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfilesRecyclerViewAdapter.java @@ -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()); diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java b/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java index e74271af..9e44faef 100644 --- a/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java +++ b/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java @@ -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 extends Observable { +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +public class ObservableList extends Observable implements List { private List list; public ObservableList(List list) { this.list = list; @@ -57,6 +58,7 @@ public class ObservableList extends Observable { public boolean contains(@Nullable Object o) { return list.contains(o); } + @NonNull public Iterator iterator() { return list.iterator(); } @@ -76,6 +78,9 @@ public class ObservableList 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 extends Observable { if (result) forceNotify(); return result; } + public boolean addAllQuietly(int index, Collection c) { + return list.addAll(index, c); + } public boolean removeAll(@NonNull Collection c) { boolean result = list.removeAll(c); if (result) forceNotify();