Verified Commit f3210e3a authored by Torsten Grote's avatar Torsten Grote
Browse files

Allow DbViewModel work on things other than lists.

parent 4074ac85
Pipeline #6240 passed with stages
in 9 minutes and 45 seconds
......@@ -53,13 +53,9 @@ abstract class BaseViewModel extends DbViewModel implements EventListener {
protected final AndroidNotificationManager notificationManager;
protected final BlogManager blogManager;
protected final MutableLiveData<LiveResult<List<BlogPostItem>>> blogPosts =
protected final MutableLiveData<LiveResult<ListUpdate>> blogPosts =
new MutableLiveData<>();
// UI thread
@Nullable
private Boolean postAddedWasLocal = null;
BaseViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
......@@ -147,11 +143,10 @@ abstract class BaseViewModel extends DbViewModel implements EventListener {
@UiThread
private void onBlogPostItemAdded(BlogPostItem item, boolean local) {
List<BlogPostItem> items = addListItem(blogPosts, item);
List<BlogPostItem> items = addListItem(getBlogPostItems(), item);
if (items != null) {
Collections.sort(items);
postAddedWasLocal = local;
blogPosts.setValue(new LiveResult<>(items));
blogPosts.setValue(new LiveResult<>(new ListUpdate(local, items)));
}
}
......@@ -168,17 +163,42 @@ abstract class BaseViewModel extends DbViewModel implements EventListener {
});
}
LiveData<LiveResult<List<BlogPostItem>>> getBlogPosts() {
LiveData<LiveResult<ListUpdate>> getBlogPosts() {
return blogPosts;
}
@UiThread
@Nullable
Boolean getPostAddedWasLocalAndReset() {
if (postAddedWasLocal == null) return null;
boolean wasLocal = postAddedWasLocal;
postAddedWasLocal = null;
return wasLocal;
protected List<BlogPostItem> getBlogPostItems() {
LiveResult<ListUpdate> value = blogPosts.getValue();
if (value == null) return null;
ListUpdate result = value.getResultOrNull();
return result == null ? null : result.getItems();
}
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;
}
/**
* @return null when not a single post was added with this update.
* true when a single post was added locally and false if remotely.
*/
@Nullable
public Boolean getPostAddedWasLocal() {
return postAddedWasLocal;
}
public List<BlogPostItem> getItems() {
return items;
}
}
}
......@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R;
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.sharing.BlogSharingStatusActivity;
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.widget.LinkDialogFragment;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.Nullable;
......@@ -174,9 +173,9 @@ public class BlogFragment extends BaseFragment
return TAG;
}
private void onBlogPostsLoaded(List<BlogPostItem> items) {
adapter.submitList(items, () -> {
Boolean wasLocal = viewModel.getPostAddedWasLocalAndReset();
private void onBlogPostsLoaded(ListUpdate update) {
adapter.submitList(update.getItems(), () -> {
Boolean wasLocal = update.getPostAddedWasLocal();
if (wasLocal != null && wasLocal) {
list.scrollToPosition(0);
displaySnackbar(R.string.blogs_blog_post_created,
......
......@@ -145,7 +145,8 @@ class BlogViewModel extends BaseViewModel {
}
private void loadBlogPosts(GroupId groupId) {
loadList(txn -> loadBlogPosts(txn, groupId), blogPosts::setValue);
loadFromDb(txn -> new ListUpdate(null, loadBlogPosts(txn, groupId)),
blogPosts::setValue);
}
private void loadSharingContacts(GroupId groupId) {
......
......@@ -13,14 +13,13 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
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.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import org.briarproject.briar.api.blog.Blog;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.Nullable;
......@@ -101,10 +100,9 @@ public class FeedFragment extends BaseFragment
list.stopPeriodicUpdate();
}
private void onBlogPostsLoaded(List<BlogPostItem> items) {
if (items.isEmpty()) list.showData();
else adapter.submitList(items, () -> {
Boolean wasLocal = viewModel.getPostAddedWasLocalAndReset();
private void onBlogPostsLoaded(ListUpdate update) {
adapter.submitList(update.getItems(), () -> {
Boolean wasLocal = update.getPostAddedWasLocal();
if (wasLocal != null && wasLocal) {
showSnackBar(R.string.blogs_blog_post_created);
} else if (wasLocal != null) {
......
......@@ -13,7 +13,6 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.LiveResult;
......@@ -34,10 +33,8 @@ import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
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.briar.api.blog.BlogManager.CLIENT_ID;
......@@ -70,14 +67,6 @@ class FeedViewModel extends BaseViewModel {
BlogPostAddedEvent b = (BlogPostAddedEvent) e;
LOG.info("Blog post added");
onBlogPostAdded(b.getHeader(), b.isLocal());
} else if (e instanceof GroupAddedEvent) {
GroupAddedEvent g = (GroupAddedEvent) e;
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;
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
......@@ -115,11 +104,11 @@ class FeedViewModel extends BaseViewModel {
}
private void loadAllBlogPosts() {
loadList(this::loadAllBlogPosts, blogPosts::setValue);
loadFromDb(this::loadAllBlogPosts, blogPosts::setValue);
}
@DatabaseExecutor
private List<BlogPostItem> loadAllBlogPosts(Transaction txn)
private ListUpdate loadAllBlogPosts(Transaction txn)
throws DbException {
long start = now();
List<BlogPostItem> posts = new ArrayList<>();
......@@ -128,29 +117,17 @@ class FeedViewModel extends BaseViewModel {
}
Collections.sort(posts);
logDuration(LOG, "Loading all posts", start);
return posts;
}
private void onBlogAdded(GroupId g) {
runOnDbThread(true, txn -> {
List<BlogPostItem> posts = loadBlogPosts(txn, g);
txn.attach(() -> onBlogPostItemsAdded(posts));
}, e -> logException(LOG, WARNING, e));
return new ListUpdate(null, posts);
}
@UiThread
private void onBlogPostItemsAdded(List<BlogPostItem> posts) {
List<BlogPostItem> items = addListItems(blogPosts, posts);
if (items != null) {
Collections.sort(items);
blogPosts.setValue(new LiveResult<>(items));
}
}
private void onBlogRemoved(GroupId g) {
removeAndUpdateListItems(blogPosts, item ->
List<BlogPostItem> items = removeListItems(getBlogPostItems(), item ->
item.getGroupId().equals(g)
);
if (items != null) {
blogPosts.setValue(new LiveResult<>(new ListUpdate(null, items)));
}
}
}
......@@ -86,7 +86,7 @@ public class ContactsViewModel extends DbViewModel implements EventListener {
}
protected void loadContacts() {
loadList(this::loadContacts, contactListItems::setValue);
loadFromDb(this::loadContacts, contactListItems::setValue);
}
private List<ContactListItem> loadContacts(Transaction txn)
......@@ -151,7 +151,7 @@ public class ContactsViewModel extends DbViewModel implements EventListener {
@UiThread
private void updateItem(ContactId c,
Function<ContactListItem, ContactListItem> replacer, boolean sort) {
List<ContactListItem> list = updateListItems(contactListItems,
List<ContactListItem> list = updateListItems(getList(contactListItems),
itemToTest -> itemToTest.getContact().getId().equals(c),
replacer);
if (list == null) return;
......@@ -161,10 +161,8 @@ public class ContactsViewModel extends DbViewModel implements EventListener {
@UiThread
private void removeItem(ContactId c) {
List<ContactListItem> list = removeListItems(contactListItems,
removeAndUpdateListItems(contactListItems,
itemToTest -> itemToTest.getContact().getId().equals(c));
if (list == null) return;
contactListItems.setValue(new LiveResult<>(list));
}
}
......@@ -127,7 +127,7 @@ class ForumListViewModel extends DbViewModel implements EventListener {
}
public void loadForums() {
loadList(this::loadForums, forumItems::setValue);
loadFromDb(this::loadForums, forumItems::setValue);
}
@DatabaseExecutor
......@@ -145,7 +145,7 @@ class ForumListViewModel extends DbViewModel implements EventListener {
@UiThread
private void onForumPostReceived(GroupId g, ForumPostHeader header) {
List<ForumListItem> list = updateListItems(forumItems,
List<ForumListItem> list = updateListItems(getList(forumItems),
itemToTest -> itemToTest.getForum().getId().equals(g),
itemToUpdate -> new ForumListItem(itemToUpdate, header));
if (list == null) return;
......
......@@ -135,7 +135,7 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
@Override
public void loadItems() {
loadList(txn -> {
loadFromDb(txn -> {
long start = now();
List<ForumPostHeader> headers =
forumManager.getPostHeaders(txn, groupId);
......
......@@ -159,7 +159,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
@Override
public void loadItems() {
loadList(txn -> {
loadFromDb(txn -> {
// check first if group is dissolved
isDissolved
.postValue(privateGroupManager.isDissolved(txn, groupId));
......
......@@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.list;
import android.app.Application;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
......@@ -142,7 +141,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
}
void loadGroups() {
loadList(this::loadGroups, groupItems::setValue);
loadFromDb(this::loadGroups, groupItems::setValue);
}
@DatabaseExecutor
......@@ -173,7 +172,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
@UiThread
private void onGroupMessageAdded(GroupMessageHeader header) {
GroupId g = header.getGroupId();
List<GroupItem> list = updateListItems(groupItems,
List<GroupItem> list = updateListItems(getList(groupItems),
itemToTest -> itemToTest.getId().equals(g),
itemToUpdate -> new GroupItem(itemToUpdate, header));
if (list == null) return;
......@@ -184,7 +183,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
@UiThread
private void onGroupDissolved(GroupId groupId) {
List<GroupItem> list = updateListItems(groupItems,
List<GroupItem> list = updateListItems(getList(groupItems),
itemToTest -> itemToTest.getId().equals(groupId),
itemToUpdate -> new GroupItem(itemToUpdate, true));
if (list == null) return;
......
......@@ -70,7 +70,7 @@ public abstract class DbViewModel extends AndroidViewModel {
* <p>
* If you need a list of items to be displayed in a
* {@link RecyclerView.Adapter},
* use {@link #loadList(DbCallable, UiConsumer)} instead.
* use {@link #loadFromDb(DbCallable, UiConsumer)} instead.
*/
protected void runOnDbThread(Runnable task) {
dbExecutor.execute(() -> {
......@@ -90,7 +90,7 @@ public abstract class DbViewModel extends AndroidViewModel {
* <p>
* If you need a list of items to be displayed in a
* {@link RecyclerView.Adapter},
* use {@link #loadList(DbCallable, UiConsumer)} instead.
* use {@link #loadFromDb(DbCallable, UiConsumer)} instead.
*/
protected void runOnDbThread(boolean readOnly,
DbRunnable<Exception> task, Consumer<Exception> err) {
......@@ -108,21 +108,20 @@ public abstract class DbViewModel extends AndroidViewModel {
}
/**
* Loads a list of items on the {@link DatabaseExecutor} within a single
* Loads a data on the {@link DatabaseExecutor} within a single
* {@link Transaction} and publishes it as a {@link LiveResult}
* to the {@link UiThread}.
* <p>
* Use this to ensure that modifications to your local list do not get
* Use this to ensure that modifications to your local UI data do not get
* overridden by database loads that were in progress while the modification
* was made.
* E.g. An event about the removal of a message causes the message item to
* be removed from the local list while all messages are reloaded.
* be removed from the local data set while all messages are reloaded.
* This method ensures that those operations can be processed on the
* UiThread in the correct order so that the removed message will not be
* re-added when the re-load completes.
*/
protected <T extends List<?>> void loadList(
DbCallable<T, DbException> task,
protected <T> void loadFromDb(DbCallable<T, DbException> task,
UiConsumer<LiveResult<T>> uiConsumer) {
dbExecutor.execute(() -> {
try {
......@@ -149,63 +148,46 @@ public abstract class DbViewModel extends AndroidViewModel {
}
/**
* Creates a copy of the list available in the given LiveData
* and adds the given item to the copy.
* Creates a copy of the given list and adds the given item to the copy.
*
* @return a copy of the list in the LiveData with item added or null when
* <ul>
* <li> LiveData does not have a value
* <li> LiveResult in the LiveData has an error
* </ul>
* @return an updated copy of the list, or null if the list is null
*/
@Nullable
protected <T> List<T> addListItem(LiveData<LiveResult<List<T>>> liveData,
T item) {
List<T> items = getListCopy(liveData);
if (items == null) return null;
items.add(item);
return items;
protected <T> List<T> addListItem(@Nullable List<T> list, T item) {
if (list == null) return null;
List<T> copy = new ArrayList<>(list);
copy.add(item);
return copy;
}
/**
* Creates a copy of the list available in the given LiveData
* and adds the given items to the copy.
* Creates a copy of the given list and adds the given items to the copy.
*
* @return a copy of the list in the LiveData with items added or null when
* <ul>
* <li> LiveData does not have a value
* <li> LiveResult in the LiveData has an error
* </ul>
* @return an updated copy of the list, or null if the list is null
*/
@Nullable
protected <T> List<T> addListItems(LiveData<LiveResult<List<T>>> liveData,
protected <T> List<T> addListItems(@Nullable List<T> list,
Collection<T> items) {
List<T> copiedItems = getListCopy(liveData);
if (copiedItems == null) return null;
copiedItems.addAll(items);
return copiedItems;
if (list == null) return null;
List<T> copy = new ArrayList<>(list);
copy.addAll(items);
return copy;
}
/**
* Creates a copy of the list available in the given LiveData
* and replaces items where the given test function returns true.
* Creates a copy of the given list, replacing items where the given test
* function returns true.
*
* @return a copy of the list in the LiveData with item(s) replaced
* or null when the
* <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>
* @return an updated copy of the list, or null if either the list is null
* or the test function returns false for all items
*/
@Nullable
protected <T> List<T> updateListItems(
LiveData<LiveResult<List<T>>> liveData, Function<T, Boolean> test,
Function<T, T> replacer) {
List<T> items = getListCopy(liveData);
if (items == null) return null;
protected <T> List<T> updateListItems(@Nullable List<T> list,
Function<T, Boolean> test, Function<T, T> replacer) {
if (list == null) return null;
List<T> copy = new ArrayList<>(list);
ListIterator<T> iterator = items.listIterator();
ListIterator<T> iterator = copy.listIterator();
boolean changed = false;
while (iterator.hasNext()) {
T item = iterator.next();
......@@ -214,28 +196,23 @@ public abstract class DbViewModel extends AndroidViewModel {
iterator.set(replacer.apply(item));
}
}
return changed ? items : null;
return changed ? copy : null;
}
/**
* Creates a copy of the list available in the given LiveData
* and removes the items from it where the given test function returns true.
* Creates a copy of the given list, removing items from it where the given
* test function returns true.
*
* @return a copy of the list in the LiveData with item(s) removed
* or null when the
* <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>
* @return an updated copy of the list, or null if either the list is null
* or the test function returns false for all items
*/
@Nullable
protected <T> List<T> removeListItems(
LiveData<LiveResult<List<T>>> liveData, Function<T, Boolean> test) {
List<T> items = getListCopy(liveData);
if (items == null) return null;
protected <T> List<T> removeListItems(@Nullable List<T> list,
Function<T, Boolean> test) {
if (list == null) return null;
List<T> copy = new ArrayList<>(list);
ListIterator<T> iterator = items.listIterator();
ListIterator<T> iterator = copy.listIterator();
boolean changed = false;
while (iterator.hasNext()) {
T item = iterator.next();
......@@ -244,7 +221,7 @@ public abstract class DbViewModel extends AndroidViewModel {
iterator.remove();
}
}
return changed ? items : null;
return changed ? copy : null;
}
/**
......@@ -255,29 +232,26 @@ public abstract class DbViewModel extends AndroidViewModel {
* <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
* <li> test function returned false for all items in the list
* </ul>
*/
@UiThread
protected <T> void removeAndUpdateListItems(
MutableLiveData<LiveResult<List<T>>> liveData,
Function<T, Boolean> test) {
List<T> list = removeListItems(liveData, test);
if (list != null) liveData.setValue(new LiveResult<>(list));
List<T> copy = removeListItems(getList(liveData), test);
if (copy != null) liveData.setValue(new LiveResult<>(copy));
}
/**
* Retrieves a copy of the list of items from the given LiveData
* or null if it is not available.
* The list copy can be safely mutated.
* Returns the list of items from the given LiveData, or null if no list is
* available.
*/
@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();
if (value == null) return null;
List<T> list = value.getResultOrNull();
if (list == null) return null;
return new ArrayList<>(list);
return value.getResultOrNull();
}
/**
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment