Tag

RecyclerView-Example

Browsing

In this android app tutorial, I’ll explain, RecyclerView Kotlin implementation in a step by step. In this sample application, we’ll use Kotlin programming language along with RxAndroid and Dagger2. So let’s get started.

What is RecyclerView

If you are an android developer you already familiar RecyclerView. When you need to show scrolling list based on large data set, you should use RecyclerView.

RecyclerView is a flexible and upgraded version of ListView. In the RecyclerView several different works together to display your data. Basically, RecyclerView works on ViewHolder design pattern. Each row managed by ViewHolder. So you can say, each view holder is in responsible for displaying a single item with a view.

Technology Used

In this RecyclerView Kotlin tutorial, I’ll create a sample application that fetch the users details from server and will displayed on RecyclerView. In this sample application, We’ll use following technology

  • Androidx Support Lib
  • Retrofit for APIs calling
  • RxJava
  • Glide
1. Create a RecyclerView Sample App

Let’s move to Android Studio, create a new project with androidx and choose Kotlin language from language dropdown.

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.recyclerviewexample"
        minSdkVersion 15
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

def lifeCycleExtensionsVersion = '1.1.1'
def retrofitVersion = '2.3.0'
def daggerVersion = '2.13'
def glideVersion = '4.9.0'
def rxJavaVersion = '2.1.1'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
    implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"

    implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
    implementation "io.reactivex.rxjava2:rxandroid:$rxJavaVersion"

    implementation "com.google.dagger:dagger:$daggerVersion"
    implementation "com.google.dagger:dagger-android-support:$daggerVersion"
    kapt "com.google.dagger:dagger-compiler:$daggerVersion"
    kapt "com.google.dagger:dagger-android-processor:$daggerVersion"

    implementation "android.arch.lifecycle:extensions:$lifeCycleExtensionsVersion"

    implementation "com.github.bumptech.glide:glide:$glideVersion"


    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0-beta04'
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
}

After adding these dependencies, Sync the project.

2. Open the MainActvitiy layout file and paste below code.

In this layout file, I’m adding the following widget.

  • RecyclerView for showing user list.
  • AppCompatTextView for showing an error message.
  • ProgressBar for showing loader while getting data from a remote server.
  • SwipeRefreshLayout as a root view for refreshing list item on pull to refresh.
 <?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/swipeRefreshLayout"
        tools:context=".view.MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/usersList"
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginEnd="8dp"
                android:layout_marginRight="8dp"
                app:layout_constraintStart_toStartOf="parent"
                android:layout_marginLeft="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent"/>

        <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/list_error"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:text="@string/error_text"
                android:gravity="center"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginEnd="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                android:layout_marginLeft="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent"/>

        <ProgressBar
                android:id="@+id/loading_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginEnd="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                android:layout_marginLeft="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
2. Create a data model class for below JSON

The JSON payload looks like below. I need to write a data model to parse that JSON.

{
  "page": 1,
  "per_page": 6,
  "total": 12,
  "total_pages": 2,
  "data": [
    {
      "id": 1,
      "email": "george.bluth@reqres.in",
      "first_name": "George",
      "last_name": "Bluth",
      "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"
    },
    .
    . 
  ]
}
2.1 For parsing above JSON I’m creating below data model class named is Data.kt
package com.recyclerviewexample.model

import com.google.gson.annotations.SerializedName

data class Data(
    @SerializedName("page")
    val page: Int,
    @SerializedName("per_page")
    val perPage: Int,
    @SerializedName("total")
    val total: Int,
    @SerializedName("total_pages")
    val totalPages: Int,
    @SerializedName("data")
    val users: List<User>
)
2.2 Create another data model class named is User.kt
package com.recyclerviewexample.model

import com.google.gson.annotations.SerializedName

data class User(
    @SerializedName("avatar")
    val avatar: String,
    @SerializedName("email")
    val email: String,
    @SerializedName("first_name")
    val firstName: String,
    @SerializedName("id")
    val id: Int,
    @SerializedName("last_name")
    val lastName: String
)
3. Prepare an XML layout for showing list user list item.

For displaying the user on a list item in RecyclerView, creates layout named is item_user.xml. It contains ImageView for use image and TextViews for the display name and email.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="@dimen/layout_height"
        android:layout_margin="4dp">

    <ImageView
            android:id="@+id/imageView"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="@dimen/standard_padding" />

    <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:gravity="center_vertical"
            android:orientation="vertical">

        <TextView
                android:id="@+id/name"
                style="@style/Title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="Name" />

        <TextView
                android:id="@+id/email"
                style="@style/Text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="Email" />

    </LinearLayout>

</LinearLayout>
4. Create a RecyclerView Adapter that holds that holds user item.

For the above layout creates a RecycleView holder adapter that holds the user list item

package com.recyclerviewexample.view

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.recyclerviewexample.R
import com.recyclerviewexample.model.User
import com.recyclerviewexample.util.loadImage
import kotlinx.android.synthetic.main.item_user.view.*


class UserListAdapter(var users: ArrayList<User>) :
    RecyclerView.Adapter<UserListAdapter.UserViewHolder>() {

    fun updateUsers(newUsers: List<User>) {
        users.clear()
        users.addAll(newUsers)
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, p1: Int) = UserViewHolder(
        LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false)
    )

    override fun getItemCount() = users.size

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.bind(users[position])
    }

    class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {

        private val imageView = view.imageView
        private val userName = view.name
        private val userEmail = view.email

        fun bind(country: User) {
            userName.text = country.firstName + " " + country.lastName
            userEmail.text = country.email
            imageView.loadImage(country.avatar)
        }
    }
}
5. Create a Util class for with help us to display user profile picture
package com.recyclerviewexample.util

import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.recyclerviewexample.R

fun ImageView.loadImage(uri: String?) {
    val options = RequestOptions()
        .placeholder(R.drawable.loader)
        .circleCrop()
        .error(R.mipmap.ic_launcher_round)
    Glide.with(this.context)
        .setDefaultRequestOptions(options)
        .load(uri)
        .into(this)
}
6. Step up the Retrofit client for calling APIs

In src folder create a new Retrofit calling interface named is UsersApi

package com.recyclerviewexample.model

import io.reactivex.Single
import retrofit2.http.GET

interface UsersApi {

    @GET("users")
    fun getUsers(): Single<Data>
}
7. Now create Dagger module file name is ApiModule

In this dagger module, we are providing Retrofit client and User Service using @provide annotation

package com.recyclerviewexample.di

import com.recyclerviewexample.model.UsersApi
import com.recyclerviewexample.model.UsersService
import dagger.Module
import dagger.Provides
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory

@Module
class ApiModule {

    private val BASE_URL = "https://reqres.in/api/"

    @Provides
    fun provideUsersApi(): UsersApi {
        return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
            .create(UsersApi::class.java)
    }

    @Provides
    fun provideUsersService(): UsersService {
        return UsersService()
    }
}
8. Let prepare to dagger component named is ApiComponent

In this ApiComponent interface, we are injecting User Service and ListView Model that I’ll create later.

package com.recyclerviewexample.di

import com.recyclerviewexample.model.UsersService
import com.recyclerviewexample.viewmodel.ListViewModel
import dagger.Component

@Component(modules = [ApiModule::class])
interface ApiComponent {

    fun inject(service: UsersService)

    fun inject(viewModel: ListViewModel)
}
9. Now I’m creating a service provider class for calling named is UsersService.
package com.recyclerviewexample.model


import com.recyclerviewexample.di.DaggerApiComponent
import io.reactivex.Single
import javax.inject.Inject

class UsersService {

    @Inject
    lateinit var api: UsersApi

    init {
        DaggerApiComponent.create().inject(this)
    }

    fun getUsers(): Single<Data> {
        return api.getUsers()
    }
}
10. Finally, create a subclass of ViewModel name is ListViewModel

Create a class named is ListViewModel, which extends the ViewModel. In this class, we using fetching user list from a remote server and updating ViewModel value.

package com.recyclerviewexample.viewmodel

import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.recyclerviewexample.di.DaggerApiComponent
import com.recyclerviewexample.model.Data
import com.recyclerviewexample.model.User
import com.recyclerviewexample.model.UsersService
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.observers.DisposableSingleObserver
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject

class ListViewModel : ViewModel() {

    @Inject
    lateinit var usersService: UsersService

    init {
        DaggerApiComponent.create().inject(this)
    }

    private val disposable = CompositeDisposable()

    val users = MutableLiveData<List<User>>()
    val userLoadError = MutableLiveData<Boolean>()
    val loading = MutableLiveData<Boolean>()

    fun refresh() {
        fetchUsers()
    }

    private fun fetchUsers() {
        loading.value = true
        disposable.add(
            usersService.getUsers()
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(object : DisposableSingleObserver<Data>() {
                    override fun onSuccess(data: Data) {
                        Log.d("error ", "" + data)
                        users.value = data.users
                        userLoadError.value = false
                        loading.value = false
                    }

                    override fun onError(e: Throwable) {
                        userLoadError.value = true
                        loading.value = false
                        Log.d("error ", "" + e.printStackTrace())
                    }

                })
        )
    }

    override fun onCleared() {
        super.onCleared()
        disposable.clear()
    }
}
11. Open the MainActvitiy and paste below code

Basically, In this class, I observing ViewModel and based on changes. I update the adapter list. Apart from this, I refresh value on pull to refresh using SwipeRefreshLayout component.

package com.recyclerviewexample.view

import android.os.Bundle

import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import com.recyclerviewexample.R
import com.recyclerviewexample.viewmodel.ListViewModel
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {

    lateinit var viewModel: ListViewModel
    private val usersAdapter = UserListAdapter(arrayListOf())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProviders.of(this).get(ListViewModel::class.java)
        viewModel.refresh()

        usersList.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = usersAdapter
        }

        swipeRefreshLayout.setOnRefreshListener {
            swipeRefreshLayout.isRefreshing = false
            viewModel.refresh()
        }

        observeViewModel()
    }

    fun observeViewModel() {
        viewModel.users.observe(this, Observer { countries ->
            countries?.let {
                usersList.visibility = View.VISIBLE
                usersAdapter.updateUsers(it)
            }
        })

        viewModel.userLoadError.observe(this, Observer { isError ->
            isError?.let { list_error.visibility = if (it) View.VISIBLE else View.GONE }
        })

        viewModel.loading.observe(this, Observer { isLoading ->
            isLoading?.let {
                loading_view.visibility = if (it) View.VISIBLE else View.GONE
                if (it) {
                    list_error.visibility = View.GONE
                    usersList.visibility = View.GONE
                }
            }
        })
    }
}

Conclusion

That all about RecyclerView in Kotlin, So guys in this android article, we have learned android Kotlin RecyclerView implementation. I hope it’s helpful for you, then help me by sharing this post on your social media profile such as facebook, twitter, and Linkedin.

If you have any queries please put your comment below.

Android RecyclerView Example in Java

LiveData is introduced as lifecycle-aware data holder with the observer pattern. That means, When something is new in data set it will notify. It automatically changes the view for the changes in the data set. In this tutorial, we will demonstrate LiveData android example

When we have LiveData object(eg. list of customers, can be a list of users, it can be any data sources ), we add some Lifecycle Owner (such as Activity or Fragment) as an observer to this data update. Same as observer pattern but with respecting lifecycle states.

In large application there can be two type of data source. Local Sqlite database, remote Rest APIs . With Live we can write code for observer these data source for data changes and update views accordingly.

Benefits of using LiveData

  • Ensures your UI matches your data state- It based on observer pattern. So we’ll be notified every time the data changes instead of requesting the data each time from ViewModel
  • Avoid memory leaks – Observers bounded with life cycle and when lifecycle is destroyed LiveData object also destroy.
  • No more crashes due to stopped activities- If the observer’s lifecycle is inactive, such as in the case of an activity in the back stack, then it doesn’t receive any LiveData events.
  • No more manual lifecycle handling – UI components just observe relevant data and don’t stop or resume observation. LiveData automatically manages all of this since it’s aware of the relevant lifecycle status changes while observing.
  • Always up to date data – If a lifecycle becomes inactive, it receives the latest data upon becoming active again. For example, an activity that was in the background receives the latest data right after it returns to the foreground.
  • Manage all configuration changes –If an activity or fragment is recreated due to a configuration change, like device rotation, it immediately receives the latest available data.

Step for implementation LiveData

  • Add dependencies into our app/build file
  • Create a subclass of AndroidViewModel or ViewModel (as per instance )
  • Declare an of the mutable live data object. That takes input from the repository or other sources.
  • In Activity create an instance of LiveData and subscribes the observer

LiveData Demo App

Now, I will demonstrate how to implement LiveData in Android application. In this LiveData android example, we use Retrofit2 for Rest API calling and will fetch data from a remote server and display it on RecyclerView with the help of ViewModel and LiveData.

The following component we will using this demo application
  • LifeCycle Awareness
  • ViewModel
  • LiveData
  • Retrofit
  • RxJava we are not using because I don’t want complexity this demo
  • Glide
  • Lamda Expression

1. Create a new Project with EmptyActivity Template

In AndroidStudio, create a new project with EmptyActivity template named is LiveDataExample . Now just add dependency into the app/build.gradle .

    def lifecycle_version = "1.1.1"
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version"

    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    // Glide for image loading
    implementation 'com.github.bumptech.glide:glide:4.8.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'

We also use lambda expression in this project so set compile option 1.8 inside android tag in app/build.gradle.

compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

2. Create Wrapper class for parsing JSON response

I will use retrofit and LiveData for getting data from The rest API endpoint is this https://androidwave.com/api/feed.json. and JSON data like below

{
  "status": "ok",
  "error":false,
  "message":"AndroidWave RSS feed found",
  "data": [
   {
      "title": "Background Limitations Android Oreo",
      "pubDate": "2019-02-09 11:22:31",
      "link": "https://android.com/background-limitations-android-oreo/",
      "author": "admin",
      "thumbnail": "http://35.197.65.22/wp-content/uploads/2019/02/background-limitations-android-oreo-370x247.png",
      "description": "If you are looking at what exactly limitations are applied in background service in Android in Oreo. You are in right place."
    },
   {
      "title": "ViewModel Android Example",
      "pubDate": "2019-02-09 11:22:31",
      "link": "https://android.com/viewmodel-android-example/",
      "author": "admin",
      "thumbnail": "http://35.197.65.22/wp-content/uploads/2019/02/viewmodel-android-example-370x247.png",
      "description": "When you are developing professional-level android application, one of the most common things we need the consider is configuration changes."
    },
   {
      "title": "Working with JobIntentService",
      "pubDate": "2019-02-09 11:22:31",
      "link": "https://android.com/working-with-jobintentservice/",
      "author": "admin",
      "thumbnail": "http://35.197.65.22/wp-content/uploads/2019/02/working-with-job-intent-service-370x247.png",
      "description": "In our previous article, I have explained the limitation of using the IntentService in Android Oreo devices. To overcome this problem android introduced JobIntentService"
    }]
}
2.1 Just create a wrapper class name is BlogWrapper and set getter setter like below
package com.wave.livedataexample.model;

import com.google.gson.annotations.SerializedName;

import java.util.List;

@SuppressWarnings("unused")
public class BlogWrapper {

    @SerializedName("data")
    private List<Blog> mData;
    @SerializedName("error")
    private Boolean mError;
    @SerializedName("message")
    private String mMessage;
    @SerializedName("status")
    private String mStatus;

    public List<Blog> getBlog() {
        return mData;
    }

    public void setBlog(List<Blog> data) {
        mData = data;
    }

    public Boolean getError() {
        return mError;
    }

    public void setError(Boolean error) {
        mError = error;
    }

    public String getMessage() {
        return mMessage;
    }

    public void setMessage(String message) {
        mMessage = message;
    }

    public String getStatus() {
        return mStatus;
    }

    public void setStatus(String status) {
        mStatus = status;
    }

}
2.2 Now create Wrapper class for Blog Item.
package com.wave.livedataexample.model;

import com.google.gson.annotations.SerializedName;

public class Blog {

    @SerializedName("author")
    private String mAuthor;
    @SerializedName("description")
    private String mDescription;
    @SerializedName("link")
    private String mLink;
    @SerializedName("pubDate")
    private String mPubDate;
    @SerializedName("thumbnail")
    private String mThumbnail;
    @SerializedName("title")
    private String mTitle;

    public String getAuthor() {
        return mAuthor;
    }

    public void setAuthor(String author) {
        mAuthor = author;
    }

    public String getDescription() {
        return mDescription;
    }

    public void setDescription(String description) {
        mDescription = description;
    }

    public String getLink() {
        return mLink;
    }

    public void setLink(String link) {
        mLink = link;
    }

    public String getPubDate() {
        return mPubDate;
    }

    public void setPubDate(String pubDate) {
        mPubDate = pubDate;
    }

    public String getThumbnail() {
        return mThumbnail;
    }

    public void setThumbnail(String thumbnail) {
        mThumbnail = thumbnail;
    }

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String title) {
        mTitle = title;
    }

}

3. Create a Repository for interacting to LiveData

For now create a java class named BlogRepository and declare mutable LiveData object like below.

public class BlogRepository {
    private ArrayList<Blog> movies = new ArrayList<>();
    private MutableLiveData<List<Blog>> mutableLiveData = new MutableLiveData<>();
    private Application application;
    
    public BlogRepository(Application application) {
        this.application = application;
    }

}
3.1 For interacting remote create a retrofit interface and expose below methods.

Now setup a retrofit with LiveData for interacting WebService.

package com.wave.livedataexample.service;

import com.wave.livedataexample.model.BlogWrapper;
import retrofit2.Call;
import retrofit2.http.GET;

public interface RestApiService {

    @GET("feed.json")
    Call<BlogWrapper> getPopularBlog();

}
3.2 Furthermore create a retrofit instance like below
package com.wave.livedataexample.service;

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import static com.wave.livedataexample.BuildConfig.BASE_URL;

public class RetrofitInstance {

    private static Retrofit retrofit = null;
    public static RestApiService getApiService() {
        if (retrofit == null) {
            retrofit = new Retrofit
                    .Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit.create(RestApiService.class);
    }
}
3.3 Now Open the app/build.gradle and define BASE_URL inside the config/defaultConfig tag
 buildConfigField "String", "BASE_URL", '"https://androidwave.com/api/"'
3.4 Now open the BlogRepository and expose getMutableLiveData()
public MutableLiveData<List<Blog>> getMutableLiveData() {

        RestApiService apiService = RetrofitInstance.getApiService();
        Call<BlogWrapper> call = apiService.getPopularBlog();
        call.enqueue(new Callback<BlogWrapper>() {
            @Override
            public void onResponse(Call<BlogWrapper> call, Response<BlogWrapper> response) {
                BlogWrapper mBlogWrapper = response.body();
                if (mBlogWrapper != null && mBlogWrapper.getBlog() != null) {
                    movies = (ArrayList<Blog>) mBlogWrapper.getBlog();
                    mutableLiveData.setValue(movies);
                }
            }
            @Override
            public void onFailure(Call<BlogWrapper> call, Throwable t) {
            }
        });
        return mutableLiveData;
    }
3.5 The complete BlogRepository class looks like this
package com.wave.livedataexample.model;

import android.app.Application;
import android.arch.lifecycle.MutableLiveData;
import com.wave.livedataexample.service.RestApiService;
import com.wave.livedataexample.service.RetrofitInstance;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class BlogRepository {
    private ArrayList<Blog> movies = new ArrayList<>();
    private MutableLiveData<List<Blog>> mutableLiveData = new MutableLiveData<>();
    private Application application;

    public BlogRepository(Application application) {
        this.application = application;
    }

    public MutableLiveData<List<Blog>> getMutableLiveData() {

        RestApiService apiService = RetrofitInstance.getApiService();
        Call<BlogWrapper> call = apiService.getPopularBlog();
        call.enqueue(new Callback<BlogWrapper>() {
            @Override
            public void onResponse(Call<BlogWrapper> call, Response<BlogWrapper> response) {
                BlogWrapper mBlogWrapper = response.body();
                if (mBlogWrapper != null && mBlogWrapper.getBlog() != null) {
                    movies = (ArrayList<Blog>) mBlogWrapper.getBlog();
                    mutableLiveData.setValue(movies);
                }
            }

            @Override
            public void onFailure(Call<BlogWrapper> call, Throwable t) { }
        });
        return mutableLiveData;
    }
}

4. Create ViewModel

Create a subclass of ViewModel named MainViewModel (Main is taking from the name of an activity, such as for login activity we can use LoginViewModel ). MainViewModel class is extends AndroidViewModel. So basically two superclasses for ViewModel in Android. ViewModel and AndroidViewModel. different is only visibility of Context. AndroidViewModel has an application Context that by we are using here AndroidViewModel.

package com.wave.livedataexample.viewmodel;

import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.support.annotation.NonNull;

import com.wave.livedataexample.model.Blog;
import com.wave.livedataexample.model.BlogRepository;

import java.util.List;

/**
 * Created on : Feb 26, 2019
 * Author     : AndroidWave
 */
public class MainViewModel extends AndroidViewModel {
    private BlogRepository movieRepository;

    public MainViewModel(@NonNull Application application) {
        super(application);
        movieRepository = new BlogRepository(application);
    }

    public LiveData<List<Blog>> getAllBlog() {
        return movieRepository.getMutableLiveData();
    }


}

Now the LiveData and View Model part is ready to use.

5. Create a XML layout for blog item

In this demo we are showing the blog list over the RecyclerView. So now create a layout for blog item named blog_item.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:background="#fff"
    android:padding="8dp">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:fontFamily="@font/montserrat"
        android:gravity="left"
        android:includeFontPadding="true"
        android:letterSpacing="-0.02"
        android:lineSpacingExtra="5sp"
        android:text="Dagger2 Android Example"
        android:textAlignment="gravity"
        android:textColor="@color/navy"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/ivThumbnail"
        android:layout_width="0dp"
        android:layout_height="210dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:scaleType="fitXY"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle"
        tools:srcCompat="@tools:sample/avatars" />

    <TextView
        android:id="@+id/tvDescription"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:fontFamily="@font/montserrat"
        android:text="https://androidwave.com/dagger2-android-example/"
        android:textColor="@color/navy"
        android:textSize="14sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ivThumbnail" />

    <TextView
        android:id="@+id/tvLink"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:fontFamily="@font/montserrat"
        android:text="https://androidwave.com/dagger2-android-example/"
        android:textColor="@color/windows_blue"
        android:textSize="15sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvDescription" />
</android.support.constraint.ConstraintLayout>

6. Create a RecyclerView adapter named BlogAdapter

package com.wave.livedataexample.ui;

import android.content.Intent;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.wave.livedataexample.R;
import com.wave.livedataexample.model.Blog;

import java.util.List;

/**
 * Created on : Feb 26, 2019
 * Author     : AndroidWave
 */
public class BlogAdapter extends RecyclerView.Adapter<BaseViewHolder> {
    private static final String TAG = "BlogAdapter";

    private List<Blog> mBlogList;

    public BlogAdapter(List<Blog> blogList) {
        mBlogList = blogList;
    }

    @Override
    public void onBindViewHolder(BaseViewHolder holder, int position) {
        holder.onBind(position);
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(
                LayoutInflater.from(parent.getContext()).inflate(R.layout.blog_item, parent, false));


    }

    @Override
    public int getItemViewType(int position) {
        return 0;
    }

    @Override
    public int getItemCount() {
        if (mBlogList != null && mBlogList.size() > 0) {
            return mBlogList.size();
        } else {
            return 0;
        }
    }

    public class ViewHolder extends BaseViewHolder {

        ImageView ivThumbnail;
        TextView tvTitle;
        TextView tvDescription;
        TextView tvLink;

        public ViewHolder(View itemView) {
            super(itemView);
            ivThumbnail = itemView.findViewById(R.id.ivThumbnail);
            tvTitle = itemView.findViewById(R.id.tvTitle);
            tvDescription = itemView.findViewById(R.id.tvDescription);
            tvLink = itemView.findViewById(R.id.tvLink);

        }

        protected void clear() {
            ivThumbnail.setImageDrawable(null);
            tvTitle.setText("");
            tvLink.setText("");
        }

        public void onBind(int position) {
            super.onBind(position);

            final Blog mBlog = mBlogList.get(position);

            if (mBlog.getThumbnail() != null) {
                Glide.with(itemView.getContext())
                        .load(mBlog.getThumbnail())
                        .into(ivThumbnail);
            }

            if (mBlog.getTitle() != null) {
                tvTitle.setText(mBlog.getTitle());
            }

            if (mBlog.getDescription() != null) {
                tvDescription.setText(mBlog.getDescription());
            }

            if (mBlog.getLink() != null) {
                tvLink.setText(mBlog.getLink());
            }

            tvLink.setOnClickListener(v -> {
                if (mBlog.getLink() != null) {
                    try {
                        Intent intent = new Intent();
                        intent.setAction(Intent.ACTION_VIEW);
                        intent.addCategory(Intent.CATEGORY_BROWSABLE);
                        intent.setData(Uri.parse(mBlog.getLink()));
                        itemView.getContext().startActivity(intent);
                    } catch (Exception e) {
                        Log.e(TAG, "onClick: Image url is not correct");
                    }
                }
            });
        }
    }

}
6.1 BaseViewHolder
package com.wave.livedataexample.ui;

import android.support.v7.widget.RecyclerView;
import android.view.View;

/**
 * Created on : Feb 26, 2019
 * Author     : AndroidWave
 */
public abstract class BaseViewHolder extends RecyclerView.ViewHolder {

    private int mCurrentPosition;

    public BaseViewHolder(View itemView) {
        super(itemView);
    }

    protected abstract void clear();

    public void onBind(int position) {
        mCurrentPosition = position;
        clear();
    }

    public int getCurrentPosition() {
        return mCurrentPosition;
    }
}

7. Now Open the activity_main.xml and add RecyclerView inside SwipeRefreshLayout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f2f2f2"
    android:padding="8dp"
    tools:context=".ui.MainActivity">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swiperefresh"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">


        <android.support.v7.widget.RecyclerView
            android:id="@+id/blogRecyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>

8. Now open the MainActivity and do following operation

  1. initialize views such as SwipeRefreshLayout, RecyclerView with help of findViewById
  2. Create the instance of MainViewModel with help of ViewModelProviders
  3. Applied the getAllBlog() observer on ViewModel that observe List<Blog>>
  4. Finally, prepare BlogAadapter and set on RecyclerView
  5. At last add internet permission in manifest (<uses-permission android:name=”android.permission.INTERNET” />)

Finally, MainActivity source code seems like below

package com.wave.livedataexample.ui;

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import com.wave.livedataexample.R;
import com.wave.livedataexample.model.Blog;
import com.wave.livedataexample.viewmodel.MainViewModel;

import java.util.List;

public class MainActivity extends AppCompatActivity {


    RecyclerView mRecyclerView;
    SwipeRefreshLayout swipeRefresh;
    private MainViewModel mainViewModel;

    BlogAdapter mBlogAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initializationViews();
        mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
        getPopularBlog();
        // lambda expression
        swipeRefresh.setOnRefreshListener(() -> {
            getPopularBlog();
        });
    }

    private void initializationViews() {
        swipeRefresh = findViewById(R.id.swiperefresh);
        mRecyclerView = findViewById(R.id.blogRecyclerView);
    }

    public void getPopularBlog() {
        swipeRefresh.setRefreshing(true);
        mainViewModel.getAllBlog().observe(this, new Observer<List<Blog>>() {
            @Override
            public void onChanged(@Nullable List<Blog> blogList) {
                swipeRefresh.setRefreshing(false);
                prepareRecyclerView(blogList);
            }
        });
        /**
         * Replace this statement with lambda expression
         * For using set you have to set following lines in app/build.gradle
             // add below line
             compileOptions {
             sourceCompatibility JavaVersion.VERSION_1_8
             targetCompatibility JavaVersion.VERSION_1_8
             }
             // reduce line of code
             mainViewModel.getAllBlog().observe(this, blogList -> prepareRecyclerView(blogList));

         */

    }

    private void prepareRecyclerView(List<Blog> blogList) {

        mBlogAdapter = new BlogAdapter(blogList);
        if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
            mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        } else {
            mRecyclerView.setLayoutManager(new GridLayoutManager(this, 4));

        }
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mRecyclerView.setAdapter(mBlogAdapter);
        mBlogAdapter.notifyDataSetChanged();

    }

}

After following all the above step, build the project and RUN it. The output will be as expected. If you have any queries, feel free to ask them in the comment section below. Happy Coding 🙂

Download Sample Project- LiveData Android Example

In this post, I will learn the right way of implementation RecyclerView in Android. In this RecyclerView Android Example, We will create a custom layout with CardView and render it on RecyclerView. We discuss in detail about some relevant classes such as LayoutManager, ViewHolder, Adapter, ItemDecoration etc.

Introduction

RecyclerView is the most powerful, advanced and flexible version of ListView. In RecyclerView is used several different components work together to display data. Using the standard layout managers  (such as LinearLayoutManager or GridLayoutManager ), you can create the list and grid both very easily.

RecyclerView model is a lot of optimized and powerful. As the suggest RecyclerView is used the reusable cell when scrolling up and down. Suppose the view is displaying list positions 0 to 9 the RecyclerView has bind those view holders when user scroll down RecyclerView reuse the cell and bind with holder. One more improvement in RecyclerView is that allows us to set layout managers at runtime so we can manage to scroll, such as vertical or horizontal and manage view in such as list, grid, and Staggered layout.

Prerequisite

You need to have knowledge of following classes that used in RecyclerView implementation
  • Adapter – Provides the data model and responsible for rendering the views for the individual cell
  • ViewHolder – Contains instances for all views that are filled by the data of the entry
  • LayoutManager – Allows us to set the LayoutManagers at runtime. eg. LinearLayoutManager, StaggeredLayoutManager, GridLayoutManager
RecyclerView Android Example (Demo App )

Add the support library

Open the build.gradle file in app module and add followings dependency

dependencies {
  implementation fileTree(dir: 'libs', include: ['*.jar'])
  implementation 'androidx.appcompat:appcompat:1.1.0'
  implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
  testImplementation 'junit:junit:4.12'
  androidTestImplementation 'androidx.test.ext:junit:1.1.1'
  androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

  implementation 'androidx.cardview:cardview:1.0.0'
  implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha01'
  implementation 'com.google.android.material:material:1.2.0-alpha03'
  implementation 'com.google.code.gson:gson:2.8.5'
  // ButterKnife for view binding
  implementation 'com.jakewharton:butterknife:10.1.0'
  annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
  // Glide for image loading
  implementation 'com.github.bumptech.glide:glide:4.10.0'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
}
Set Java version 1.8 for using lambda expression
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
Open the color.xml in from res=>value folder and add color values
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <color name="colorPrimary">#008577</color>
  <color name="colorPrimaryDark">#00574B</color>
  <color name="colorAccent">#FF9800</color>
  <color name="colorPrimaryText">#212121</color>
  <color name="colorSecondaryText">#757575</color>
  <color name="colorIcons">#FFFFFF</color>
  <color name="colorDivider">#BDBDBD</color>
  <color name="black_effective">#2a2a2a</color>
</resources>
Go to string.xml from res => values=> string.xml and add some string values

Open string.xml add string values and demo content arrays for showing in the list.

<resources>
  <string name="app_name">RecyclerView Example</string>

  <string name="title_placeholder">Title</string>
  <string name="news_placeholder">News</string>
  <string name="sports_info_placeholder">Here is some news</string>
  <string name="empty_screen">We do not have anything\nhere right now. Please Retry</string>
  <string name="btn_retry">Retry</string>

  <string-array name="sports_images">
    <item>https://androidwave.com/media/images/img_baseball.jpg</item>
    <item>https://androidwave.com/media/images/img_badminton.jpg</item>
    <item>https://androidwave.com/media/images/img_basketball.jpg</item>
    <item>https://androidwave.com/media/images/img_bowling.jpg</item>
    <item>https://androidwave.com/media/images/img_cycling.jpg</item>
    <item>https://androidwave.com/media/images/img_golf.jpg</item>
    <item>https://androidwave.com/media/images/img_running.jpg</item>
    <item>https://androidwave.com/media/images/img_soccer.jpg</item>
    <item>https://androidwave.com/media/images/img_swimming.jpg</item>
    <item>https://androidwave.com/media/images/img_tabletennis.jpg</item>
    <item>https://androidwave.com/media/images/img_tennis.jpg</item>
  </string-array>
  <string-array name="sports_titles">
    <item>Baseball</item>
    <item>Badminton</item>
    <item>Basketball</item>
    <item>Bowling</item>
    <item>Cycling</item>
    <item>Golf</item>
    <item>Running</item>
    <item>Soccer</item>
    <item>Swimming</item>
    <item>Table Tennis</item>
    <item>Tennis</item>
  </string-array>

  <string-array name="sports_info">
    <item>Nike Enters 10-Year Partnership With Major League Baseball! </item>
    <item>Indonesia Masters – Saina Nehwal sails into semifinals PV Sindhu, Kidambi Srikanth lose in quarters!</item>
    <item>College Basketball 2018–19 Player of the Year Power Rankings Everyone is chasing Dukes Zion Williamson!</item>
    <item>Local bowler lands 132 perfect games, shares his secret for success!</item>
    <item>Sierra solos to win at womens Cadel Evans Great Ocean Road Race!</item>
    <item>Tiger Woods Friday live blog: Woods overcomes mid-round hiccup to make the cut at Torrey Pines!</item>
    <item>The 5km fun run was organised by Ironman England as part of its annual three-day triathlon festival in Bolton, Manchester in a bid to attract more female runners to the event!</item>
    <item>Soccer insider notes: Liverpool not the only Premier League club eyeing Weston McKennie!</item>
    <item>Chloe Grimme, a USA Swimming Scholastic All American from Land O Lakes, Florida, announced on social media that she has verbally committed to the University of Florida for 2020–21!</item>
    <item>Table Tennis Federation Revokes Soumyajit Ghoshs Suspension, 9 Months After Rape Allegations!</item>
    <item>2019 Australian Open odds, predictions for mens finals!</item>
  </string-array>
</resources>
Create a class with name BaseViewHolder

Go to src and create a new Java class with BaseViewHolder which extends RecyclerView. boilerplate code.

package com.recyclerviewexample;

import android.view.View;
import androidx.recyclerview.widget.RecyclerView;

public abstract class BaseViewHolder extends RecyclerView.ViewHolder {

  private int mCurrentPosition;

  public BaseViewHolder(View itemView) {
    super(itemView);
  }

  protected abstract void clear();

  public void onBind(int position) {
    mCurrentPosition = position;
    clear();
  }

  public int getCurrentPosition() {
    return mCurrentPosition;
  }
}

Write an Adapter class

Let’s start writing an adapter class withhold the list item and render on RecyclerView in Android.

Let’s Create a Model Class with named Sport.java

Create a new POJO class, and define some entity such as title, subtitle, and news discerption with getter setter.

package com.recyclerviewexample;

import com.google.gson.annotations.SerializedName;

public class Sport {

  @SerializedName("imageUrl")
  private String mImageUrl;
  @SerializedName("info")
  private String mInfo;
  @SerializedName("subTitle")
  private String mSubTitle;
  @SerializedName("title")
  private String mTitle;

  public Sport(String mImageUrl, String mInfo, String mSubTitle, String mTitle) {
    this.mImageUrl = mImageUrl;
    this.mInfo = mInfo;
    this.mSubTitle = mSubTitle;
    this.mTitle = mTitle;
  }

  public String getImageUrl() {
    return mImageUrl;
  }

  public void setImageUrl(String imageUrl) {
    mImageUrl = imageUrl;
  }

  public String getInfo() {
    return mInfo;
  }

  public void setInfo(String info) {
    mInfo = info;
  }

  public String getSubTitle() {
    return mSubTitle;
  }

  public void setSubTitle(String subTitle) {
    mSubTitle = subTitle;
  }

  public String getTitle() {
    return mTitle;
  }

  public void setTitle(String title) {
    mTitle = title;
  }
}
Create a xml layout with list_item.xml 

Just go to res directory and create an xml layout for displaying a single row of RecyclerView. Which contains some ImageView, TextView along CardView.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="@dimen/_4dp"
    android:background="@color/colorIcons"
    >

  <androidx.cardview.widget.CardView
      android:id="@+id/card_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_gravity="center"
      android:elevation="3dp"
      android:padding="@dimen/card_view_margin"
      card_view:cardCornerRadius="@dimen/card_radius"
      >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

      <ImageView
          android:id="@+id/thumbnail"
          android:layout_width="match_parent"
          android:layout_height="130dp"
          android:background="?attr/selectableItemBackgroundBorderless"
          android:scaleType="fitXY"
          />

      <TextView
          android:id="@+id/title"
          style="@style/TextAppearance.AppCompat.Headline"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_alignBottom="@+id/thumbnail"
          android:layout_alignParentStart="true"
          android:layout_marginStart="@dimen/_8dp"
          android:layout_marginBottom="8dp"
          android:padding="@dimen/_8dp"
          android:text="@string/title_placeholder"
          android:textColor="@color/colorIcons"
          />

      <TextView
          android:id="@+id/newsTitle"
          style="@style/TextAppearance.AppCompat.Subhead"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_below="@+id/thumbnail"
          android:layout_alignParentStart="true"
          android:layout_marginStart="@dimen/_8dp"
          android:padding="@dimen/_8dp"
          android:text="@string/news_placeholder"
          android:textStyle="bold"
          />

      <TextView
          android:id="@+id/newsInfo"
          style="@style/TextAppearance.AppCompat.Subhead"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_below="@+id/newsTitle"
          android:layout_alignParentStart="true"
          android:layout_marginStart="@dimen/_8dp"
          android:layout_marginBottom="@dimen/_8dp"
          android:padding="@dimen/_8dp"
          android:text="@string/sports_info_placeholder"
          android:layout_alignParentLeft="true"
          android:layout_marginLeft="@dimen/_8dp"
          />

    </RelativeLayout>

  </androidx.cardview.widget.CardView>

</LinearLayout>

Create an XML layout that renders at the time when no item added in the list. so create XML class with named is item_empty_view.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

  <TextView
      android:id="@+id/tv_message"
      style="@style/TextAppearance.AppCompat.Headline"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="8dp"
      android:gravity="center"
      android:text="@string/empty_screen"
      android:textColor="@color/colorSecondaryText"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />

  <Button
      android:id="@+id/buttonRetry"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="16dp"
      android:layout_marginEnd="8dp"
      android:background="@color/colorAccent"
      android:text="@string/btn_retry"
      android:textColor="@color/colorIcons"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/tv_message"
      />


</androidx.constraintlayout.widget.ConstraintLayout>
Create a new java class with named is SportAdapter.java

In Java source folder create a SportAdapter.java file which RecyclerView.Adapter<BaseViewHolder> , In Adapter class onCreateViewHolder() is responsible to inflate list_tem.xml and item_empty_view.xml of each row and onBindViewHolder()  is bind the data of each row

package com.recyclerviewexample;

import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.bumptech.glide.Glide;
import java.util.List;

public class SportAdapter extends RecyclerView.Adapter<BaseViewHolder> {
  private static final String TAG = "SportAdapter";
  public static final int VIEW_TYPE_EMPTY = 0;
  public static final int VIEW_TYPE_NORMAL = 1;

  private Callback mCallback;
  private List<Sport> mSportList;

  public SportAdapter(List<Sport> sportList) {
    mSportList = sportList;
  }

  public void setCallback(Callback callback) {
    mCallback = callback;
  }

  @Override
  public void onBindViewHolder(BaseViewHolder holder, int position) {
    holder.onBind(position);
  }

  @Override
  public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    switch (viewType) {
      case VIEW_TYPE_NORMAL:
        return new ViewHolder(
            LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false));
      case VIEW_TYPE_EMPTY:
      default:
        return new EmptyViewHolder(
            LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_empty_view, parent, false));
    }
  }

  @Override
  public int getItemViewType(int position) {
    if (mSportList != null && mSportList.size() > 0) {
      return VIEW_TYPE_NORMAL;
    } else {
      return VIEW_TYPE_EMPTY;
    }
  }

  @Override
  public int getItemCount() {
    if (mSportList != null && mSportList.size() > 0) {
      return mSportList.size();
    } else {
      return 1;
    }
  }

  public void addItems(List<Sport> sportList) {
    mSportList.addAll(sportList);
    notifyDataSetChanged();
  }

  public interface Callback {
    void onEmptyViewRetryClick();
  }

  public class ViewHolder extends BaseViewHolder {

    @BindView(R.id.thumbnail)
    ImageView coverImageView;

    @BindView(R.id.title)
    TextView titleTextView;

    @BindView(R.id.newsTitle)
    TextView newsTextView;

    @BindView(R.id.newsInfo)
    TextView infoTextView;

    public ViewHolder(View itemView) {
      super(itemView);
      ButterKnife.bind(this, itemView);
    }

    protected void clear() {
      coverImageView.setImageDrawable(null);
      titleTextView.setText("");
      newsTextView.setText("");
      infoTextView.setText("");
    }

    public void onBind(int position) {
      super.onBind(position);

      final Sport mSport = mSportList.get(position);

      if (mSport.getImageUrl() != null) {
        Glide.with(itemView.getContext())
            .load(mSport.getImageUrl())
            .into(coverImageView);
      }

      if (mSport.getTitle() != null) {
        titleTextView.setText(mSport.getTitle());
      }

      if (mSport.getSubTitle() != null) {
        newsTextView.setText(mSport.getSubTitle());
      }

      if (mSport.getInfo() != null) {
        infoTextView.setText(mSport.getInfo());
      }

      itemView.setOnClickListener(v -> {
        if (mSport.getImageUrl() != null) {
          try {
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_VIEW);
            intent.addCategory(Intent.CATEGORY_BROWSABLE);
            intent.setData(Uri.parse(mSport.getImageUrl()));
            itemView.getContext().startActivity(intent);
          } catch (Exception e) {
            Log.e(TAG, "onClick: Image url is not correct");
          }
        }
      });
    }
  }

  public class EmptyViewHolder extends BaseViewHolder {

    @BindView(R.id.tv_message)
    TextView messageTextView;
    @BindView(R.id.buttonRetry)
    TextView buttonRetry;

    EmptyViewHolder(View itemView) {
      super(itemView);
      ButterKnife.bind(this, itemView);
      buttonRetry.setOnClickListener(v -> mCallback.onEmptyViewRetryClick());
    }

    @Override
    protected void clear() {

    }
  }
}

In Adapter class you are seeing a callback interface which responsible to retry API or case network failure and server So call back method will call on click of button

  buttonRetry.setOnClickListener(v -> mCallback.onEmptyViewRetryClick());
Add RecyclerView widget in activity_main.xml

You choose BasicActivity template while creating a project MainActivity and layout file will automatically be created . open activity_main.xml and add RecyclerView widget

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f2f2f2"
    tools:context=".MainActivity"
    >

  <androidx.recyclerview.widget.RecyclerView
      android:id="@+id/mRecyclerView"
      android:layout_width="0dp"
      android:layout_height="0dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="8dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />

</androidx.constraintlayout.widget.ConstraintLayout>
Open the MainActivity and add below code

Basically following things we have to do in this class

  • Bind view by id using ButterKnife.
  • Setup LinearLayoutManager and DefaultItemAnimator on RecyclerView.
  • Prepare demo content (In your you can fetch date from the remote server using retrofit).
  • Set the Adapter on RecyclerView
  • Implement Callback methods and call Retry method in case of no item in list
package com.recyclerviewexample;

import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.recyclerviewexample.utils.CommonUtils;
import com.recyclerviewexample.utils.DividerItemDecoration;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity implements SportAdapter.Callback {

  @BindView(R.id.mRecyclerView)
  RecyclerView mRecyclerView;
  SportAdapter mSportAdapter;

  LinearLayoutManager mLayoutManager;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
    setUp();
  }

  private void setUp() {
    mLayoutManager = new LinearLayoutManager(this);
    mLayoutManager.setOrientation(RecyclerView.VERTICAL);
    mRecyclerView.setLayoutManager(mLayoutManager);
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());
    Drawable dividerDrawable = ContextCompat.getDrawable(this, R.drawable.divider_drawable);
    mRecyclerView.addItemDecoration(new DividerItemDecoration(dividerDrawable));
    mSportAdapter = new SportAdapter(new ArrayList<>());

    prepareDemoContent();
  }

  private void prepareDemoContent() {
    CommonUtils.showLoading(MainActivity.this);
    new Handler().postDelayed(() -> {
      //prepare data and show loading
      CommonUtils.hideLoading();
      ArrayList<Sport> mSports = new ArrayList<>();
      String[] sportsList = getResources().getStringArray(R.array.sports_titles);
      String[] sportsInfo = getResources().getStringArray(R.array.sports_info);
      String[] sportsImage = getResources().getStringArray(R.array.sports_images);
      for (int i = 0; i < sportsList.length; i++) {
        mSports.add(new Sport(sportsImage[i], sportsInfo[i], "News", sportsList[i]));
      }
      mSportAdapter.addItems(mSports);
      mRecyclerView.setAdapter(mSportAdapter);
    }, 2000);
  }

  @Override
  public void onEmptyViewRetryClick() {
    prepareDemoContent();
  }
}
Add Divider DividerItemDecoration

You are seeing I have added divider between to cell by using DividerItemDecoration. You can manage color and space easily by using below code

Create java class with named DividerItemDecoration which extends RecyclerView.ItemDecoration

package com.recyclerviewexample.utils;

import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

  private Drawable mDivider;
  private int mOrientation;

  /**
   * Sole constructor. Takes in a {@link Drawable} to be used as the interior
   * divider.
   *
   * @param divider A divider {@code Drawable} to be drawn on the RecyclerView
   */
  public DividerItemDecoration(Drawable divider) {
    mDivider = divider;
  }

  /**
   * Draws horizontal or vertical dividers onto the parent RecyclerView.
   *
   * @param canvas The {@link Canvas} onto which dividers will be drawn
   * @param parent The RecyclerView onto which dividers are being added
   * @param state The current RecyclerView.State of the RecyclerView
   */
  @Override
  public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
    if (mOrientation == LinearLayoutManager.HORIZONTAL) {
      drawHorizontalDividers(canvas, parent);
    } else if (mOrientation == LinearLayoutManager.VERTICAL) {
      drawVerticalDividers(canvas, parent);
    }
  }

  /**
   * Determines the size and location of offsets between items in the parent
   * RecyclerView.
   *
   * @param outRect The {@link Rect} of offsets to be added around the child
   * view
   * @param view The child view to be decorated with an offset
   * @param parent The RecyclerView onto which dividers are being added
   * @param state The current RecyclerView.State of the RecyclerView
   */
  @Override
  public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
      RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);

    if (parent.getChildAdapterPosition(view) == 0) {
      return;
    }

    mOrientation = ((LinearLayoutManager) parent.getLayoutManager()).getOrientation();
    if (mOrientation == LinearLayoutManager.HORIZONTAL) {
      outRect.left = mDivider.getIntrinsicWidth();
    } else if (mOrientation == LinearLayoutManager.VERTICAL) {
      outRect.top = mDivider.getIntrinsicHeight();
    }
  }

  /**
   * Adds dividers to a RecyclerView with a LinearLayoutManager or its
   * subclass oriented horizontally.
   *
   * @param canvas The {@link Canvas} onto which horizontal dividers will be
   * drawn
   * @param parent The RecyclerView onto which horizontal dividers are being
   * added
   */
  private void drawHorizontalDividers(Canvas canvas, RecyclerView parent) {
    int parentTop = parent.getPaddingTop();
    int parentBottom = parent.getHeight() - parent.getPaddingBottom();

    int childCount = parent.getChildCount();
    for (int i = 0; i < childCount - 1; i++) {
      View child = parent.getChildAt(i);

      RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();

      int parentLeft = child.getRight() + params.rightMargin;
      int parentRight = parentLeft + mDivider.getIntrinsicWidth();

      mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom);
      mDivider.draw(canvas);
    }
  }

  /**
   * Adds dividers to a RecyclerView with a LinearLayoutManager or its
   * subclass oriented vertically.
   *
   * @param canvas The {@link Canvas} onto which vertical dividers will be
   * drawn
   * @param parent The RecyclerView onto which vertical dividers are being
   * added
   */
  private void drawVerticalDividers(Canvas canvas, RecyclerView parent) {
    int parentLeft = parent.getPaddingLeft();
    int parentRight = parent.getWidth() - parent.getPaddingRight();

    int childCount = parent.getChildCount();
    for (int i = 0; i < childCount - 1; i++) {
      View child = parent.getChildAt(i);

      RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();

      int parentTop = child.getBottom() + params.bottomMargin;
      int parentBottom = parentTop + mDivider.getIntrinsicHeight();

      mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom);
      mDivider.draw(canvas);
    }
  }
}

Create a drawable class and manage space and divider color here

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <size android:height="@dimen/_4dp" />
    <solid android:color="#EEEEEE" />
</shape>

Finally set on RecyclerView

  Drawable dividerDrawable = ContextCompat.getDrawable(this, R.drawable.divider_drawable);
    mRecyclerView.addItemDecoration(new DividerItemDecoration(dividerDrawable));

Download Source Code – RecyclerView android example

Exit mobile version