Sort array items when using PagedListAdapter? - android-paging

I'm using the Paging library.
Currently, I want to sort (using SortList) the items by the description in PagedListAdapter but I have not figured out how to do it.
How to sort elements when using a PagedListAdapter?
Thank you.

I faced this problem too, and was surprised that PagedList doesn't seem to have a straightforward way to sort items. Here's how I achieved the effect I needed using DataSource's mapByPage():
/** sorts MyItems by timestamp in descending order */
private fun DataSource.Factory<Long, MyItem>.sortByDescTime(): DataSource.Factory<Long, MyItem> {
return mapByPage {
myItemsList.sortedWith(compareByDescending { item -> item.timeStamp })
}
}
i.e, the input of mapByPage() should be your sorting function (depends on your setup, mine uses the Kotlin extensions & lambda syntax to sort the items in the target list - using Collections.sortedWith())
And then, used my extension function on the data source to sort the fetched items:
fun fetchItems(userId: Long): LiveData<PagedList<MyItem>> {
val itemDataSource = itemAPIService.getItems(userId).sortByDescTime()
val itemPagedList = LivePagedListBuilder(itemDataSource, 10).build()
return itemPagedList
}

you need to use MediatorLiveData here. though below code is in java, it should serve the basic purpose.
In Your ViewModel add both
public LiveData<PagedList<Customer>> customerList;
public MediatorLiveData<PagedList<Customer>> customerListMediator;
then first fetch liveData, that is, customerList in my case
customerList = new LivePagedListBuilder<>(mAppRepository.getCustomers(new SimpleSQLiteQuery(dynamicQuery)),1).build();
after that use the mediatorLiveData:-
customerListMediator.addSource(customerList, new Observer<PagedList<Customer>>() {
#Override
public void onChanged(#Nullable PagedList<Customer> customers) {
customerListMediator.setValue(customers);
}
});
And in your recyclerview, don't use LiveData list, instead use mediatorLiveData, in my case customerListMediator. like below:-
mViewModel.customerListMediator.observe(this, customers -> {
mViewModel.customerListAdapter.submitList(customers);
Toast.makeText(this, "list updated.", Toast.LENGTH_SHORT).show();
});

mapByPage() function works, but has side effects as you would need to "prepend" the next page items in the PagedList to have the desired effect.
Using Room #Dao and SQL sorting capabilities you can do something like this, which seems more performant as it releases sorting operations from the UI components:
#Dao
public interface MyItemDao{
enum SORT_OPT {
LABEL_ASC, LABEL_DESC, PRICE_ASC, PRICE_DESC, RATING_ASC, RATING_DESC
}
#Update
void update(ItemEntity itemEntity);
#Delete
void delete(ItemEntity itemEntity);
#Query("DELETE FROM item_table")
void deleteAll();
#Query("SELECT * FROM item_table ORDER BY price ASC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByPriceAsc();
#Query("SELECT * FROM item_table ORDER BY price DESC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByPriceDesc();
#Query("SELECT * FROM item_table ORDER BY label ASC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByLabelAsc();
#Query("SELECT * FROM item_table ORDER BY label DESC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByLabelDesc();
#Query("SELECT * FROM product_table ORDER BY totalRating ASC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByRatingAsc();
#Query("SELECT * FROM product_table ORDER BY totalRating DESC")
#Transaction
DataSource.Factory<Integer, ItemEntity> getProductsOrderByRatingDesc();
default DataSource.Factory<Integer, ItemEntity> getSortedProducts(SORT_OPT sortOptions) {
switch (sortOptions) {
case LABEL_ASC:
return getProductsOrderByLabelAsc();
case LABEL_DESC:
return getProductsOrderByLabelDesc();
case PRICE_ASC:
return getProductsOrderByPriceAsc();
case PRICE_DESC:
return getProductsOrderByPriceDesc();
case RATING_ASC:
return getProductsOrderByRatingAsc();
case RATING_DESC:
return getProductsOrderByRatingDesc();
default:
return null;
}
}
As you can notice the most important method here is:
default DataSource.Factory<Integer, ItemEntity> getSortedProducts(SORT_OPT sortOptions)
You can store you sort value in sharedPreferences and load it from there.

Related

How to find top N elements in Spring Data Jpa?

In Spring Data Jpa to get first 10 rows I can do this findTop10By...(). In my case the number or rows is not defined and comes as a parameter.
Is there something like findTopNBy...(int countOfRowsToGet)?
Here is another way without native query. I added Pageable as a parameter to the method in the interface.
findAllBySomeField(..., Pageable pageable)
I call it like this:
findAllBySomeField(..., PageRequest.of(0, limit)) // get first N rows
findAllBySomeField(..., Pageable.unpaged()) // get all rows
I don't know of a way to do exactly what you want, but if you are open to using #Query in your JPA repository class, then a prepared statement is one alternative:
#Query("SELECT * FROM Entity e ORDER BY e.id LIMIT :limit", nativeQuery=true)
Entity getEntitiesByLimit(#Param("limit") int limit);
Did it by using pagination, as described in the first answer. Just adding a more explicit example.
This example will give you the first 50 records ordered by id.
Repository:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, String> {
Page<MyEntity> findAll(Pageable pageable);
}
Service:
#Service
public class MyDataService {
#Autowired
MyRepository myRepository;
private static final int LIMIT = 50;
public Optional<List<MyEntity>> getAllLimited() {
Page<MyEntity> page = myRepository.findAll(PageRequest.of(0, LIMIT, Sort.by(Sort.Order.asc("id"))));
return Optional.of(page.getContent());
}
}
Found the original idea here:
https://itqna.net/questions/16074/spring-data-jpa-does-not-recognize-sql-limit-command
(which will also link to another SO question btw)

JPQL - implement conditional logic based on most recent element

I'm new to JPA and Spring data. I would like to implement a function with the following logic in my ordering system:
If no order after given timestamp, return 1
otherwise return last order's counter+1
Can I implement such logic with Pure JPQL?
Order entity:
#Entity
public class Order {
#Id
#GeneratedValue
private UUID id;
private Integer counter;
#CreationTimestamp
private Timestamp creationTimestamp;
...
OrderRepository.java:
#Repository
public interface OrderRepository extends CrudRepository<Order, UUID> {
// TODO what goes after else?
#Query("select case when count(o) < 1 then 1 else ... from Order o where o.creationTimestamp > :timestamp order by o.creationTimestamp desc")
Integer nextCounter(#Param("timestamp") Timestamp timestamp);
}
I'm curious as to what the exact use case is. If you could rely on the fact that the counters for orders created after the instant provided as the parameter are increasing, you could simply select NVL(MAX(o.counter), 0) + 1, or event COUNT(o) + 1. I understand this is not the case.
What you want could be achieved with a subquery as follows:
SELECT NVL(MAX(o.counter), 0) + 1
FROM Order o
WHERE o.creationTimestamp = (
SELECT MAX(o.creationTimestamp)
FROM Order o
WHERE o.creationTimestamp > :timestamp
)

delete list using JPA

I have list to be deleted.
My code to delete my list is:
for (MyDataModel dataMo: listData) {
testEJB.delete(dataMo.getPkId(), MyDataModel.class);
}
public void delete(Object id, Class<T> classe) {
T entityToBeRemoved = em.getReference(classe, id);
em.remove(entityToBeRemoved);
}
Since my list size may be more than 500, data deletion by this method is much time consuming.I want alternative so that deletion is quicker.I need help.
Ok i have got solution on my own for this i used native query.Here i do not have to generate the list too.My code is:
public int deleteUsingNativeQuery(String query){
Query qry = em.createNativeQuery(query );
return qry.executeUpdate();
}
Here i pass the native query "delete from 'table name' where 'condition'" in function deleteUsingNativeQuery and deletion was also quick.

Criteria for where predicate using columns from two tables and functions in NHibernate

My goal is to fetch two values from two tables joined together and perform a comparison which if true, outputs the rows from table 1. The TSQL code below illustrates the query, question is whether there is a way to do the third predicate in the where clause using NHibernate criteria using session.CreateCriteria:
declare #currentSystemTime datetimeoffset = '2015-07-22 18:42:16.1172838 +00:00'
select
inst.ExpiryDate,
ent.DaysToExpiryNotificationStart,
convert(date, dateadd(day, -ent.DaysToExpiryNotificationStart, inst.ExpiryDate)) as NotificationStart,
convert(date, #currentSystemTime) as CurrentSystemTime,
*
from
Instances inst
inner join Entries ent on inst.Entry_id = ent.Id
where
inst.ExpiryDate is not null
and ent.DaysToExpiryNotificationStart is not null
and convert(date, #currentSystemTime) >= convert(date, dateadd(day, -ent.DaysToExpiryNotificationStart, inst.ExpiryDate))
The properties are defined as follows in the entity classes:
public virtual DateTimeOffset? ExpiryDate { get; set; }
public virtual int? DaysToExpiryNotificationStart { get; set; }
I am using Fluent NHibernate to map these. Are manual queries via CreateQuery or CreateSQLQuery the only way to go? If there is an easier way to accomplish this task, I am open. Any help will be appreciated.
Thanks,
Shawn
session.Query<Instances>()
.Where(i => Datetime.Today >= i.ExpiryDate.AddDays(i.DaysToExpiryNotificationStart))
adddays is not yet natively supported but can be added quite easyly. see here to make AddDays work
the same slightly different can be done with QueryOver
session.QueryOver<Instances>()
.Where(i => Datetime.Today >= i.ExpiryDate.AddDays(i.DaysToExpiryNotificationStart))
public static class QueryOverExtensions
{
public static void Register()
{
ExpressionProcessor.RegisterCustomProjection(() => default(DatTime).AddDays(1), QueryOverExtensions.ProcessAddDays);
}
private static IProjection ProcessAddDays(MethodCallExpression methodCallExpression)
{
IProjection property = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[0]).AsProjection();
return (Projections.SqlFunction("addDays", NHibernateUtil.DateTime, NHibernateUtil.Int32, property));
}
}
Note: I'm not sure if adddays is already defined as sql function. you might need to register one in the driver

Custom extension function in select statment of an EF query

In our database we have a table that looks like this which we have mapped to an entity in our Database-First EF model:
CREATE TABLE Texts(
Id integer,
Eng nvarchar(max),
Nob nvarchar(max),
...
)
A row in this table may be quite large, so we only want to get the value of the column that is currently need by a language selection the user has done.
My idea was to have an extension function to do it for me, but I dont have any idea nor can't find any way to write it (if it is even possible). I have tried a few variants, but (obviously) it failed with an exception that states that it cannot be translated into a store expression. So I am a bit stuck.
The idea of usage for this function is:
context.Codes.Where(row => row.Id == 234).Select(row => new {
row.Id,
Text = Text.GetLocalizedText("Eng") // This should generate an SQL that only retrieves the Eng
// column of the Text navigation property (which is
// connected to the Texts table.
});
That should generate a select similar to this (which are similar to the example above except using Text.Eng directly):
SELECT
[Extent1].[Id] AS [Id],
[Extent2].[Eng] AS [Eng]
FROM [dbo].[Codes] AS [Extent1]
INNER JOIN [dbo].[Texts] AS [Extent2] ON [Extent1].[TextId] = [Extent2].[Id]
WHERE 234 = [Extent1].[Id]
Does anyone know if this is possible, and if it is; how to write it? If it isn't possible, does anyone have any other idea on how to solve this without retrieving the whole Text entity with all of it's columns?
An extension method of IQueryable<Code> would work but it is not as flexible as you probably want to have it because you would need to have an extension per type of projection you want to perform and you cannot work with an anonymous result object.
The idea is basically like so:
You need a named class (instead of anonymous) which you can project into:
public class CodeData
{
public int Id { get; set; }
public string LocalizedText { get; set; }
}
And then an extension method with the language parameter:
public static class CustomExtensions
{
public static IQueryable<CodeData> SelectCodeData(
this IQueryable<Code> query, string language)
{
switch (language)
{
case "Eng":
return query.Select(code => new CodeData
{
Id = code.Id,
LocalizedText = code.Text.Eng
});
case "Nob":
return query.Select(code => new CodeData
{
Id = code.Id,
LocalizedText = code.Text.Nob
});
//... more languages
}
throw new ArgumentException("Invalid language code.", "language");
}
}
Then it can be called like this:
using CustomExtensions;
// ...
IQueryable<CodeData> codeDataQuery = context.Codes
.Where(row => row.Id == 234)
.SelectCodeData("Eng");