Compare commits

...

1 Commits

Author SHA1 Message Date
Torsten Grote
b4f38e81ea Allow DbViewModel work on things other than lists. 2021-03-23 14:15:14 +00:00
9 changed files with 99 additions and 134 deletions

View File

@@ -53,13 +53,9 @@ abstract class BaseViewModel extends DbViewModel implements EventListener {
protected final AndroidNotificationManager notificationManager; protected final AndroidNotificationManager notificationManager;
protected final BlogManager blogManager; protected final BlogManager blogManager;
protected final MutableLiveData<LiveResult<List<BlogPostItem>>> blogPosts = protected final MutableLiveData<LiveResult<ListUpdate>> blogPosts =
new MutableLiveData<>(); new MutableLiveData<>();
// UI thread
@Nullable
private Boolean postAddedWasLocal = null;
BaseViewModel(Application application, BaseViewModel(Application application,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
@@ -147,11 +143,10 @@ abstract class BaseViewModel extends DbViewModel implements EventListener {
@UiThread @UiThread
private void onBlogPostItemAdded(BlogPostItem item, boolean local) { private void onBlogPostItemAdded(BlogPostItem item, boolean local) {
List<BlogPostItem> items = addListItem(blogPosts, item); List<BlogPostItem> items = addListItem(getBlogPostItems(), item);
if (items != null) { if (items != null) {
Collections.sort(items); Collections.sort(items);
postAddedWasLocal = local; blogPosts.setValue(new LiveResult<>(new ListUpdate(local, items)));
blogPosts.setValue(new LiveResult<>(items));
} }
} }
@@ -168,17 +163,37 @@ abstract class BaseViewModel extends DbViewModel implements EventListener {
}); });
} }
LiveData<LiveResult<List<BlogPostItem>>> getBlogPosts() { LiveData<LiveResult<ListUpdate>> getBlogPosts() {
return blogPosts; return blogPosts;
} }
@UiThread @UiThread
@Nullable protected List<BlogPostItem> getBlogPostItems() {
Boolean getPostAddedWasLocalAndReset() { LiveResult<ListUpdate> value = blogPosts.getValue();
if (postAddedWasLocal == null) return null; if (value == null) return null;
boolean wasLocal = postAddedWasLocal; ListUpdate result = value.getResultOrNull();
postAddedWasLocal = null; return result == null ? null : result.getItems();
return wasLocal;
} }
static class ListUpdate {
@Nullable
private final Boolean postAddedWasLocal;
private final List<BlogPostItem> items;
ListUpdate(@Nullable Boolean postAddedWasLocal,
List<BlogPostItem> items) {
this.postAddedWasLocal = postAddedWasLocal;
this.items = items;
}
@Nullable
public Boolean getPostAddedWasLocal() {
return postAddedWasLocal;
}
public List<BlogPostItem> getItems() {
return items;
}
}
} }

View File

@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.blog.BaseViewModel.ListUpdate;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity; import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
import org.briarproject.briar.android.sharing.ShareBlogActivity; import org.briarproject.briar.android.sharing.ShareBlogActivity;
@@ -22,8 +23,6 @@ import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.widget.LinkDialogFragment; import org.briarproject.briar.android.widget.LinkDialogFragment;
import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -174,9 +173,9 @@ public class BlogFragment extends BaseFragment
return TAG; return TAG;
} }
private void onBlogPostsLoaded(List<BlogPostItem> items) { private void onBlogPostsLoaded(ListUpdate update) {
adapter.submitList(items, () -> { adapter.submitList(update.getItems(), () -> {
Boolean wasLocal = viewModel.getPostAddedWasLocalAndReset(); Boolean wasLocal = update.getPostAddedWasLocal();
if (wasLocal != null && wasLocal) { if (wasLocal != null && wasLocal) {
list.scrollToPosition(0); list.scrollToPosition(0);
displaySnackbar(R.string.blogs_blog_post_created, displaySnackbar(R.string.blogs_blog_post_created,

View File

@@ -145,7 +145,8 @@ class BlogViewModel extends BaseViewModel {
} }
private void loadBlogPosts(GroupId groupId) { private void loadBlogPosts(GroupId groupId) {
loadList(txn -> loadBlogPosts(txn, groupId), blogPosts::setValue); loadList(txn -> new ListUpdate(null, loadBlogPosts(txn, groupId)),
blogPosts::setValue);
} }
private void loadSharingContacts(GroupId groupId) { private void loadSharingContacts(GroupId groupId) {

View File

@@ -13,14 +13,13 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.blog.BaseViewModel.ListUpdate;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.widget.LinkDialogFragment; import org.briarproject.briar.android.widget.LinkDialogFragment;
import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.Blog;
import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -101,10 +100,9 @@ public class FeedFragment extends BaseFragment
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
} }
private void onBlogPostsLoaded(List<BlogPostItem> items) { private void onBlogPostsLoaded(ListUpdate update) {
if (items.isEmpty()) list.showData(); adapter.submitList(update.getItems(), () -> {
else adapter.submitList(items, () -> { Boolean wasLocal = update.getPostAddedWasLocal();
Boolean wasLocal = viewModel.getPostAddedWasLocalAndReset();
if (wasLocal != null && wasLocal) { if (wasLocal != null && wasLocal) {
showSnackBar(R.string.blogs_blog_post_created); showSnackBar(R.string.blogs_blog_post_created);
} else if (wasLocal != null) { } else if (wasLocal != null) {

View File

@@ -13,7 +13,6 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent; import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.LiveResult; import org.briarproject.briar.android.viewmodel.LiveResult;
@@ -34,10 +33,8 @@ import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID; import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID;
@@ -70,15 +67,8 @@ class FeedViewModel extends BaseViewModel {
BlogPostAddedEvent b = (BlogPostAddedEvent) e; BlogPostAddedEvent b = (BlogPostAddedEvent) e;
LOG.info("Blog post added"); LOG.info("Blog post added");
onBlogPostAdded(b.getHeader(), b.isLocal()); onBlogPostAdded(b.getHeader(), b.isLocal());
} else if (e instanceof GroupAddedEvent) { }
GroupAddedEvent g = (GroupAddedEvent) e; if (e instanceof GroupRemovedEvent) {
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
LOG.info("Blog added");
// TODO how can this even happen?
// added RSS feeds should trigger BlogPostAddedEvent, no?
onBlogAdded(g.getGroup().getId());
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e; GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getClientId().equals(CLIENT_ID)) { if (g.getGroup().getClientId().equals(CLIENT_ID)) {
LOG.info("Blog removed"); LOG.info("Blog removed");
@@ -119,7 +109,7 @@ class FeedViewModel extends BaseViewModel {
} }
@DatabaseExecutor @DatabaseExecutor
private List<BlogPostItem> loadAllBlogPosts(Transaction txn) private ListUpdate loadAllBlogPosts(Transaction txn)
throws DbException { throws DbException {
long start = now(); long start = now();
List<BlogPostItem> posts = new ArrayList<>(); List<BlogPostItem> posts = new ArrayList<>();
@@ -128,29 +118,17 @@ class FeedViewModel extends BaseViewModel {
} }
Collections.sort(posts); Collections.sort(posts);
logDuration(LOG, "Loading all posts", start); logDuration(LOG, "Loading all posts", start);
return posts; return new ListUpdate(null, posts);
}
private void onBlogAdded(GroupId g) {
runOnDbThread(true, txn -> {
List<BlogPostItem> posts = loadBlogPosts(txn, g);
txn.attach(() -> onBlogPostItemsAdded(posts));
}, e -> logException(LOG, WARNING, e));
} }
@UiThread @UiThread
private void onBlogPostItemsAdded(List<BlogPostItem> posts) { private void onBlogRemoved(GroupId g) {
List<BlogPostItem> items = addListItems(blogPosts, posts); List<BlogPostItem> items = removeListItems(getBlogPostItems(),
item -> item.getGroupId().equals(g)
);
if (items != null) { if (items != null) {
Collections.sort(items); blogPosts.setValue(new LiveResult<>(new ListUpdate(null, items)));
blogPosts.setValue(new LiveResult<>(items));
} }
} }
private void onBlogRemoved(GroupId g) {
removeAndUpdateListItems(blogPosts, item ->
item.getGroupId().equals(g)
);
}
} }

View File

@@ -151,7 +151,7 @@ public class ContactsViewModel extends DbViewModel implements EventListener {
@UiThread @UiThread
private void updateItem(ContactId c, private void updateItem(ContactId c,
Function<ContactListItem, ContactListItem> replacer, boolean sort) { Function<ContactListItem, ContactListItem> replacer, boolean sort) {
List<ContactListItem> list = updateListItems(contactListItems, List<ContactListItem> list = updateListItems(getList(contactListItems),
itemToTest -> itemToTest.getContact().getId().equals(c), itemToTest -> itemToTest.getContact().getId().equals(c),
replacer); replacer);
if (list == null) return; if (list == null) return;
@@ -161,10 +161,8 @@ public class ContactsViewModel extends DbViewModel implements EventListener {
@UiThread @UiThread
private void removeItem(ContactId c) { private void removeItem(ContactId c) {
List<ContactListItem> list = removeListItems(contactListItems, removeAndUpdateListItems(contactListItems,
itemToTest -> itemToTest.getContact().getId().equals(c)); itemToTest -> itemToTest.getContact().getId().equals(c));
if (list == null) return;
contactListItems.setValue(new LiveResult<>(list));
} }
} }

View File

@@ -145,7 +145,7 @@ class ForumListViewModel extends DbViewModel implements EventListener {
@UiThread @UiThread
private void onForumPostReceived(GroupId g, ForumPostHeader header) { private void onForumPostReceived(GroupId g, ForumPostHeader header) {
List<ForumListItem> list = updateListItems(forumItems, List<ForumListItem> list = updateListItems(getList(forumItems),
itemToTest -> itemToTest.getForum().getId().equals(g), itemToTest -> itemToTest.getForum().getId().equals(g),
itemToUpdate -> new ForumListItem(itemToUpdate, header)); itemToUpdate -> new ForumListItem(itemToUpdate, header));
if (list == null) return; if (list == null) return;

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.list;
import android.app.Application; import android.app.Application;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -173,7 +172,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
@UiThread @UiThread
private void onGroupMessageAdded(GroupMessageHeader header) { private void onGroupMessageAdded(GroupMessageHeader header) {
GroupId g = header.getGroupId(); GroupId g = header.getGroupId();
List<GroupItem> list = updateListItems(groupItems, List<GroupItem> list = updateListItems(getList(groupItems),
itemToTest -> itemToTest.getId().equals(g), itemToTest -> itemToTest.getId().equals(g),
itemToUpdate -> new GroupItem(itemToUpdate, header)); itemToUpdate -> new GroupItem(itemToUpdate, header));
if (list == null) return; if (list == null) return;
@@ -184,7 +183,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
@UiThread @UiThread
private void onGroupDissolved(GroupId groupId) { private void onGroupDissolved(GroupId groupId) {
List<GroupItem> list = updateListItems(groupItems, List<GroupItem> list = updateListItems(getList(groupItems),
itemToTest -> itemToTest.getId().equals(groupId), itemToTest -> itemToTest.getId().equals(groupId),
itemToUpdate -> new GroupItem(itemToUpdate, true)); itemToUpdate -> new GroupItem(itemToUpdate, true));
if (list == null) return; if (list == null) return;

View File

@@ -120,9 +120,11 @@ public abstract class DbViewModel extends AndroidViewModel {
* This method ensures that those operations can be processed on the * This method ensures that those operations can be processed on the
* UiThread in the correct order so that the removed message will not be * UiThread in the correct order so that the removed message will not be
* re-added when the re-load completes. * re-added when the re-load completes.
*
* TODO: Rename this method and update javadoc, as it's not restricted to
* lists
*/ */
protected <T extends List<?>> void loadList( protected <T> void loadList(DbCallable<T, DbException> task,
DbCallable<T, DbException> task,
UiConsumer<LiveResult<T>> uiConsumer) { UiConsumer<LiveResult<T>> uiConsumer) {
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
try { try {
@@ -149,63 +151,46 @@ public abstract class DbViewModel extends AndroidViewModel {
} }
/** /**
* Creates a copy of the list available in the given LiveData * Creates a copy of the given list and adds the given item to the copy.
* and adds the given item to the copy.
* *
* @return a copy of the list in the LiveData with item added or null when * @return an updated copy of the list, or null if the list is null
* <ul>
* <li> LiveData does not have a value
* <li> LiveResult in the LiveData has an error
* </ul>
*/ */
@Nullable @Nullable
protected <T> List<T> addListItem(LiveData<LiveResult<List<T>>> liveData, protected <T> List<T> addListItem(@Nullable List<T> list, T item) {
T item) { if (list == null) return null;
List<T> items = getListCopy(liveData); List<T> copy = new ArrayList<>(list);
if (items == null) return null; copy.add(item);
items.add(item); return copy;
return items;
} }
/** /**
* Creates a copy of the list available in the given LiveData * Creates a copy of the given list and adds the given items to the copy.
* and adds the given items to the copy.
* *
* @return a copy of the list in the LiveData with items added or null when * @return an updated copy of the list, or null if the list is null
* <ul>
* <li> LiveData does not have a value
* <li> LiveResult in the LiveData has an error
* </ul>
*/ */
@Nullable @Nullable
protected <T> List<T> addListItems(LiveData<LiveResult<List<T>>> liveData, protected <T> List<T> addListItems(@Nullable List<T> list,
Collection<T> items) { Collection<T> items) {
List<T> copiedItems = getListCopy(liveData); if (list == null) return null;
if (copiedItems == null) return null; List<T> copy = new ArrayList<>(list);
copiedItems.addAll(items); copy.addAll(items);
return copiedItems; return copy;
} }
/** /**
* Creates a copy of the list available in the given LiveData * Creates a copy of the given list, replacing items where the given test
* and replaces items where the given test function returns true. * function returns true.
* *
* @return a copy of the list in the LiveData with item(s) replaced * @return an updated copy of the list, or null if either the list is null
* or null when the * or the test function returns false for all items
* <ul>
* <li> LiveData does not have a value
* <li> LiveResult in the LiveData has an error
* <li> test function did return false for all items in the list
* </ul>
*/ */
@Nullable @Nullable
protected <T> List<T> updateListItems( protected <T> List<T> updateListItems(@Nullable List<T> list,
LiveData<LiveResult<List<T>>> liveData, Function<T, Boolean> test, Function<T, Boolean> test, Function<T, T> replacer) {
Function<T, T> replacer) { if (list == null) return null;
List<T> items = getListCopy(liveData); List<T> copy = new ArrayList<>(list);
if (items == null) return null;
ListIterator<T> iterator = items.listIterator(); ListIterator<T> iterator = copy.listIterator();
boolean changed = false; boolean changed = false;
while (iterator.hasNext()) { while (iterator.hasNext()) {
T item = iterator.next(); T item = iterator.next();
@@ -214,28 +199,23 @@ public abstract class DbViewModel extends AndroidViewModel {
iterator.set(replacer.apply(item)); iterator.set(replacer.apply(item));
} }
} }
return changed ? items : null; return changed ? copy : null;
} }
/** /**
* Creates a copy of the list available in the given LiveData * Creates a copy of the given list, removing items from it where the given
* and removes the items from it where the given test function returns true. * test function returns true.
* *
* @return a copy of the list in the LiveData with item(s) removed * @return an updated copy of the list, or null if either the list is null
* or null when the * or the test function returns false for all items
* <ul>
* <li> LiveData does not have a value
* <li> LiveResult in the LiveData has an error
* <li> test function did return false for all items in the list
* </ul>
*/ */
@Nullable @Nullable
protected <T> List<T> removeListItems( protected <T> List<T> removeListItems(@Nullable List<T> list,
LiveData<LiveResult<List<T>>> liveData, Function<T, Boolean> test) { Function<T, Boolean> test) {
List<T> items = getListCopy(liveData); if (list == null) return null;
if (items == null) return null; List<T> copy = new ArrayList<>(list);
ListIterator<T> iterator = items.listIterator(); ListIterator<T> iterator = copy.listIterator();
boolean changed = false; boolean changed = false;
while (iterator.hasNext()) { while (iterator.hasNext()) {
T item = iterator.next(); T item = iterator.next();
@@ -244,7 +224,7 @@ public abstract class DbViewModel extends AndroidViewModel {
iterator.remove(); iterator.remove();
} }
} }
return changed ? items : null; return changed ? copy : null;
} }
/** /**
@@ -255,29 +235,26 @@ public abstract class DbViewModel extends AndroidViewModel {
* <ul> * <ul>
* <li> LiveData does not have a value * <li> LiveData does not have a value
* <li> LiveResult in the LiveData has an error * <li> LiveResult in the LiveData has an error
* <li> test function did return false for all items in the list * <li> test function returned false for all items in the list
* </ul> * </ul>
*/ */
@UiThread @UiThread
protected <T> void removeAndUpdateListItems( protected <T> void removeAndUpdateListItems(
MutableLiveData<LiveResult<List<T>>> liveData, MutableLiveData<LiveResult<List<T>>> liveData,
Function<T, Boolean> test) { Function<T, Boolean> test) {
List<T> list = removeListItems(liveData, test); List<T> copy = removeListItems(getList(liveData), test);
if (list != null) liveData.setValue(new LiveResult<>(list)); if (copy != null) liveData.setValue(new LiveResult<>(copy));
} }
/** /**
* Retrieves a copy of the list of items from the given LiveData * Returns the list of items from the given LiveData, or null if no list is
* or null if it is not available. * available.
* The list copy can be safely mutated.
*/ */
@Nullable @Nullable
private <T> List<T> getListCopy(LiveData<LiveResult<List<T>>> liveData) { protected <T> List<T> getList(LiveData<LiveResult<List<T>>> liveData) {
LiveResult<List<T>> value = liveData.getValue(); LiveResult<List<T>> value = liveData.getValue();
if (value == null) return null; if (value == null) return null;
List<T> list = value.getResultOrNull(); return value.getResultOrNull();
if (list == null) return null;
return new ArrayList<>(list);
} }
/** /**