Crv Anotations

Recycler View with pagination in a clean way

License

License

GroupId

GroupId

com.github.vicpinm
ArtifactId

ArtifactId

crv-annotations
Last Version

Last Version

5.0.1
Release Date

Release Date

Type

Type

jar
Description

Description

Crv Anotations
Recycler View with pagination in a clean way
Project URL

Project URL

https://github.com/vicpinm/CleanRecyclerView
Source Code Management

Source Code Management

https://github.com/vicpinm/CleanRecyclerView

Download crv-annotations

How to add to project

<!-- https://jarcasting.com/artifacts/com.github.vicpinm/crv-annotations/ -->
<dependency>
    <groupId>com.github.vicpinm</groupId>
    <artifactId>crv-annotations</artifactId>
    <version>5.0.1</version>
</dependency>
// https://jarcasting.com/artifacts/com.github.vicpinm/crv-annotations/
implementation 'com.github.vicpinm:crv-annotations:5.0.1'
// https://jarcasting.com/artifacts/com.github.vicpinm/crv-annotations/
implementation ("com.github.vicpinm:crv-annotations:5.0.1")
'com.github.vicpinm:crv-annotations:jar:5.0.1'
<dependency org="com.github.vicpinm" name="crv-annotations" rev="5.0.1">
  <artifact name="crv-annotations" type="jar" />
</dependency>
@Grapes(
@Grab(group='com.github.vicpinm', module='crv-annotations', version='5.0.1')
)
libraryDependencies += "com.github.vicpinm" % "crv-annotations" % "5.0.1"
[com.github.vicpinm/crv-annotations "5.0.1"]

Dependencies

There are no dependencies for this project. It is a standalone project that does not depend on any other jars.

Project Modules

There are no modules declared in this project.

CleanRecycler is a library written completely in Kotlin and built on top of KPresenterAdapter (https://github.com/vicpinm/KPresenterAdapter). While the main purpose of KPresenterAdapter is to provide a framework to organize your adapter-related code following the MPV pattern, this library aims to reduce the boilerplate code related with data loading from diferent datasources, data pagination and placeholders management.

Setup library

1 - Add the CleanRecyclerView widget to your xml

 <com.vicpin.cleanrecycler.view.CleanRecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:itemsPerPage="50"
        app:refreshEnabled="true"
        app:emptyLayout="@layout/empty_layout"
        app:errorLayout="@layout/empty_layout_error"
        app:cellMargin="14dp"/>

2 - Customize the widget with the following attributes

XML Attribute Method Description
app:emptyLayout setEmptyLayout(Int) Layout placeholder to show when there is no data
app:errorLayout setErrorLayout(Int) Error placeholder to show when cloud datasource returns an error and there is no cached data to show
app:errorToast errorToast(String) Text to show in a toast when there is an error trying to load more data (when data collection is paginated)
app:itemsPerPage setItemsPerPage(Int) When data is paginated, size of each page (optional)
app:refreshEnabled setRefreshEnabled(Boolean) Enable or disable pull to refresh
app:showHeaderIfEmptyList setShowHeaderIfEmptyList(Boolean) Show or hide list header when there is no data to show. Default false (header is not shown when there is no data to load)
app:dividerDrawable setDividerDrawable(Int) Drawable resource to be used as row divider when using LinearLayoutManager
app:cellMargin setCellMargin(Int) Set a margin around your cells

3 - Provide the datasources you want to retreive your data from

You can use two datasource types:

  • Cached datasource (data storaged in device, no cloud request is needed)
  • Cloud datasource (data storaged in a remote server)

You can implement any or both of them.

//When cloud data is not paginated
cleanRecycler.load(adapter = presenterAdapter, cloud = DataService::class, cache = DataCache::class)
//When cloud data is paginated
cleanRecycler.loadPaged(adapter = presenterAdapter, cloud = PagedDataService::class, cache = DataCache::class)

Notice that you have to pass an instance of your adapter usign KPresenterAdapter library (https://github.com/vicpinm/KPresenterAdapter)

How it works

First, CleanRecyclerView loads cached data, if any. Then, it tries to update local data from your cloud datasource. When your cloud datasource retreives the data successfully, the library stores the updated data in your local storage, if a cached datasource is provided. Next, it updates the view with the new data. If an error is thrown while fetching data from your server, an error placeholder is shown to the user if there is no local data to be shown. If view already contains data fetched from your local storage, a toast is used to warn your user about the error (if a string resource is setted to errorToast attribute). If local storage does not contain any data and your cloud datasource returns no data either, an empty placeholder will be shown to the user.

This workflow is explained in detail in the following diagram:

DataSources

CleanRecycler library uses rxjava2 to listen to changes in your database and perform api requests. You are not bound to any database library or network client, only implement the methods required by the datasource interface you inherit from. DataSources are parameterized with the type of entity model that datasource is responsible for. You should create separate datasources for each entity you want to manage.

This library is prepared to work with two datasource types: cache datasource and cloud datasource. You can also use only one of them if you don't need both.

Depending on your requieremens, there are different varieties of cache/cloud datasources:

Cloud datasources types

  • CloudDataSource<Model>: Simplest cloud datasource. You only have to implement one method: fun getData(): Single<List<Model>>. It returns a single object with a list of objects retreived from your server.

  • CloudPagedDataSource<Model>: Paginated cloud datasource. The method you have to implement is the same than before, but it receives a 'page' param, which indicates the current page you have to request to your server: fun getData(page: Int): Single<List<Model>>. Every time user reaches the bottom of the list, this method will be invoked with an incremented value of the 'page' parameter.

Cache datasources types

  • CacheDataSource<Model>: With cache datasources, two methods are required:
fun getData(): Flowable<List<Model>>

fun saveData(clearOldData: Boolean, data: List<Model>)

When you need to observe changes in your database and update the view consequently, you should use this datasource. As can be seen, 'getData' method returns a Flowable with the result of the query you perform to your local storage. Most of the ORM libraries are able to return this type of data for listening to database changes.

On the other hand, 'saveData' method is invoked when a new set of data is retreived from your cloud. The first parameter, clearOldData, is set to true when the previous local data need to be cleaned. This is true always when you use CloudDataSource. With CloudPagedDataSource, it will be only true when first data page is requested. Remaining pages will be appended to your existing data, without removing old local data.

Data mapping between layers

In a clean arquitecture based app, is common to separate your database entities and your view entities, mapping the first to the second in an intermediate class.

CleanRecyclerView class is parameterized with two generic types:

class CleanRecyclerView<ViewEntity : Any, DataEntity : Any>

As you can see, first generic type corresponds to your view's entity type, and the second one corresponds to your database's entity type. You can set a mapper class between your two entities in your 'load' or 'loadPaged' method:

val cleanRecycler = viewlist as CleanRecyclerView<ViewItem, DatabaseItem>
cleanRecycler.load(adapter = presenterAdapter, cloud = DataService::class, cache = DataCache::class, mapper: Mapper:class)

Your mapper class will look like:

class Mapper: EntityMapper<ViewItem, DatabaseItem> {
    override fun transform(dataEntity: DatabaseItem): ViewItem {
    	val viewItem = ViewItem()
	...
        return viewItem
    }
}

If you work only with one entity type, you can use SimpleCleanRecyclerView instead:

val cleanRecycler = list as SimpleCleanRecyclerView<Item>
cleanRecycler.load(adapter = presenterAdapter, cloud = DataService::class, cache = DataCache::class)

Annotation Processor

This library includes a small annotation processor which helps you write even less code. One problem I have identified with generics in view classes is that you can't set your generic type in your xml, so you have to do a casting in your activity like:

val cleanRecycler = viewlist as CleanRecyclerView<ViewItem, DatabaseItem>
cleanRecycler.load(adapter = presenterAdapter, cloud = DataService::class, cache = DataCache::class, mapper: Mapper:class)

The annotation processor included is really simple and it can help you reduce the code above. It works with two annotations: @DataSource and @Mapper. In the previous example, we have two datasources: DataService and DataCache. Also, we use a mapper class to transform DatabaseItem model to ViewItem model. If we annotate both datasources classes with @DataSource and our mapper class with @Mapper, and press build project, the annotation processor will generate for you a custom view called DatabaseItemCleanRecyclerView, which automatically uses with your datasources and your mapper class, so you only have to specify your adapter class.

In your xml file, instead of declaring a view of type CleanRecyclerView, you can reference to your brand new view automatically generated for you:

<com.vicpin.cleanrecycler.DatabaseItemCleanRecyclerView
	android:id="@+id/cleanRecycler"
	...			

And in your activity class, you won't need to perform any casting, you only have to set your adapter to your view:

cleanRecycler.load(adapter = presenterAdapter)

Thas is all! You can see a real example of annotation processor usage in the sample project.

Download

Add it in your root build.gradle at the end of repositories:

allprojects {
    repositories {
	     ...
	     maven { url 'https://jitpack.io' }
    }
}

And add the dependency

dependencies {
  implementation 'com.github.vicpinm:cleanrecyclerview:5.1.2'
  //Optional annotation proccesor
  kapt 'com.github.vicpinm:crv-processor:5.0.1'
}

Versions

Version
5.0.1
5.0.0
5.0.0.beta1