migrate a bunch of globals to LiveData
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Fri, 3 May 2019 15:09:52 +0000 (18:09 +0300)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Fri, 3 May 2019 15:09:52 +0000 (18:09 +0300)
18 files changed:
app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTask.java
app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java
app/src/main/java/net/ktnx/mobileledger/async/UpdateAccountsTask.java
app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java
app/src/main/java/net/ktnx/mobileledger/model/Data.java
app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java
app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.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/ProfileDetailActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileThemedActivity.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/ui/transaction_list/TransactionListViewModel.java
app/src/main/java/net/ktnx/mobileledger/utils/Colors.java
app/src/main/java/net/ktnx/mobileledger/utils/Locker.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java

index 2c3d90f..cb13d6d 100644 (file)
@@ -34,7 +34,7 @@ public class CommitAccountsTask
     protected ArrayList<LedgerAccount> doInBackground(CommitAccountsTaskParams... params) {
         Data.backgroundTaskStarted();
         ArrayList<LedgerAccount> newList = new ArrayList<>();
-        String profile = Data.profile.get().getUuid();
+        String profile = Data.profile.getValue().getUuid();
         try {
 
             SQLiteDatabase db = MLDB.getDatabase();
index 20a8a20..9db9c9d 100644 (file)
@@ -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(
             "<span class=\"[^\"]*\\bamount\\b[^\"]*\">\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?</span>");
-    public RetrieveTransactionsTask(WeakReference<MainActivity> contextRef) {
+    private MobileLedgerProfile profile;
+    public RetrieveTransactionsTask(WeakReference<MainActivity> 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) {
index bd179f2..88ff97b 100644 (file)
@@ -34,7 +34,8 @@ public class UpdateAccountsTask extends AsyncTask<Void, Void, ArrayList<LedgerAc
     protected ArrayList<LedgerAccount> 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<LedgerAccount> newList = new ArrayList<>();
index dc6536a..1ed7adb 100644 (file)
@@ -36,7 +36,7 @@ import static net.ktnx.mobileledger.utils.Logger.debug;
 
 public class UpdateTransactionsTask extends AsyncTask<String, Void, String> {
     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();
index 0902ced..5e08297 100644 (file)
@@ -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<TransactionListItem> transactions =
             new ObservableList<>(new ArrayList<>());
     public static ObservableList<LedgerAccount> accounts = new ObservableList<>(new ArrayList<>());
-    private static AtomicInteger backgroundTaskCount = new AtomicInteger(0);
     public static MutableLiveData<Boolean> backgroundTasksRunning = new MutableLiveData<>(false);
     public static MutableLiveData<Date> lastUpdateDate = new MutableLiveData<>();
-    public static ObservableValue<MobileLedgerProfile> profile = new ObservableValue<>();
-    public static ObservableList<MobileLedgerProfile> profiles =
-            new ObservableList<>(new ArrayList<>());
+    public static MutableLiveData<MobileLedgerProfile> profile = new MutableLiveData<>();
+    public static MutableLiveData<ArrayList<MobileLedgerProfile>> profiles =
+            new MutableLiveData<>(new ArrayList<>());
     public static ObservableValue<Boolean> optShowOnlyStarred = new ObservableValue<>();
     public static MutableLiveData<String> 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<MobileLedgerProfile> 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<MobileLedgerProfile> 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<MobileLedgerProfile> 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;
index 1685c1a..fd900ef 100644 (file)
@@ -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<LedgerTransactionAccount> 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);
index 4822a92..a279f34 100644 (file)
@@ -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<MobileLedgerProfile> list = new ArrayList<>();
+        ArrayList<MobileLedgerProfile> 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();
         }
index 5796b33..5534aca 100644 (file)
@@ -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(
index 2ebc662..3e9af7b 100644 (file)
@@ -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);
index e2857a1..3f0a427 100644 (file)
@@ -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<MobileLedgerProfile> list) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
 
         List<ShortcutInfo> 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<MobileLedgerProfile> 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<MobileLedgerProfile> 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<LedgerAccount> children =
-                            Data.profile.get().loadVisibleChildAccountsOf(acc);
+                    List<LedgerAccount> 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);
                 }
index 40051da..1568c65 100644 (file)
@@ -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<MobileLedgerProfile> 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);
index fb6db90..079e0df 100644 (file)
@@ -43,6 +43,6 @@ public class ProfileThemedActivity extends CrashReportingActivity {
         setupProfileColors();
     }
     protected void initProfile() {
-        mProfile = Data.profile.get();
+        mProfile = Data.profile.getValue();
     }
 }
index 4f61ba2..0843dc2 100644 (file)
@@ -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<MobileLedgerProfile> oldList = Data.profiles.getValue();
+                assert oldList != null;
+                ArrayList<MobileLedgerProfile> newList =
+                        (ArrayList<MobileLedgerProfile>) 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<MobileLedgerProfile> 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<MobileLedgerProfile> 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<MobileLedgerProfile> 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<MobileLedgerProfile> profiles = Data.profiles.getValue();
+            assert profiles != null;
+            ArrayList<MobileLedgerProfile> newList =
+                    (ArrayList<MobileLedgerProfile>) 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());
index a4221cd..f24f6f0 100644 (file)
@@ -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<MobileLedgerProfile> 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<MobileLedgerProfile> 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) ? "<NULL>" : currentProfile.getUuid()));
         holder.itemView.setTag(profile);
@@ -216,7 +215,8 @@ public class ProfilesRecyclerViewAdapter
     }
     @Override
     public int getItemCount() {
-        return Data.profiles.size();
+        final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
+        return profiles != null ? profiles.size() : 0;
     }
     public boolean isEditingProfiles() {
         return editingProfiles.get();
index 89b390f..e88481c 100644 (file)
@@ -32,7 +32,7 @@ public class TransactionListViewModel extends ViewModel {
     public static ObservableValue<String> 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<String, Void, String> task = new UTT();
index fc2951f..9342e05 100644 (file)
@@ -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 (file)
index 0000000..515b314
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+ */
+
+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);
+    }
+}
index 30b77f9..9089b9e 100644 (file)
@@ -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,