From: Damyan Ivanov Date: Fri, 3 May 2019 15:09:52 +0000 (+0300) Subject: migrate a bunch of globals to LiveData X-Git-Tag: v0.10.0~60 X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=commitdiff_plain;h=0fc2ddc465cd9b9314ae336e69535020a96a7fbc migrate a bunch of globals to LiveData --- 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 2c3d90f8..cb13d6db 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTask.java @@ -34,7 +34,7 @@ public class CommitAccountsTask protected ArrayList doInBackground(CommitAccountsTaskParams... params) { Data.backgroundTaskStarted(); ArrayList newList = new ArrayList<>(); - String profile = Data.profile.get().getUuid(); + String profile = Data.profile.getValue().getUuid(); try { SQLiteDatabase db = MLDB.getDatabase(); 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 20a8a20b..9db9c9df 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java @@ -73,8 +73,11 @@ public class RetrieveTransactionsTask private Pattern reAccountName = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\""); private Pattern reAccountValue = Pattern.compile( "\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?"); - public RetrieveTransactionsTask(WeakReference contextRef) { + private MobileLedgerProfile profile; + public RetrieveTransactionsTask(WeakReference contextRef, + MobileLedgerProfile profile) { this.contextRef = contextRef; + this.profile = profile; } private static void L(String msg) { //debug("transaction-parser", msg); @@ -107,7 +110,7 @@ public class RetrieveTransactionsTask if (context == null) return; context.onRetrieveDone(null); } - private String retrieveTransactionListLegacy(MobileLedgerProfile profile) + private String retrieveTransactionListLegacy() throws IOException, ParseException, HTTPException { Progress progress = new Progress(); int maxTransactionId = Progress.INDETERMINATE; @@ -393,7 +396,7 @@ public class RetrieveTransactionsTask new String[]{profile.getUuid()}); db.execSQL("update accounts set keep=0 where profile=?;", new String[]{profile.getUuid()}); } - private boolean retrieveAccountList(MobileLedgerProfile profile) + private boolean retrieveAccountList() throws IOException, HTTPException { Progress progress = new Progress(); @@ -481,7 +484,7 @@ public class RetrieveTransactionsTask return true; } - private boolean retrieveTransactionList(MobileLedgerProfile profile) + private boolean retrieveTransactionList() throws IOException, ParseException, HTTPException { Progress progress = new Progress(); int maxTransactionId = Progress.INDETERMINATE; @@ -589,11 +592,10 @@ public class RetrieveTransactionsTask @SuppressLint("DefaultLocale") @Override protected String doInBackground(Void... params) { - MobileLedgerProfile profile = Data.profile.get(); Data.backgroundTaskStarted(); try { - if (!retrieveAccountList(profile) || !retrieveTransactionList(profile)) - return retrieveTransactionListLegacy(profile); + if (!retrieveAccountList() || !retrieveTransactionList()) + return retrieveTransactionListLegacy(); return null; } catch (MalformedURLException e) { diff --git a/app/src/main/java/net/ktnx/mobileledger/async/UpdateAccountsTask.java b/app/src/main/java/net/ktnx/mobileledger/async/UpdateAccountsTask.java index bd179f29..88ff97b8 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/UpdateAccountsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/UpdateAccountsTask.java @@ -34,7 +34,8 @@ public class UpdateAccountsTask extends AsyncTask doInBackground(Void... params) { Data.backgroundTaskStarted(); try { - MobileLedgerProfile profile = Data.profile.get(); + MobileLedgerProfile profile = Data.profile.getValue(); + assert profile != null; String profileUUID = profile.getUuid(); boolean onlyStarred = Data.optShowOnlyStarred.get(); ArrayList newList = new ArrayList<>(); diff --git a/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java b/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java index dc6536a1..1ed7adbe 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java @@ -36,7 +36,7 @@ import static net.ktnx.mobileledger.utils.Logger.debug; public class UpdateTransactionsTask extends AsyncTask { protected String doInBackground(String[] filterAccName) { - final MobileLedgerProfile profile = Data.profile.get(); + final MobileLedgerProfile profile = Data.profile.getValue(); if (profile == null) return "Profile not configured"; String profile_uuid = profile.getUuid(); 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 0902ced5..5e08297e 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/Data.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/Data.java @@ -21,6 +21,8 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import net.ktnx.mobileledger.utils.LockHolder; +import net.ktnx.mobileledger.utils.Locker; +import net.ktnx.mobileledger.utils.Logger; import net.ktnx.mobileledger.utils.MLDB; import net.ktnx.mobileledger.utils.ObservableList; import net.ktnx.mobileledger.utils.ObservableValue; @@ -38,14 +40,15 @@ public final class Data { public static ObservableList transactions = new ObservableList<>(new ArrayList<>()); public static ObservableList accounts = new ObservableList<>(new ArrayList<>()); - private static AtomicInteger backgroundTaskCount = new AtomicInteger(0); public static MutableLiveData backgroundTasksRunning = new MutableLiveData<>(false); public static MutableLiveData lastUpdateDate = new MutableLiveData<>(); - public static ObservableValue profile = new ObservableValue<>(); - public static ObservableList profiles = - new ObservableList<>(new ArrayList<>()); + public static MutableLiveData profile = new MutableLiveData<>(); + public static MutableLiveData> profiles = + new MutableLiveData<>(new ArrayList<>()); public static ObservableValue optShowOnlyStarred = new ObservableValue<>(); public static MutableLiveData accountFilter = new MutableLiveData<>(); + private static AtomicInteger backgroundTaskCount = new AtomicInteger(0); + private static Locker profilesLocker = new Locker(); public static void backgroundTaskStarted() { int cnt = backgroundTaskCount.incrementAndGet(); debug("data", @@ -62,12 +65,14 @@ public final class Data { } public static void setCurrentProfile(MobileLedgerProfile newProfile) { MLDB.setOption(MLDB.OPT_PROFILE_UUID, newProfile.getUuid()); - profile.set(newProfile); + profile.postValue(newProfile); } public static int getProfileIndex(MobileLedgerProfile profile) { - try (LockHolder ignored = profiles.lockForReading()) { - for (int i = 0; i < profiles.size(); i++) { - MobileLedgerProfile p = profiles.get(i); + try (LockHolder ignored = profilesLocker.lockForReading()) { + List prList = profiles.getValue(); + assert prList != null; + for (int i = 0; i < prList.size(); i++) { + MobileLedgerProfile p = prList.get(i); if (p.equals(profile)) return i; } @@ -76,9 +81,11 @@ public final class Data { } @SuppressWarnings("WeakerAccess") public static int getProfileIndex(String profileUUID) { - try (LockHolder ignored = profiles.lockForReading()) { - for (int i = 0; i < profiles.size(); i++) { - MobileLedgerProfile p = profiles.get(i); + try (LockHolder ignored = profilesLocker.lockForReading()) { + List prList = profiles.getValue(); + assert prList != null; + for (int i = 0; i < prList.size(); i++) { + MobileLedgerProfile p = prList.get(i); if (p.getUuid().equals(profileUUID)) return i; } @@ -100,14 +107,19 @@ public final class Data { } public static MobileLedgerProfile getProfile(String profileUUID) { MobileLedgerProfile profile; - if (profiles.isEmpty()) { - profile = MobileLedgerProfile.loadAllFromDB(profileUUID); - } - else { - try (LockHolder ignored = profiles.lockForReading()) { + try (LockHolder readLock = profilesLocker.lockForReading()) { + List prList = profiles.getValue(); + assert prList != null; + if (prList.isEmpty()) { + readLock.close(); + try (LockHolder ignored = profilesLocker.lockForWriting()) { + profile = MobileLedgerProfile.loadAllFromDB(profileUUID); + } + } + else { int i = getProfileIndex(profileUUID); if (i == -1) i = 0; - profile = profiles.get(i); + profile = prList.get(i); } } return profile; diff --git a/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java b/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java index 1685c1a7..fd900efa 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java @@ -32,8 +32,6 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.Date; -import static net.ktnx.mobileledger.utils.Logger.debug; - public class LedgerTransaction { private static final String DIGEST_TYPE = "SHA-256"; public final Comparator comparator = @@ -68,7 +66,7 @@ public class LedgerTransaction { dataLoaded = false; } public LedgerTransaction(Integer id, Date date, String description) { - this(id, date, description, Data.profile.get()); + this(id, date, description, Data.profile.getValue()); } public LedgerTransaction(Date date, String description) { this(null, date, description); 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 4822a92a..a279f34f 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java @@ -22,7 +22,6 @@ import android.database.sqlite.SQLiteDatabase; import net.ktnx.mobileledger.async.DbOpQueue; import net.ktnx.mobileledger.utils.Globals; -import net.ktnx.mobileledger.utils.LockHolder; import net.ktnx.mobileledger.utils.MLDB; import java.util.ArrayList; @@ -68,7 +67,7 @@ public final class MobileLedgerProfile { // returns the profile with the given UUID public static MobileLedgerProfile loadAllFromDB(String currentProfileUUID) { MobileLedgerProfile result = null; - List list = new ArrayList<>(); + ArrayList list = new ArrayList<>(); SQLiteDatabase db = MLDB.getDatabase(); try (Cursor cursor = db.rawQuery("SELECT uuid, name, url, use_authentication, auth_user, " + "auth_password, permit_posting, theme, order_no, " + @@ -90,7 +89,7 @@ public final class MobileLedgerProfile { if (item.getUuid().equals(currentProfileUUID)) result = item; } } - Data.profiles.setList(list); + Data.profiles.setValue(list); return result; } public static void storeProfilesOrder() { @@ -98,14 +97,11 @@ public final class MobileLedgerProfile { db.beginTransaction(); try { int orderNo = 0; - try (LockHolder lh = Data.profiles.lockForReading()) { - 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++; - } + for (MobileLedgerProfile p : Data.profiles.getValue()) { + 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/AccountSummaryFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java index 5796b336..5534acac 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java @@ -97,7 +97,7 @@ public class AccountSummaryFragment extends MobileLedgerListFragment { Colors.themeWatch.observe(this, this::themeChanged); swiper.setOnRefreshListener(() -> { debug("ui", "refreshing accounts via swipe"); - mActivity.scheduleTransactionListRetrieval(); + Data.scheduleTransactionListRetrieval(mActivity); }); Data.accounts.addObserver( 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 2ebc6626..3e9af7ba 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 @@ -39,7 +39,7 @@ public class AccountSummaryViewModel extends ViewModel { new CommitAccountsTaskParams(Data.accounts, Data.optShowOnlyStarred.get())); } static public void scheduleAccountListReload() { - if (Data.profile.get() == null) return; + if (Data.profile.getValue() == null) return; UAT task = new UAT(); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 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 e2857a18..3f0a427a 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 @@ -103,13 +103,12 @@ public class MainActivity extends ProfileThemedActivity { private int mCurrentPage; private String mAccountFilter; private boolean mBackMeansToAccountList = false; - private Observer profileObserver; - private Observer profilesObserver; private Toolbar mToolbar; private DrawerLayout.SimpleDrawerListener drawerListener; private ActionBarDrawerToggle barDrawerToggle; private ViewPager.SimpleOnPageChangeListener pageChangeListener; private Observer editingProfilesObserver; + private MobileLedgerProfile profile; @Override protected void onStart() { super.onStart(); @@ -129,10 +128,6 @@ public class MainActivity extends ProfileThemedActivity { @Override protected void onDestroy() { mSectionsPagerAdapter = null; - Data.profile.deleteObserver(profileObserver); - profileObserver = null; - Data.profiles.deleteObserver(profilesObserver); - profilesObserver = null; RecyclerView root = findViewById(R.id.nav_profile_list); if (root != null) root.setAdapter(null); if (drawer != null) drawer.removeDrawerListener(drawerListener); @@ -177,15 +172,9 @@ public class MainActivity extends ProfileThemedActivity { mToolbar = findViewById(R.id.toolbar); setSupportActionBar(mToolbar); - if (profileObserver == null) { - profileObserver = (o, arg) -> onProfileChanged(arg); - Data.profile.addObserver(profileObserver); - } + Data.profile.observe(this, this::onProfileChanged); - if (profilesObserver == null) { - profilesObserver = (o, arg) -> onProfileListChanged(arg); - Data.profiles.addObserver(profilesObserver); - } + Data.profiles.observe(this, this::onProfileListChanged); if (barDrawerToggle == null) { barDrawerToggle = new ActionBarDrawerToggle(this, drawer, mToolbar, @@ -313,11 +302,7 @@ public class MainActivity extends ProfileThemedActivity { findViewById(R.id.nav_profile_list_head_layout) .setOnClickListener(this::navProfilesHeadClicked); findViewById(R.id.nav_profiles_label).setOnClickListener(this::navProfilesHeadClicked); - boolean initialStart = Data.profile.get() == null; setupProfile(); - if (!initialStart) onProfileChanged(null); - - updateLastUpdateTextFromDB(); } private void scheduleDataRetrievalIfStale(Date lastUpdate) { long now = new Date().getTime(); @@ -330,103 +315,97 @@ public class MainActivity extends ProfileThemedActivity { scheduleTransactionListRetrieval(); } } - private void createShortcuts() { + private void createShortcuts(List list) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; List shortcuts = new ArrayList<>(); - try (LockHolder ignored = Data.profiles.lockForReading()) { - for (int i = 0; i < Data.profiles.size(); i++) { - MobileLedgerProfile p = Data.profiles.get(i); - if (!p.isPostingPermitted()) continue; - - ShortcutInfo si = new ShortcutInfo.Builder(this, "new_transaction_" + p.getUuid()) - .setShortLabel(p.getName()) - .setIcon(Icon.createWithResource(this, R.drawable.svg_thick_plus_white)) - .setIntent(new Intent(Intent.ACTION_VIEW, null, this, - NewTransactionActivity.class).putExtra("profile_uuid", p.getUuid())) - .setRank(i).build(); - shortcuts.add(si); - } + int i = 0; + for (MobileLedgerProfile p : list) { + if (!p.isPostingPermitted()) continue; + + ShortcutInfo si = new ShortcutInfo.Builder(this, "new_transaction_" + p.getUuid()) + .setShortLabel(p.getName()) + .setIcon(Icon.createWithResource(this, R.drawable.svg_thick_plus_white)) + .setIntent( + new Intent(Intent.ACTION_VIEW, null, this, NewTransactionActivity.class) + .putExtra("profile_uuid", p.getUuid())).setRank(i).build(); + shortcuts.add(si); + i++; } ShortcutManager sm = getSystemService(ShortcutManager.class); sm.setDynamicShortcuts(shortcuts); } - private void onProfileListChanged(Object arg) { + private void onProfileListChanged(List newList) { + if (newList.isEmpty()) { + findViewById(R.id.no_profiles_layout).setVisibility(View.VISIBLE); + findViewById(R.id.pager_layout).setVisibility(View.GONE); + findViewById(R.id.loading_layout).setVisibility(View.GONE); + return; + } + + findViewById(R.id.pager_layout).setVisibility(View.VISIBLE); + findViewById(R.id.no_profiles_layout).setVisibility(View.GONE); + findViewById(R.id.loading_layout).setVisibility(View.GONE); + findViewById(R.id.nav_profile_list).setMinimumHeight( - (int) (getResources().getDimension(R.dimen.thumb_row_height) * - Data.profiles.size())); + (int) (getResources().getDimension(R.dimen.thumb_row_height) * newList.size())); debug("profiles", "profile list changed"); - if (arg == null) mProfileListAdapter.notifyDataSetChanged(); - else mProfileListAdapter.notifyItemChanged((int) arg); - - createShortcuts(); - } - private void onProfileChanged(Object arg) { - MobileLedgerProfile profile = Data.profile.get(); - MainActivity.this.runOnUiThread(() -> { - - boolean haveProfile = profile != null; - findViewById(R.id.no_profiles_layout) - .setVisibility(haveProfile ? View.GONE : View.VISIBLE); - findViewById(R.id.pager_layout) - .setVisibility(haveProfile ? View.VISIBLE : View.VISIBLE); - - if (profile == null) MainActivity.this.setTitle(R.string.app_name); - else MainActivity.this.setTitle(profile.getName()); - MainActivity.this.updateLastUpdateTextFromDB(); - int old_index = -1; - int new_index = -1; - if (arg != null) { - MobileLedgerProfile old = (MobileLedgerProfile) arg; - old_index = Data.getProfileIndex(old); - new_index = Data.getProfileIndex(profile); - } + mProfileListAdapter.notifyDataSetChanged(); - if ((old_index != -1) && (new_index != -1)) { - mProfileListAdapter.notifyItemChanged(old_index); - mProfileListAdapter.notifyItemChanged(new_index); - } - else mProfileListAdapter.notifyDataSetChanged(); - - MainActivity.this.collapseProfileList(); - - int newProfileTheme = (profile == null) ? -1 : profile.getThemeId(); - if (newProfileTheme != Colors.profileThemeId) { - debug("profiles", String.format("profile theme %d → %d", Colors.profileThemeId, - newProfileTheme)); - MainActivity.this.profileThemeChanged(); - Colors.profileThemeId = newProfileTheme; - // profileThemeChanged would restart the activity, so no need to reload the - // data sets below - return; - } - drawer.closeDrawers(); + createShortcuts(newList); + } + /** called when the current profile has changed */ + private void onProfileChanged(MobileLedgerProfile profile) { + boolean haveProfile = profile != null; + findViewById(R.id.no_profiles_layout).setVisibility(haveProfile ? View.GONE : View.VISIBLE); + findViewById(R.id.pager_layout).setVisibility(haveProfile ? View.VISIBLE : View.VISIBLE); - Data.transactions.clear(); - debug("transactions", "requesting list reload"); - TransactionListViewModel.scheduleTransactionListReload(); + if (haveProfile) setTitle(profile.getName()); + else setTitle(R.string.app_name); + + this.profile = profile; + + mProfileListAdapter.notifyDataSetChanged(); - Data.accounts.clear(); - AccountSummaryViewModel.scheduleAccountListReload(); + int newProfileTheme = haveProfile ? profile.getThemeId() : -1; + if (newProfileTheme != Colors.profileThemeId) { + debug("profiles", + String.format(Locale.ENGLISH, "profile theme %d → %d", Colors.profileThemeId, + newProfileTheme)); + MainActivity.this.profileThemeChanged(); + Colors.profileThemeId = newProfileTheme; + // profileThemeChanged would restart the activity, so no need to reload the + // data sets below + return; + } + collapseProfileList(); + + drawer.closeDrawers(); - if (profile == null) { + Data.transactions.clear(); + debug("transactions", "requesting list reload"); + TransactionListViewModel.scheduleTransactionListReload(); + + Data.accounts.clear(); + AccountSummaryViewModel.scheduleAccountListReload(); + + if (haveProfile) { + if (profile.isPostingPermitted()) { mToolbar.setSubtitle(null); - fab.hide(); + fab.show(); } else { - if (profile.isPostingPermitted()) { - mToolbar.setSubtitle(null); - fab.show(); - } - else { - mToolbar.setSubtitle(R.string.profile_subitlte_read_only); - fab.hide(); - } + mToolbar.setSubtitle(R.string.profile_subitlte_read_only); + fab.hide(); } + } + else { + mToolbar.setSubtitle(null); + fab.hide(); + } - updateLastUpdateTextFromDB(); - }); + updateLastUpdateTextFromDB(); } private void updateLastUpdateDisplay(Date newValue) { LinearLayout l = findViewById(R.id.transactions_last_update_layout); @@ -444,20 +423,6 @@ public class MainActivity extends ProfileThemedActivity { scheduleDataRetrievalIfStale(newValue); } - @Override - public void finish() { - if (profilesObserver != null) { - Data.profiles.deleteObserver(profilesObserver); - profilesObserver = null; - } - - if (profileObserver != null) { - Data.profile.deleteObserver(profileObserver); - profileObserver = null; - } - - super.finish(); - } private void profileThemeChanged() { setupProfileColors(); @@ -465,6 +430,11 @@ public class MainActivity extends ProfileThemedActivity { onSaveInstanceState(bundle); // restart activity to reflect theme change finish(); + + // un-hook all observed LiveData + Data.profile.removeObservers(this); + Data.profiles.removeObservers(this); + Data.lastUpdateDate.removeObservers(this); Intent intent = new Intent(this, this.getClass()); intent.putExtra(BUNDLE_SAVED_STATE, bundle); startActivity(intent); @@ -481,24 +451,10 @@ public class MainActivity extends ProfileThemedActivity { } private void setupProfile() { String profileUUID = MLDB.getOption(MLDB.OPT_PROFILE_UUID, null); - MobileLedgerProfile profile; - - profile = Data.getProfile(profileUUID); - - if (Data.profiles.isEmpty()) { - findViewById(R.id.no_profiles_layout).setVisibility(View.VISIBLE); - findViewById(R.id.pager_layout).setVisibility(View.GONE); - return; - } - - findViewById(R.id.pager_layout).setVisibility(View.VISIBLE); - findViewById(R.id.no_profiles_layout).setVisibility(View.GONE); - - if (profile == null) profile = Data.profiles.get(0); - - if (profile == null) throw new AssertionError("profile must have a value"); + MobileLedgerProfile startupProfile; - Data.setCurrentProfile(profile); + startupProfile = Data.getProfile(profileUUID); + Data.setCurrentProfile(startupProfile); } public void fabNewTransactionClicked(View view) { Intent intent = new Intent(this, NewTransactionActivity.class); @@ -568,7 +524,6 @@ public class MainActivity extends ProfileThemedActivity { } } public void updateLastUpdateTextFromDB() { - final MobileLedgerProfile profile = Data.profile.get(); long last_update = (profile != null) ? profile.getLongOption(MLDB.OPT_LAST_SCRAPE, 0L) : 0; debug("transactions", String.format(Locale.ENGLISH, "Last update = %d", last_update)); @@ -630,7 +585,6 @@ public class MainActivity extends ProfileThemedActivity { } } public void fabShouldShow() { - MobileLedgerProfile profile = Data.profile.get(); if ((profile != null) && profile.isPostingPermitted()) fab.show(); } public void fabHide() { @@ -653,9 +607,10 @@ public class MainActivity extends ProfileThemedActivity { profileListHeadArrow.startAnimation(AnimationUtils.loadAnimation(this, R.anim.rotate_180)); profileListHeadMore.setVisibility(View.VISIBLE); profileListHeadMore.startAnimation(AnimationUtils.loadAnimation(this, R.anim.fade_in)); + final ArrayList profiles = Data.profiles.getValue(); findViewById(R.id.nav_profile_list).setMinimumHeight( (int) (getResources().getDimension(R.dimen.thumb_row_height) * - Data.profiles.size())); + (profiles != null ? profiles.size() : 0))); } private void collapseProfileList() { boolean wasExpanded = profileListExpanded; @@ -726,7 +681,7 @@ public class MainActivity extends ProfileThemedActivity { acc.toggleExpanded(); DbOpQueue.add("update accounts set expanded=? where name=? and profile=?", - new Object[]{acc.isExpanded(), acc.getName(), Data.profile.get().getUuid() + new Object[]{acc.isExpanded(), acc.getName(), profile.getUuid() }); if (wasExpanded) { @@ -772,8 +727,7 @@ public class MainActivity extends ProfileThemedActivity { debug("accounts", String.format("Expanding account '%s'", acc.getName())); arrow.setRotation(180); animator.rotationBy(-180); - List children = - Data.profile.get().loadVisibleChildAccountsOf(acc); + List children = profile.loadVisibleChildAccountsOf(acc); try (LockHolder ignored = Data.accounts.lockForWriting()) { int parentPos = Data.accounts.indexOf(acc); if (parentPos != -1) { @@ -791,7 +745,7 @@ public class MainActivity extends ProfileThemedActivity { DbOpQueue .add("update accounts set amounts_expanded=? where name=? and profile=?", new Object[]{acc.amountsExpanded(), acc.getName(), - Data.profile.get().getUuid() + profile.getUuid() }); Data.accounts.triggerItemChangedNotification(acc); } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileDetailActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileDetailActivity.java index 40051dac..1568c65f 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileDetailActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileDetailActivity.java @@ -26,6 +26,7 @@ import net.ktnx.mobileledger.model.MobileLedgerProfile; import net.ktnx.mobileledger.ui.profiles.ProfileDetailFragment; import net.ktnx.mobileledger.utils.Colors; +import java.util.ArrayList; import java.util.Locale; import androidx.appcompat.app.ActionBar; @@ -47,12 +48,16 @@ public class ProfileDetailActivity extends CrashReportingActivity { final int index = getIntent().getIntExtra(ProfileDetailFragment.ARG_ITEM_ID, -1); if (index != -1) { - profile = Data.profiles.get(index); - if (profile == null) throw new AssertionError( - String.format("Can't get profile " + "(index:%d) from the global list", index)); + ArrayList profiles = Data.profiles.getValue(); + if (profiles != null) { + profile = profiles.get(index); + if (profile == null) throw new AssertionError( + String.format("Can't get profile " + "(index:%d) from the global list", + index)); debug("profiles", String.format(Locale.ENGLISH, "Editing profile %s (%s); hue=%d", profile.getName(), profile.getUuid(), profile.getThemeId())); + } } super.onCreate(savedInstanceState); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileThemedActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileThemedActivity.java index fb6db900..079e0df6 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileThemedActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileThemedActivity.java @@ -43,6 +43,6 @@ public class ProfileThemedActivity extends CrashReportingActivity { setupProfileColors(); } protected void initProfile() { - mProfile = Data.profile.get(); + mProfile = Data.profile.getValue(); } } 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 4f61ba2e..0843dc29 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 @@ -45,11 +45,15 @@ import net.ktnx.mobileledger.ui.HueRingDialog; import net.ktnx.mobileledger.ui.activity.ProfileDetailActivity; import net.ktnx.mobileledger.utils.Colors; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; import java.util.Objects; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import static net.ktnx.mobileledger.utils.Logger.debug; @@ -91,7 +95,7 @@ public class ProfileDetailFragment extends Fragment implements HueRingDialog.Hue public ProfileDetailFragment() { } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) { debug("profiles", "[fragment] Creating profile details options menu"); super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.profile_details, menu); @@ -100,44 +104,59 @@ public class ProfileDetailFragment extends Fragment implements HueRingDialog.Hue AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle(mProfile.getName()); builder.setMessage(R.string.remove_profile_dialog_message); - builder.setPositiveButton(R.string.Remove, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - debug("profiles", - String.format("[fragment] removing profile %s", mProfile.getUuid())); - mProfile.removeFromDB(); - Data.profiles.remove(mProfile); - if (Data.profile.get().equals(mProfile)) { - debug("profiles", "[fragment] setting current profile to 0"); - Data.setCurrentProfile(Data.profiles.get(0)); - } - getActivity().finish(); + builder.setPositiveButton(R.string.Remove, (dialog, which) -> { + debug("profiles", + String.format("[fragment] removing profile %s", mProfile.getUuid())); + mProfile.removeFromDB(); + ArrayList oldList = Data.profiles.getValue(); + assert oldList != null; + ArrayList newList = + (ArrayList) oldList.clone(); + newList.remove(mProfile); + Data.profiles.setValue(newList); + if (mProfile.equals(Data.profile.getValue())) { + debug("profiles", "[fragment] setting current profile to 0"); + Data.setCurrentProfile(newList.get(0)); + final FragmentActivity activity = getActivity(); + if (activity != null) activity.finish(); } }); builder.show(); return false; }); - menuDeleteProfile.setVisible((mProfile != null) && (Data.profiles.size() > 1)); + final ArrayList profiles = Data.profiles.getValue(); + menuDeleteProfile + .setVisible((mProfile != null) && (profiles != null) && (profiles.size() > 1)); if (BuildConfig.DEBUG) { final MenuItem menuWipeProfileData = menu.findItem(R.id.menuWipeData); - menuWipeProfileData.setOnMenuItemClickListener(this::onWipeDataMenuClicked); + menuWipeProfileData.setOnMenuItemClickListener(ignored -> onWipeDataMenuClicked()); menuWipeProfileData.setVisible(mProfile != null); } } - private boolean onWipeDataMenuClicked(MenuItem item) { + private boolean onWipeDataMenuClicked() { // this is a development option, so no confirmation mProfile.wipeAllData(); - Data.profile.forceNotifyObservers(); + if (mProfile.equals(Data.profile.getValue())) triggerProfileChange(); return true; } + private void triggerProfileChange() { + int index = Data.getProfileIndex(mProfile); + MobileLedgerProfile newProfile = new MobileLedgerProfile(mProfile); + final ArrayList profiles = Data.profiles.getValue(); + assert profiles != null; + profiles.set(index, newProfile); + if (mProfile.equals(Data.profile.getValue())) Data.profile.setValue(newProfile); + } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if ((getArguments() != null) && getArguments().containsKey(ARG_ITEM_ID)) { int index = getArguments().getInt(ARG_ITEM_ID, -1); - if (index != -1) mProfile = Data.profiles.get(index); + ArrayList profiles = Data.profiles.getValue(); + if ((profiles != null) && (index != -1) && (index < profiles.size())) + mProfile = profiles.get(index); Activity activity = this.getActivity(); if (activity == null) throw new AssertionError(); @@ -155,38 +174,38 @@ public class ProfileDetailFragment extends Fragment implements HueRingDialog.Hue if (context == null) return; FloatingActionButton fab = context.findViewById(R.id.fab); - fab.setOnClickListener(v -> { - if (!checkValidity()) return; - - if (mProfile != null) { - updateProfileFromUI(); -// debug("profiles", String.format("Selected item is %d", mProfile.getThemeId())); - mProfile.storeInDB(); - debug("profiles", "profile stored in DB"); - Data.profiles.triggerItemChangedNotification(mProfile); - + fab.setOnClickListener(v -> onSaveFabClicked()); - if (mProfile.getUuid().equals(Data.profile.get().getUuid())) { - // dummy update to notify the observers of the possibly new name/URL - Data.profile.forceNotifyObservers(); - } - } - else { - mProfile = new MobileLedgerProfile(); - updateProfileFromUI(); - mProfile.storeInDB(); - Data.profiles.add(mProfile); - MobileLedgerProfile.storeProfilesOrder(); + profileName.requestFocus(); + } + private void onSaveFabClicked() { + if (!checkValidity()) return; - // first profile ever? - if (Data.profiles.size() == 1) Data.profile.set(mProfile); - } + if (mProfile != null) { + updateProfileFromUI(); +// debug("profiles", String.format("Selected item is %d", mProfile.getThemeId())); + mProfile.storeInDB(); + debug("profiles", "profile stored in DB"); + triggerProfileChange(); + } + else { + mProfile = new MobileLedgerProfile(); + updateProfileFromUI(); + mProfile.storeInDB(); + final ArrayList profiles = Data.profiles.getValue(); + assert profiles != null; + ArrayList newList = + (ArrayList) profiles.clone(); + newList.add(mProfile); + Data.profiles.setValue(newList); + MobileLedgerProfile.storeProfilesOrder(); - Activity activity = getActivity(); - if (activity != null) activity.finish(); - }); + // first profile ever? + if (newList.size() == 1) Data.profile.setValue(mProfile); + } - profileName.requestFocus(); + Activity activity = getActivity(); + if (activity != null) activity.finish(); } private void updateProfileFromUI() { mProfile.setName(profileName.getText()); 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 a4221cdd..f24f6f0c 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 @@ -36,6 +36,7 @@ import net.ktnx.mobileledger.ui.activity.ProfileDetailActivity; import net.ktnx.mobileledger.utils.Colors; import net.ktnx.mobileledger.utils.ObservableValue; +import java.util.ArrayList; import java.util.Collections; import java.util.Locale; import java.util.Observer; @@ -69,16 +70,12 @@ public class ProfilesRecyclerViewAdapter public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { - Data.profiles.blockNotifications(); - try { - Collections.swap(Data.profiles, viewHolder.getAdapterPosition(), - target.getAdapterPosition()); - MobileLedgerProfile.storeProfilesOrder(); - notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition()); - } - finally { - Data.profiles.unblockNotifications(); - } + final ArrayList profiles = Data.profiles.getValue(); + assert profiles != null; + Collections.swap(profiles, viewHolder.getAdapterPosition(), + target.getAdapterPosition()); + MobileLedgerProfile.storeProfilesOrder(); + notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition()); return true; } @Override @@ -122,7 +119,7 @@ public class ProfilesRecyclerViewAdapter else startEditingProfiles(); } private void editProfile(View view, MobileLedgerProfile profile) { - int index = Data.profiles.indexOf(profile); + int index = Data.getProfileIndex(profile); Context context = view.getContext(); Intent intent = new Intent(context, ProfileDetailActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION); @@ -172,8 +169,10 @@ public class ProfilesRecyclerViewAdapter } @Override public void onBindViewHolder(@NonNull final ProfileListViewHolder holder, int position) { - final MobileLedgerProfile profile = Data.profiles.get(position); - final MobileLedgerProfile currentProfile = Data.profile.get(); + final ArrayList profiles = Data.profiles.getValue(); + assert profiles != null; + final MobileLedgerProfile profile = profiles.get(position); + final MobileLedgerProfile currentProfile = Data.profile.getValue(); debug("profiles", String.format(Locale.ENGLISH,"pos %d: %s, current: %s", position, profile.getUuid(), (currentProfile == null) ? "" : currentProfile.getUuid())); holder.itemView.setTag(profile); @@ -216,7 +215,8 @@ public class ProfilesRecyclerViewAdapter } @Override public int getItemCount() { - return Data.profiles.size(); + final ArrayList profiles = Data.profiles.getValue(); + return profiles != null ? profiles.size() : 0; } public boolean isEditingProfiles() { return editingProfiles.get(); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java index 89b390f4..e88481cf 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java @@ -32,7 +32,7 @@ public class TransactionListViewModel extends ViewModel { public static ObservableValue updateError = new ObservableValue<>(); public static void scheduleTransactionListReload() { - if (Data.profile.get() == null) return; + if (Data.profile.getValue() == null) return; String filter = Data.accountFilter.getValue(); AsyncTask task = new UTT(); diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/Colors.java b/app/src/main/java/net/ktnx/mobileledger/utils/Colors.java index fc2951f6..9342e052 100644 --- a/app/src/main/java/net/ktnx/mobileledger/utils/Colors.java +++ b/app/src/main/java/net/ktnx/mobileledger/utils/Colors.java @@ -160,7 +160,7 @@ public class Colors { return result; } public static void setupTheme(Activity activity) { - MobileLedgerProfile profile = Data.profile.get(); + MobileLedgerProfile profile = Data.profile.getValue(); setupTheme(activity, profile); } public static void setupTheme(Activity activity, MobileLedgerProfile profile) { diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/Locker.java b/app/src/main/java/net/ktnx/mobileledger/utils/Locker.java new file mode 100644 index 00000000..515b314c --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/utils/Locker.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2019 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.utils; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class Locker { + private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + public LockHolder lockForWriting() { + ReentrantReadWriteLock.WriteLock wLock = lock.writeLock(); + wLock.lock(); + + ReentrantReadWriteLock.ReadLock rLock = lock.readLock(); + rLock.lock(); + + return new LockHolder(rLock, wLock); + } + public LockHolder lockForReading() { + ReentrantReadWriteLock.ReadLock rLock = lock.readLock(); + rLock.lock(); + return new LockHolder(rLock); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java b/app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java index 30b77f9c..9089b9e5 100644 --- a/app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java +++ b/app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java @@ -126,7 +126,8 @@ public final class MLDB { final AutoCompleteTextView view, final String table, final String field, final boolean profileSpecific) { - hookAutocompletionAdapter(context, view, table, field, profileSpecific, null, null, Data.profile.get()); + hookAutocompletionAdapter(context, view, table, field, profileSpecific, null, null, + Data.profile.getValue()); } @TargetApi(Build.VERSION_CODES.N) public static void hookAutocompletionAdapter(final Context context,