Architecture Components

Working With LiveData | LiveData Android Example

Google+ Pinterest LinkedIn Tumblr

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.gradle 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

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 remote server . 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://androidwave.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://androidwave.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://androidwave.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 to 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;

/**
 * Created on : Feb 25, 2019
 * Author     : AndroidWave
 */
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;

/**
 * Created on : Feb 25, 2019
 * Author     : AndroidWave
 */
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 &amp;&amp; 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;

/**
 * Created on : Feb 26, 2019
 * Author     : AndroidWave
 */
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 &amp;&amp; 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 &amp;&amp; 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

Author

Write A Comment