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