Android LiveData and ViewModel with Example.

Tutorial

In this tutorial, you’ll get started with Android LiveData, we will create simple Nots app that you can add new Nots and also edit or delete Nots.

LIVEDATA:

Livedata is an observable data holder class. Unlike a regular observable, livedata is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures livedata only updates app component observers that are in an active lifecycle state

Benefits of using LiveData

  • Ensures that your ui matches your data status based on the pattern of the observer. So be notified whenever the data changes instead of requesting the data from viewmodel each time
  • No memory leaks: because the observers are linked to their own lifecycle objects, when their lifecycle is destroyed they are automatically cleaned.
  • Change in proper configuration: if an activity or fragment is re-created due to a change in configuration (such as device rotation), the last available location data is received instantly.
  • No crashes due to stopped activitiesif the observer’s lifecycle is inactive, such as in the case of an activity in the back stack, then it doesnt receive any livedata events.

To configure your app to use LiveData , add the LiveData and Room component to the build.gradle file in the app module 

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.materialuiux.androidlivedataandviewmodelwithexample"
        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'
        }
    }


    defaultConfig {
        javaCompileOptions {
            // provide the directory for schema export:
            annotationProcessorOptions {
                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'com.google.android.material:material:1.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    // Recycler View components
    implementation 'androidx.recyclerview:recyclerview:1.0.0'

    // Room components
    implementation "android.arch.persistence.room:runtime:1.1.1"
    annotationProcessor "android.arch.persistence.room:compiler:1.1.1"

    // ViewModel and LiveData components
    implementation "android.arch.lifecycle:extensions:1.1.1"
    annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
    
}

Adding Room Entities

Create a java class with getter and denoted it with @Entity annotations.

import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

/**
 * Entity class to store in Room Database
 */
@Entity(tableName = "note_table")
public class Note {

    /**
     * id that auto generate
     */
    @NonNull
    @PrimaryKey(autoGenerate = true)
    private int UID;

    @ColumnInfo(name = "label")
    private String label;

    @ColumnInfo(name = "content")
    private String content;

    @ColumnInfo(name = "lastEdit")
    private String lastEdit;

    public Note(@NonNull int UID, String label, String content, String lastEdit) {
        this.UID = UID;
        this.label = label;
        this.content = content;
        this.lastEdit = lastEdit;
    }

    @NonNull
    public int getUID() {
        return UID;
    }

    public String getLabel() {
        return label;
    }

    public String getContent() {
        return content;
    }

    public String getLastEdit() {
        return lastEdit;
    }
}

Creating a DAO Interface

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;

@Dao
public interface INoteDao {

    /**
     * Function to insert a note in room database
     *
     * @param note to be inserted in database
     */
    @Insert
    void insertNote(Note note);


    /**
     * Function to Update an note in room database
     *
     * @param note the object to be Update
     */
    @Update
    void updateNote(Note note);


    /**
     * Function to delete an note in room database
     *
     * @param note the object to be deleted
     */
    @Delete
    void deleteNote(Note note);


    /**
     * Get all Notes in database ordered by ASC
     *
     * @return a list with all Contacts
     */
    @Query("SELECT * from note_table ")
    LiveData<List<Note>> getItemList();

}

Create a database holder

Create abstract database class that extends the RoomDatabase. The INoteDao interface class is added as a public abstract member variable. The AppDatabase class is annotated with @Database. The entities attribute is assigned an object that contains all the Java object table mappers and the version is the current version of the database , It is good practice to use singleton approach for the database, so you need to create an static method which will return instance of Database.

import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

@Database(entities = {Note.class}, version = 1, exportSchema = false)
public abstract class NoteRoomDatabase extends RoomDatabase {

    private static NoteRoomDatabase INSTANCE;
    public abstract INoteDao iNoteDao();

    static NoteRoomDatabase getDatabase(final Context context) {
        if (INSTANCE == null) {
            synchronized (NoteRoomDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            NoteRoomDatabase.class, "note_database")
                            .build();
                }
            }
        }
        return INSTANCE;
    }
}

Create Note Repository

Create NoteRepository class so we can mange the data
import android.app.Application;
import android.os.AsyncTask;
import androidx.lifecycle.LiveData;
import java.util.List;

public class NoteRepository {

    private INoteDao myNotsDao;
    private LiveData<List<Note>> noteList;


    NoteRepository(Application application) {
        // initialize the database
        NoteRoomDatabase roomDatabase = NoteRoomDatabase.getDatabase(application);
        myNotsDao = roomDatabase.iNoteDao();
        noteList = myNotsDao.getItemList();
    }


    LiveData<List<Note>> getAllItems() {
        return noteList;
    }

    public void insert(Note note) {
        new insertAsyncTask(myNotsDao).execute(note);
    }

    public void update(Note note) {
        new updateAsyncTask(myNotsDao).execute(note);
    }

    public void delete(Note note) {
        new deleteAsyncTask(myNotsDao).execute(note);
    }

    private static class insertAsyncTask extends AsyncTask<Note, Void, Void> {

        private INoteDao myAsyncDao;

        insertAsyncTask(INoteDao dao) {
            myAsyncDao = dao;
        }

        @Override
        protected Void doInBackground(final Note... params) {
            myAsyncDao.insertNote(params[0]);
            return null;
        }

    }

    private static class deleteAsyncTask extends AsyncTask<Note, Void, Void> {

        private INoteDao myAsyncDao;

        deleteAsyncTask(INoteDao dao) {
            myAsyncDao = dao;
        }

        @Override
        protected Void doInBackground(final Note... params) {
            myAsyncDao.deleteNote(params[0]);
            return null;
        }

    }

    private static class updateAsyncTask extends AsyncTask<Note, Void, Void> {

        private INoteDao myAsyncDao;

        updateAsyncTask(INoteDao dao) {
            myAsyncDao = dao;
        }

        @Override
        protected Void doInBackground(final Note... params) {
            myAsyncDao.updateNote(params[0]);
            return null;
        }

    }
}

Create a ViewModel class

Create NoteViewModel.java the ViewModel will stores your data for UI and it’s lifecycle-aware

import android.app.Application;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.List;


public class NoteViewModel extends AndroidViewModel {

    private NoteRepository myRepository;
    private LiveData<List<Note>> allItems;

    // Create a LiveData with a NoteRepository
    public NoteViewModel(Application application) {
        super(application);
        myRepository = new NoteRepository(application);
        allItems = myRepository.getAllItems();
    }

    void insert(Note note) {
        myRepository.insert(note);
    }
    void delete(Note note) {
        myRepository.delete(note);
    }
    void update(Note note) {
        myRepository.update(note);
    }

    LiveData<List<Note>> getAllItems() {
        return allItems;
    }
}

create MainActivity

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    private NoteViewModel myItemViewModel;
    private FloatingActionButton actionButton;
    private RecyclerView myRecyclerView;

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

        actionButton = findViewById(R.id.Add_Note);
        myRecyclerView = findViewById(R.id.recyclerview);

        // Get the ViewModel.
        myItemViewModel = ViewModelProviders.of(this).get(NoteViewModel.class);

        // pass the viewModel to the adapter
        final NoteListAdapter myAdapter = new NoteListAdapter(this, myItemViewModel);
        myRecyclerView.setAdapter(myAdapter);
        StaggeredGridLayoutManager _sGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
        myRecyclerView.setLayoutManager(_sGridLayoutManager);

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        myItemViewModel.getAllItems().observe(this, new Observer<List<Note>>() {
            @Override
            public void onChanged(@Nullable final List<Note> items) {
                // Update the UI.
                myAdapter.setItems(items);
            }

        });


        actionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // add new Note
                newNoteDialog();
            }
        });

    }

    public void newNoteDialog() {

        final Dialog dialog = new Dialog(this);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setCancelable(false);
        dialog.setContentView(R.layout.add_note_dialog);
        final TextView label = dialog.findViewById(R.id.label_edit_text);
        final TextView content = dialog.findViewById(R.id.content_edit_text);
        final TextView title = dialog.findViewById(R.id.dialog_title);
        final TextView date = dialog.findViewById(R.id.time_line);
        final Button cancel = dialog.findViewById(R.id.btn_cancel);

        final String mDate = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
        title.setText("Create New Note");
        date.setText(mDate);
        cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dialog.dismiss();
            }
        });
        Button ok = dialog.findViewById(R.id.btn_okay);
        ok.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Note note = new Note(0, label.getText().toString(), content.getText().toString(), mDate);
                myItemViewModel.insert(note);
                dialog.dismiss();
            }
        });
        if (dialog.getWindow() != null)
            dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        dialog.show();
    }

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">


<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp" />


<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/Add_Note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:src="@drawable/ic_add" />


</RelativeLayout>

note_dialog.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tool="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:orientation="vertical"
    android:paddingLeft="10dp"
    android:paddingTop="20dp"
    android:paddingRight="10dp"
    android:paddingBottom="10dp">

    <TextView
        android:id="@+id/dialog_title"
        android:textSize="22sp"
        android:textStyle="bold"
        android:layout_width="match_parent"
        android:layout_marginTop="10dp"
        android:layout_height="wrap_content" />

    <EditText
        android:layout_marginTop="5dp"
        android:id="@+id/label_edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Label"
        android:textSize="18sp" />

    <EditText
        android:id="@+id/content_edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:hint="content"
        android:lineSpacingExtra="4dp" />

    <TextView
        android:id="@+id/time_line"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:layout_marginTop="6dp"
        android:gravity="end"
        android:lineSpacingExtra="4dp"
        android:textColor="#d2d2d2"
        android:textSize="14sp" />


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="20dp"
        android:layout_marginTop="5dp"
        android:layout_weight="40"
        android:orientation="horizontal"
        android:weightSum="100">


        <Button
            android:id="@+id/btn_cancel"
            android:layout_width="80dp"
            android:layout_height="wrap_content"
            android:layout_marginRight="30dp"
            android:layout_weight="50"
            android:background="@color/colorPrimaryDark"
            android:gravity="center"
            android:text="CANCEL"
            android:textColor="#ffffffff"
            android:textSize="13dp"
            android:textStyle="bold" />


        <Button
            android:id="@+id/btn_okay"
            android:layout_width="80dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:layout_weight="50"
            android:background="@color/colorPrimary"
            android:gravity="center"
            android:text="OKAY"
            android:textColor="#ffffffff"
            android:textSize="13dp"
            android:textStyle="bold" />

    </LinearLayout>

</LinearLayout>

Create NoteListAdapter

import android.app.Dialog;
import android.content.Context;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class NoteListAdapter extends RecyclerView.Adapter<NoteListAdapter.ItemViewHolder> {

    class ItemViewHolder extends RecyclerView.ViewHolder {

        private TextView label_tx, content_tx, last_edited_tx;
        private ImageView options;

        private ItemViewHolder(View itemView) {
            super(itemView);
            label_tx = itemView.findViewById(R.id.note_label);
            content_tx = itemView.findViewById(R.id.note_content);
            last_edited_tx = itemView.findViewById(R.id.note_last_edit);
            options = itemView.findViewById(R.id.option);

        }

        private void optionDialog(final Note note) {
            final AlertDialog.Builder alert = new AlertDialog.Builder(mContext);
            LinearLayout layout = new LinearLayout(mContext);
            LinearLayout.LayoutParams parms = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            layout.setOrientation(LinearLayout.VERTICAL);
            layout.setLayoutParams(parms);

            layout.setGravity(Gravity.CLIP_VERTICAL);
            layout.setPadding(2, 2, 2, 2);

            TextView edit = new TextView(mContext);
            edit.setText("Edit");
            edit.setPadding(20, 20, 20, 20);
            edit.setGravity(Gravity.LEFT);
            edit.setTextSize(16);

            TextView delete = new TextView(mContext);
            delete.setText("Delete");
            delete.setPadding(20, 20, 20, 20);
            delete.setGravity(Gravity.LEFT);
            delete.setTextSize(16);

            LinearLayout.LayoutParams tv1Params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            layout.addView(edit,tv1Params);
            layout.addView(delete ,tv1Params);

            alert.setView(layout);
            final AlertDialog alertDialog = alert.create();
            alertDialog.show();
            delete.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    myItemViewModel.delete(note);
                    alertDialog.dismiss();
                }
            });
            edit.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    alertDialog.dismiss();
                    editNoteDialog(note);
                }
            });
        }

        public void editNoteDialog(final Note note) {
            final Dialog dialog = new Dialog(mContext);
            dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
            dialog.setCancelable(false);
            dialog.setContentView(R.layout.note_dialog);
            dialog.setTitle("Edit Note");

            final TextView title = dialog.findViewById(R.id.dialog_title);
            final TextView label = dialog.findViewById(R.id.label_edit_text);
            final TextView content = dialog.findViewById(R.id.content_edit_text);
            final TextView date = dialog.findViewById(R.id.time_line);
            final Button cancel = dialog.findViewById(R.id.btn_cancel);

            label.setText(note.getLabel());
            title.setText("Edit Note");
            content.setText(note.getContent());
            final String mDate = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
            date.setText(mDate);
            cancel.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    dialog.dismiss();
                }
            });
            Button ok = dialog.findViewById(R.id.btn_okay);
            ok.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Note newNote = new Note(note.getUID(), label.getText().toString(), content.getText().toString(), mDate);
                    myItemViewModel.update(newNote);
                    dialog.dismiss();
                }
            });
            if (dialog.getWindow() !=null)
                dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            dialog.show();
        }


    }

    private List<Note> myItems;
    private final LayoutInflater myInflater;
    private NoteViewModel myItemViewModel;
    private Context mContext;

    NoteListAdapter(Context context, NoteViewModel myItemViewModel) {
        mContext = context;
        myInflater = LayoutInflater.from(context);
        this.myItemViewModel = myItemViewModel;

    }

    @Override
    public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = myInflater.inflate(R.layout.note_layout, parent, false);
        return new ItemViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(final ItemViewHolder holder, int position) {
        final Note current = myItems.get(position);
        holder.label_tx.setText(current.getLabel());
        holder.content_tx.setText(current.getContent());
        holder.last_edited_tx.setText(current.getLastEdit());
        holder.options.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // dialog for choosing if user want to delete note or modify note.
                holder.optionDialog(current); 
            }
        });
    }

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

    void setItems(List<Note> items) {
        Collections.reverse(items);
        myItems = items;
        notifyDataSetChanged();
    }   
}

note_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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:id="@+id/notes_item_root"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="4dp"
    android:foreground="?android:attr/selectableItemBackground"
    app:cardCornerRadius="2dp"
    app:cardElevation="6dp">


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

        <ImageView
            android:id="@+id/option"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:src="@drawable/ic_more_vert"
            android:layout_alignParentEnd="true" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical">

            <TextView
                android:id="@+id/note_label"
                android:layout_width="wrap_content"
                android:layout_height="0dp"
                android:layout_weight="1.0"
                android:ellipsize="end"
                android:lineSpacingExtra="2dp"
                android:maxLines="14"
                android:paddingLeft="6dp"
                android:paddingTop="6dp"
                android:paddingBottom="4dp"
                android:textColor="@android:color/black"
                android:textSize="18sp"
                tools:text="Label Text" />

            <TextView
                android:id="@+id/note_content"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:lineSpacingExtra="2dp"
                android:maxLines="14"
                android:padding="6dp"
                android:textColor="#413f3f"
                android:textSize="16sp"
                tools:text="Content Text" />

            <TextView
                android:id="@+id/note_last_edit"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:maxLines="1"
                android:padding="6dp"
                android:textColor="?android:textColorSecondary"
                android:textSize="12sp"
                tools:text="2015-5-24:16:05" />
        </LinearLayout>

    </RelativeLayout>
</androidx.cardview.widget.CardView>

Get the full example Github

Leave a Reply

×