Implement paging for the items list and refactor the filter/sort logic using more queries

This commit is contained in:
Shinokuni 2019-04-20 13:16:25 +02:00
parent 30092d3a86
commit b9a31d4652
9 changed files with 188 additions and 149 deletions

View File

@ -48,6 +48,9 @@ dependencies {
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation "android.arch.persistence.room:runtime:1.1.1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
implementation 'android.arch.paging:runtime:1.0.1'
implementation 'android.arch.paging:common:1.0.1'
implementation "joda-time:joda-time:2.9.9"
implementation 'org.jsoup:jsoup:1.11.3'

View File

@ -1,13 +1,9 @@
package com.readrops.app.activities;
import android.arch.lifecycle.ViewModelProviders;
import android.arch.paging.PagedList;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
@ -15,7 +11,6 @@ import android.support.annotation.Nullable;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@ -48,14 +43,10 @@ import com.readrops.app.database.pojo.ItemWithFeed;
import com.readrops.app.utils.DrawerManager;
import com.readrops.app.utils.GlideApp;
import com.readrops.app.utils.SharedPreferencesManager;
import com.readrops.app.utils.Utils;
import com.readrops.app.viewmodels.MainViewModel;
import com.readrops.app.views.MainItemListAdapter;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@ -63,7 +54,6 @@ import java.util.Map;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.observers.DisposableCompletableObserver;
import io.reactivex.observers.DisposableSingleObserver;
import io.reactivex.schedulers.Schedulers;
@ -81,8 +71,7 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
private Drawer drawer;
private FloatingActionMenu actionMenu;
private List<ItemWithFeed> allItems;
private List<ItemWithFeed> filteredItems;
private PagedList<ItemWithFeed> allItems;
private MainViewModel viewModel;
private DrawerManager drawerManager;
@ -93,11 +82,7 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
private int feedCount;
private int feedNb;
private int filterFeedId;
private boolean readItLater;
private boolean showReadItems;
private ListSortType sortType;
private boolean scrollToTop;
private ActionMode actionMode;
@ -113,17 +98,15 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
actionMenu = findViewById(R.id.fab_menu);
viewModel = ViewModelProviders.of(this).get(MainViewModel.class);
allItems = new ArrayList<>();
viewModel.setShowReadItems(SharedPreferencesManager.readBoolean(this,
SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES));
showReadItems = SharedPreferencesManager.readBoolean(this,
SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES);
viewModel.getItemsWithFeed().observe(this, (itemWithFeeds -> {
viewModel.getItemsWithFeed().observe(this, itemWithFeeds -> {
allItems = itemWithFeeds;
if (!refreshLayout.isRefreshing())
filterItems(filterFeedId);
}));
adapter.submitList(itemWithFeeds);
});
refreshLayout = findViewById(R.id.swipe_refresh_layout);
refreshLayout.setOnRefreshListener(this);
@ -134,7 +117,6 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
feedCount = 0;
initRecyclerView();
sortType = ListSortType.NEWEST_TO_OLDEST;
drawer = new DrawerBuilder()
.withActivity(this)
@ -154,65 +136,24 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
if (drawerItem instanceof PrimaryDrawerItem) {
drawer.closeDrawer();
int id = (int)drawerItem.getIdentifier();
filterFeedId = 0;
switch (id) {
case DrawerManager.ARTICLES_ITEM_ID:
readItLater = false;
filterItems(0);
viewModel.setFilterType(MainViewModel.FilterType.NO_FILTER);
viewModel.invalidate();
break;
case DrawerManager.READ_LATER_ID:
readItLater = true;
filterItems(0);
viewModel.setFilterType(MainViewModel.FilterType.READ_IT_LATER_FILTER);
viewModel.invalidate();
break;
}
} else if (drawerItem instanceof SecondaryDrawerItem) {
readItLater = false;
drawer.closeDrawer();
filterItems((int)drawerItem.getIdentifier());
}
}
private void filterItems(int id) {
filterFeedId = id;
filteredItems = new ArrayList<>(allItems);
CollectionUtils.filter(filteredItems, object -> {
boolean showRead;
if (object.getItem().isRead())
showRead = (object.getItem().isRead() == showReadItems);
else
showRead = true; // item unread
if (id != 0) {
if (readItLater)
return object.getItem().isReadItLater() && object.getFeedId() == id && showRead;
else
return !object.getItem().isReadItLater() && object.getFeedId() == id && showRead;
} else {
if (readItLater)
return object.getItem().isReadItLater() && showRead;
else
return !object.getItem().isReadItLater() && showRead;
}
});
sortItems();
adapter.submitList(filteredItems);
}
private void sortItems() {
switch (sortType) {
case OLDEST_TO_NEWEST:
Collections.sort(filteredItems, ((o1, o2) -> o1.getItem().getPubDate().compareTo(o2.getItem().getPubDate())));
break;
case NEWEST_TO_OLDEST:
Collections.sort(filteredItems, ((o1, o2) -> -1 * o1.getItem().getPubDate().compareTo(o2.getItem().getPubDate())));
break;
default:
Collections.sort(filteredItems, ((o1, o2) -> -1 * o1.getItem().getPubDate().compareTo(o2.getItem().getPubDate())));
break;
viewModel.setFilterFeedId((int) drawerItem.getIdentifier());
viewModel.setFilterType(MainViewModel.FilterType.FEED_FILTER);
viewModel.invalidate();
}
}
@ -392,7 +333,7 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
if (readItLater)
if (viewModel.getFilterType() == MainViewModel.FilterType.READ_IT_LATER_FILTER)
adapter.notifyItemChanged(viewHolder.getAdapterPosition());
}
}
@ -406,7 +347,19 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
recyclerView.scrollToPosition(0);
if (scrollToTop) {
recyclerView.scrollToPosition(0);
scrollToTop = false;
}
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
if (scrollToTop) {
recyclerView.scrollToPosition(0);
scrollToTop = false;
} else
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
}
});
}
@ -501,8 +454,9 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
syncProgressLayout.setVisibility(View.GONE);
refreshLayout.setRefreshing(false);
scrollToTop = true;
adapter.submitList(allItems);
filterItems(filterFeedId);
updateDrawerFeeds(); // update drawer after syncing feeds
}
});
@ -513,7 +467,7 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
getMenuInflater().inflate(R.menu.item_list_menu, menu);
MenuItem articlesItem = menu.findItem(R.id.item_filter_read_items);
articlesItem.setChecked(showReadItems);
articlesItem.setChecked(viewModel.showReadItems());
return true;
}
@ -524,17 +478,17 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
case R.id.item_filter_read_items:
if (item.isChecked()) {
item.setChecked(false);
showReadItems = false;
viewModel.setShowReadItems(false);
SharedPreferencesManager.writeValue(this,
SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES, false);
} else {
item.setChecked(true);
showReadItems = true;
viewModel.setShowReadItems(true);
SharedPreferencesManager.writeValue(this,
SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES, true);
}
filterItems(filterFeedId);
viewModel.invalidate();
return true;
case R.id.item_sort:
displayFilterDialog();
@ -545,7 +499,7 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
}
private void displayFilterDialog() {
int index = sortType == ListSortType.OLDEST_TO_NEWEST ? 1 : 0;
int index = viewModel.getSortType() == ListSortType.OLDEST_TO_NEWEST ? 1 : 0;
new MaterialDialog.Builder(this)
.title(getString(R.string.filter))
@ -554,14 +508,12 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
String[] items = getResources().getStringArray(R.array.filter_items);
if (text.toString().equals(items[0]))
sortType = ListSortType.NEWEST_TO_OLDEST;
viewModel.setSortType(ListSortType.NEWEST_TO_OLDEST);
else
sortType = ListSortType.OLDEST_TO_NEWEST;
sortItems();
adapter.submitList(filteredItems);
adapter.notifyDataSetChanged();
viewModel.setSortType(ListSortType.OLDEST_TO_NEWEST);
scrollToTop = true;
viewModel.invalidate();
return true;
})
.show();

View File

@ -2,6 +2,8 @@ package com.readrops.app.database.dao;
import android.arch.lifecycle.LiveData;
import android.arch.paging.DataSource;
import android.arch.paging.PageKeyedDataSource;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
@ -14,14 +16,45 @@ import java.util.List;
@Dao
public interface ItemDao {
@Query("Select * from Item Where feed_id = :feedId")
LiveData<List<Item>> getAllByFeed(int feedId);
String SELECT_ALL_FIELDS = "Item.id, title, clean_description, image_link, pub_date, read, read_it_later, " +
"Feed.name, text_color, background_color, icon_url, read_time, Feed.id as feedId, Folder.id as folder_id, " +
"Folder.name as folder_name";
@Query("Select * from Item Order By pub_date DESC")
LiveData<List<Item>> getAll();
String SELECT_ALL_JOIN = "Item Inner Join Feed, Folder on Item.feed_id = Feed.id And Folder.id = Feed.folder_id";
@Query("Select Item.id, title, clean_description, image_link, pub_date, read, read_it_later, Feed.name, text_color, background_color, icon_url, read_time, Feed.id as feedId, Folder.id as folder_id, Folder.name as folder_name from Item Inner Join Feed, Folder on Item.feed_id = Feed.id And Folder.id = Feed.folder_id Order By Item.id DESC")
LiveData<List<ItemWithFeed>> getAllItemWithFeeds();
String SELECT_ALL_ORDER_BY_ASC = "Order by Item.id DESC";
String SELECT_ALL_ORDER_BY_DESC = "Order By pub_date ASC";
@Query("Select " + SELECT_ALL_FIELDS + " from " + SELECT_ALL_JOIN + " Where feed_id = :feedId " +
"And read = :readState And read_it_later = 0 " + SELECT_ALL_ORDER_BY_ASC)
DataSource.Factory<Integer, ItemWithFeed> selectAllByFeedASC(int feedId, int readState);
@Query("Select " + SELECT_ALL_FIELDS + " from " + SELECT_ALL_JOIN + " Where feed_id = :feedId " +
"And read = :readState And read_it_later = 0 " + SELECT_ALL_ORDER_BY_DESC)
DataSource.Factory<Integer, ItemWithFeed> selectAllByFeedsDESC(int feedId, int readState);
@Query("Select " + SELECT_ALL_FIELDS + " from " + SELECT_ALL_JOIN + " Where read_it_later = 1 " +
"And read = :readState " + SELECT_ALL_ORDER_BY_ASC)
DataSource.Factory<Integer, ItemWithFeed> selectAllReadItLaterASC(int readState);
@Query("Select " + SELECT_ALL_FIELDS + " from " + SELECT_ALL_JOIN + " Where read_it_later = 1 " +
"And read = :readState " + SELECT_ALL_ORDER_BY_DESC)
DataSource.Factory<Integer, ItemWithFeed> selectAllReadItLaterDESC(int readState);
/**
* ASC means here from the newest (inserted) to the oldest
*/
@Query("Select " + SELECT_ALL_FIELDS + " From " + SELECT_ALL_JOIN + " Where read = :readState And " +
"read_it_later = 0 " + SELECT_ALL_ORDER_BY_ASC)
DataSource.Factory<Integer, ItemWithFeed> selectAllASC(int readState);
/**
* DESC means here from the oldest to the newest
*/
@Query("Select " + SELECT_ALL_FIELDS + " From " + SELECT_ALL_JOIN + " Where read = :readState And "
+ "read_it_later = 0 " + SELECT_ALL_ORDER_BY_DESC)
PageKeyedDataSource.Factory<Integer, ItemWithFeed> selectAllDESC(int readState);
@Query("Select case When :guid In (Select guid from Item) Then 'true' else 'false' end")
String guidExist(String guid);

View File

@ -36,12 +36,8 @@ public abstract class ARepository {
public abstract Observable<Feed> sync(List<Feed> feeds);
public abstract void addFeed(ParsingResult result);
public abstract Single<List<FeedInsertionResult>> addFeeds(List<ParsingResult> results);
public abstract void updateFeed(Feed feed);
public abstract void updateFeedWithFolder(FeedWithFolder feedWithFolder);
public abstract Completable deleteFeed(int feedId);

View File

@ -2,7 +2,7 @@ package com.readrops.app.repositories;
import android.accounts.NetworkErrorException;
import android.app.Application;
import android.arch.lifecycle.LiveData;
import android.arch.paging.PageKeyedDataSource;
import android.support.annotation.Nullable;
import com.readrops.app.database.entities.Folder;
@ -41,16 +41,11 @@ public class LocalFeedRepository extends ARepository {
private static final String TAG = LocalFeedRepository.class.getSimpleName();
private LiveData<List<ItemWithFeed>> itemsWhithFeed;
private PageKeyedDataSource.Factory<Integer, ItemWithFeed> itemsWhithFeed;
public LocalFeedRepository(Application application) {
super(application);
itemsWhithFeed = database.itemDao().getAllItemWithFeeds();
}
public LiveData<List<ItemWithFeed>> getItemsWithFeed() {
return itemsWhithFeed;
}
@Override
@ -106,23 +101,6 @@ public class LocalFeedRepository extends ARepository {
});
}
@Override
public void addFeed(ParsingResult result) {
executor.execute(() -> {
try {
RSSQuery rssQuery = new RSSQuery();
RSSQueryResult queryResult = rssQuery.queryUrl(result.getUrl(), new HashMap<>());
if (queryResult != null && queryResult.getException() == null) {
insertFeed(queryResult.getFeed(), queryResult.getRssType());
}
} catch (Exception e) {
}
});
}
@Override
public Single<List<FeedInsertionResult>> addFeeds(List<ParsingResult> results) {
return Single.create(emitter -> {
@ -164,17 +142,6 @@ public class LocalFeedRepository extends ARepository {
});
}
@Override
public void updateFeed(Feed feed) {
executor.execute(() -> {
try {
database.feedDao().update(feed);
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
@Override
public void updateFeedWithFolder(FeedWithFolder feedWithFolder) {
executor.execute(() -> {

View File

@ -1,10 +1,19 @@
package com.readrops.app.viewmodels;
import android.app.Application;
import android.arch.core.util.Function;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MediatorLiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.Transformations;
import android.arch.paging.DataSource;
import android.arch.paging.LivePagedListBuilder;
import android.arch.paging.PagedList;
import android.support.annotation.NonNull;
import com.readrops.app.activities.MainActivity;
import com.readrops.app.database.Database;
import com.readrops.app.database.entities.Feed;
import com.readrops.app.database.entities.Folder;
@ -22,19 +31,101 @@ import io.reactivex.Single;
public class MainViewModel extends AndroidViewModel {
private LiveData<List<ItemWithFeed>> itemsWithFeed;
private MediatorLiveData<PagedList<ItemWithFeed>> itemsWithFeed;
private LiveData<PagedList<ItemWithFeed>> lastFetch;
private LocalFeedRepository repository;
private Database db;
private boolean showReadItems;
private FilterType filterType;
private MainActivity.ListSortType sortType;
private int filterFeedId;
public MainViewModel(@NonNull Application application) {
super(application);
filterType = FilterType.NO_FILTER;
sortType = MainActivity.ListSortType.NEWEST_TO_OLDEST;
repository = new LocalFeedRepository(application);
itemsWithFeed = repository.getItemsWithFeed();
db = Database.getInstance(application);
itemsWithFeed = new MediatorLiveData<>();
buildPagedList();
}
public LiveData<List<ItemWithFeed>> getItemsWithFeed() {
private void buildPagedList() {
DataSource.Factory<Integer, ItemWithFeed> items;
int readState = showReadItems ? 1 : 0;
switch (filterType) {
case FEED_FILTER:
if (sortType == MainActivity.ListSortType.NEWEST_TO_OLDEST)
items = db.itemDao().selectAllByFeedASC(filterFeedId, readState);
else
items = db.itemDao().selectAllByFeedsDESC(filterFeedId, readState);
break;
case READ_IT_LATER_FILTER:
if (sortType == MainActivity.ListSortType.NEWEST_TO_OLDEST)
items = db.itemDao().selectAllReadItLaterASC(readState);
else
items = db.itemDao().selectAllReadItLaterDESC(readState);
break;
default:
if (sortType == MainActivity.ListSortType.NEWEST_TO_OLDEST)
items = db.itemDao().selectAllASC(readState);
else
items = db.itemDao().selectAllDESC(readState);
break;
}
if (lastFetch != null)
itemsWithFeed.removeSource(lastFetch);
lastFetch = new LivePagedListBuilder<>(items, new PagedList.Config.Builder()
.setPageSize(40)
.setPrefetchDistance(80)
.setEnablePlaceholders(false)
.build())
.build();
itemsWithFeed.addSource(lastFetch, itemWithFeeds -> itemsWithFeed.setValue(itemWithFeeds));
}
public void invalidate() {
buildPagedList();
}
public void setShowReadItems(boolean showReadItems) {
this.showReadItems = showReadItems;
}
public boolean showReadItems() {
return showReadItems;
}
public void setFilterType(FilterType filterType) {
this.filterType = filterType;
}
public FilterType getFilterType() {
return filterType;
}
public void setSortType(MainActivity.ListSortType sortType) {
this.sortType = sortType;
}
public MainActivity.ListSortType getSortType() {
return sortType;
}
public void setFilterFeedId(int filterFeedId) {
this.filterFeedId = filterFeedId;
}
public MediatorLiveData<PagedList<ItemWithFeed>> getItemsWithFeed() {
return itemsWithFeed;
}
@ -89,4 +180,10 @@ public class MainViewModel extends AndroidViewModel {
emitter.onComplete();
});
}
public enum FilterType {
FEED_FILTER,
READ_IT_LATER_FILTER,
NO_FILTER
}
}

View File

@ -1,5 +1,6 @@
package com.readrops.app.views;
import android.arch.paging.PagedListAdapter;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@ -43,7 +44,7 @@ import java.util.List;
import static com.bumptech.glide.load.resource.bitmap.BitmapTransitionOptions.withCrossFade;
public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListAdapter.ItemViewHolder> implements ListPreloader.PreloadModelProvider<String> {
public class MainItemListAdapter extends PagedListAdapter<ItemWithFeed, MainItemListAdapter.ItemViewHolder> implements ListPreloader.PreloadModelProvider<String> {
private GlideRequests glideRequests;
private OnItemClickListener listener;

View File

@ -1,8 +0,0 @@
package com.readrops.readropslibrary.localfeed;
public interface OperationCallback {
void onSuccess();
void OnFailure();
}

View File

@ -38,7 +38,7 @@ public class RSSQuery {
/**
* Request the url given in parameter.
* This method is synchronous, <b>it has to be called from another thread than the main one</b>.
* This method is synchronous, it <b>has</b> to be called from another thread than the main one.
* @param url url to request
* @throws Exception
*/
@ -116,8 +116,6 @@ public class RSSQuery {
}
}
/**
* Parse input feed
* @param stream source to parse
@ -145,7 +143,7 @@ public class RSSQuery {
switch (type) {
case RSS_2:
feed = serializer.read(RSSFeed.class, xml);
if (((RSSFeed)feed).getChannel().getFeedUrl() == null) // workaround si the channel does not have any atom:link tag
if (((RSSFeed)feed).getChannel().getFeedUrl() == null) // workaround if the channel does not have any atom:link tag
((RSSFeed)feed).getChannel().getLinks().add(new RSSLink(null, response.request().url().toString()));
feed.setEtag(etag);
feed.setLastModified(lastModified);