Live Data – Life cycle Aware Component

live data android example
[yasr_overall_rating] 280 Users rated 05/05, 300 people voted.

Live Data – Lifecycle Aware Component

  • In 2017 Google introduced Lifecycle aware jetpack library. This library handles the memory leaks and solving common android lifecycle challenges like screen orientation, activity destroyed.
  • In Android, views (Activity, Fragments, etc) can be destroyed at any time, so reference to these may lead to a memory leak or results in NullPointerException.
  • In the same way LiveData is designed to communicate between the UI(Usually View Model as its a lifecycle aware component) and View Controller (Activity, Fragment) With LiveData, this communication is SAFE as the data will only be received when the View is Active.

What is LiveData?

  • Similarly, Observable Data Holder => which means it observes the data and if any changes have been made or new data has been added the owner who holds this object will get notified.
  • It is Lifecycle Aware => well aware about activity and fragment or you can say the owner or components lifecycle which holds this object. That means it will only notify UI which has active state of lifecycle.
  • Usually works best with View Model class
  • It is one of the components in Android Jetpack library.

 

NOTE: This document contains LiveData + RoomDatabase

Priority has been given to LiveData not to room db that much.

LiveData basic concepts have been covered

 

LiveData Setup

 

Live Data – Lifecycle Aware Component
Live Data – Lifecycle Aware Component

 

Refer : https://codelabs.developers.google.com/codelabs/android-training-livedata-viewmodel/#0

 

Design Pattern to be followed – MVVM (MODEL – VIEW – VIEWMODEL)

 

Steps to Add Live Data Component

Add Dependencies in Gradle.

 

// Room components

implementation “android.arch.persistence.room:runtime:1.1.1”

annotationProcessor “android.arch.persistence.room:compiler:1.1.1”

androidTestImplementation “android.arch.persistence.room:testing:1.1.1”

// Lifecycle components

implementation “android.arch.lifecycle:extensions:1.1.1”

annotationProcessor “android.arch.lifecycle:compiler:1.1.1”

Setup the Room Database

Room Database – Another android jetpack library component , lifecycle aware

– Furthermore provides compile time verification for sql queries

As your schema changes, you need to update the affected SQL queries manually in Sqlite. Room solves this problem.

– supports LiveData efficiently

Add a DataModel class which will define the schema of the database.

 

Room persistent library provide us annotation to define the database table schema

@Entity(tableName = “word_table”)  => Mention Database Name

public class WordModel {

   @PrimaryKey(autoGenerate = true) => Autogenerated primary key not null , specify column name

   @NonNull

   @ColumnInfo(name = “word_id”)

   private int id;

   @NonNull

   @ColumnInfo(name = “word_name”)

   private String word;

   public WordModel(String word)

   {

       this.word = word;

   }

   public int getId() {

       return id;

   }

   public String getWord() {

       return word;

   }

   public void setId(int id) {

       this.id = id;

   }

}

 

Add database DAO interface

– The DAO  be an interface or abstract class.

– Equally the room uses  DAO to create a new clean API for your code.

– By default, all queries (@Query) must be executed on a thread other than the main thread.

 

– For operations such as inserting or deleting, if you use the provided convenience annotations, Room takes care of thread management for you.

@Dao

public interface WordDAO {

   @Insert(onConflict = OnConflictStrategy.IGNORE)

   void insert(WordModel word);

   @Query(“DELETE FROM word_table”)

   void deleteAll();

   //ordering the words is not strictly necessary. However, by default, return order is not guaranteed, and ordering makes testing straightforward.

   @Query(“SELECT * from word_table ORDER BY word_name ASC”)

   LiveData<List<WordModel>> getAllWords();

}

Add RoomDatabase class

//Room is a database layer on top of an SQLite database.

// Room takes care of tasks that you used to handle with a database helper class such as SQLiteOpenHelper.

@Database(entities = {WordModel.class} , version = 1, exportSchema = false)

//set exportschema to false, export schema keeps a history of schema versions.

//For this practical, you can disable it, since you are not migrating the database

public abstract class WordRoomDatabase extends RoomDatabase {

   //Room uses the DAO to issue queries to its database.

   //By default, to avoid poor UI performance, Room doesn’t allow you to issue database queries on the main thread.

   //LiveData applies this rule by automatically running the query asynchronously on a background thread when needed.

   //Room provides compile-time checks of SQLite statements.

   //Room class must be abstract and extend RoomDatabase.

   //Usually, only need one instance of the Room database for the whole app.

   public abstract WordDAO wordDao();

   private static WordRoomDatabase INSTANCE;

   //Create the WordRoomDatabase as a singleton to prevent having multiple instances of the database opened at the same time.

   public static WordRoomDatabase getDatabase(final Context mContext)

   {

       if(INSTANCE == null)

       {

           synchronized (WordRoomDatabase.class)

           {

               if(INSTANCE == null)

               {

                   //Create database here

                   INSTANCE = Room.databaseBuilder(mContext.getApplicationContext() ,

                           WordRoomDatabase.class ,

                           “word_database”)

                           // Wipes and rebuilds instead of migrating

                           // if no Migration object.

                           // Migration is not part of this practical.

                           .fallbackToDestructiveMigration()

                           .addCallback(sRoomDatabaseCallback) //callback to delete and insert new records in db

                           .build();

               }

           }

       }

       return INSTANCE;

   }

   @NonNull

   @Override

   protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config) {

       return null;

   }

   @NonNull

   @Override

   protected InvalidationTracker createInvalidationTracker() {

       return null;

   }

   @Override

   public void clearAllTables() {

   }

   //To delete content and repopulate the database whenever the app restarted,

   // you create a RoomDatabase.Callback and override the onOpen() method.

   // you cannot do Room database operations on the UI thread,

   // onOpen() creates and executes an AsyncTask to add content to the database

   private static RoomDatabase.Callback sRoomDatabaseCallback =

           new RoomDatabase.Callback(){

               @Override

               public void onOpen (@NonNull SupportSQLiteDatabase db){

                   super.onOpen(db);

                   new PopulateDbAsync(INSTANCE).execute();

               }

           };

   private static class PopulateDbAsync extends AsyncTask<Void ,Void, Void> {

       private WordDAO mDao;

       String[] words = {“dolphin”, “crocodile”, “cobra”};

       public PopulateDbAsync(WordRoomDatabase db) {

           mDao = db.wordDao();

       }

       @Override

       protected Void doInBackground(Void… voids) {

           // Start an app with a clean database every time.

           // Not needed if you only populate the database

           // when it is first created

           Log.d(“Delete”,“::”+mDao.getAllWords());

           mDao.deleteAll();

           for (int i = 0; i <= words.length 1; i++) {

               WordModel word = new WordModel(words[i]);

               mDao.insert(word);

               Log.d(“Insert”,“::”+word);

           }

           return null;

       }

   }

}

Add Repository

Repository implements the logic for deciding whether to fetch data from a network or use results cached in the local database.

public class WordRepository {

   private WordDAO wordDao;

   private LiveData<List<WordModel>> mWordList;

   //to initialize the database and member variable

   public WordRepository(Application application)

   {

       WordRoomDatabase db = WordRoomDatabase.getDatabase(application);

       wordDao = db.wordDao();

       mWordList = wordDao.getAllWords();

   }

   //A wrapper method , returns the cached words as LiveData

   //Room executes all queries on seperate thread

   //Observed LiveData notified the observer when the data changes

   public LiveData<List<WordModel>> getAllWords()

   {

       return mWordList;

   }

   //Wrapper for insert

   //Use an asynctask to call insert , on non UI thread , or your app will crash

   public void insert(WordModel wordModel)

   {

       new InsertAsynkTask(wordDao).execute(wordModel);

   }

   private static class InsertAsynkTask extends AsyncTask<WordModel , Void, Void>

   {

       private WordDAO wordDao;

       InsertAsynkTask(WordDAO wordDAO)

       {

           this.wordDao = wordDAO;

       }

       @Override

       protected Void doInBackground(WordModel… wordModels) {

           for (WordModel word : wordModels)

           wordDao.insert(word);

           return null;

       }

   }

}

Add ViewModel class to handle the UI data across the configuration change

– provide data to UI and survive configuration changes acts as mediator between repository and UI

 

public class WordViewModel extends AndroidViewModel {

   //Never pass context into ViewModel instances.

   //Do not store Activity, Fragment, or View instances or their Context in the ViewModel.

   private WordRepository wordRepository;

   private LiveData<List<WordModel>> mWordList;

   //to get the reference of the word repository

   //to get the list of words from word repository

   public WordViewModel(@NonNull Application application)

   {

       super(application);

       wordRepository = new WordRepository(application);

       mWordList = wordRepository.getAllWords();

   }

   //get all the list of words

   //hides the implementation from the ui

   public LiveData<List<WordModel>> getAllWords()

   {

       return mWordList;

   }

   //wrapper method that calls the repository insert()

   //hides the implementation from UI

   public void insert(WordModel wordModel)

   {

       wordRepository.insert(wordModel);

   }

}

 Create XML , MainActivity , Adapters

 

=> ADAPTER

<?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=“wrap_content”>

   <TextView

       android:id=“@+id/textViewWord”

       android:layout_width=“match_parent”

       android:layout_height=“wrap_content”

       android:visibility=“visible”/>

</RelativeLayout>

public class WordListAdapter extends RecyclerView.Adapter<WordListAdapter.ViewHolder> {

   private LayoutInflater mInflater = null;

   private List<WordModel> mWords; // Cached copy of words

   public WordListAdapter(Context context) { mInflater = LayoutInflater.from(context); }

   @NonNull

   @Override

   public WordListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {

       View itemView = mInflater.inflate(R.layout.recyclerview_item, viewGroup, false);

       return new ViewHolder(itemView);

   }

   @Override

   public void onBindViewHolder(@NonNull WordListAdapter.ViewHolder viewHolder, int position) {

       if (mWords != null) {

           WordModel current = mWords.get(position);

           Log.d(“Main”,“::”+current.getWord());

           viewHolder.wordItemView.setText(current.getWord());

       } else {

           // Covers the case of data not being ready yet.

           viewHolder.wordItemView.setText(“No Word”);

       }

   }

   public void setWords(List<WordModel> words){

       mWords = words;

       notifyDataSetChanged();

   }

   // getItemCount() is called many times, and when it is first called,

   // mWords has not been updated (means initially, it’s null, and we can’t return null).

   @Override

   public int getItemCount() {

       if (mWords != null)

       {

           return mWords.size();

       }

       else return 0;

   }

   class ViewHolder extends RecyclerView.ViewHolder {

       private final TextView wordItemView;

       private ViewHolder(View itemView) {

           super(itemView);

           wordItemView = itemView.findViewById(R.id.textViewWord);

       }

   }

}

MAIN ACTIVITY

 

<?xml version=“1.0” encoding=“utf-8”?>

<RelativeLayout

   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”

   tools:context=“.MainActivity”>

   <android.support.v7.widget.RecyclerView

       android:id=“@+id/recyclerview”

       android:layout_width=“match_parent”

       android:layout_height=“wrap_content”

       android:layout_marginTop=“70dp”

       app:layoutManager=“android.support.v7.widget.LinearLayoutManager”

       />

   <android.support.design.widget.FloatingActionButton

       android:id=“@+id/fab”

       android:layout_width=“wrap_content”

       android:layout_height=“wrap_content”

       android:layout_gravity=“end|bottom”

       android:src=“@drawable/ic_add_icon”

       android:layout_alignParentBottom=“true”

       android:layout_alignParentRight=“true”

       android:layout_margin=“16dp” />

</RelativeLayout>

 

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

   private RecyclerView recyclerView;

   //all the activity’s interactions are with the WordViewModel only.

   private WordViewModel mWordViewModel;

   private WordListAdapter adapter;

   private FloatingActionButton mAddNewWord;

   //Retrofit’

   private ApiInterface apiInterface;

   private List<CityModel> mCityList;

   public static final int NEW_WORD_ACTIVITY_REQUEST_CODE = 1;

   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       setContentView(R.layout.activity_main);

       loadLocalView();

       loadRecyclerView();

       callToApi();

   }

   private void callToApi() {

       apiInterface = ApiClient.getClient().create(ApiInterface.class);

       Call<List<CityModel>> call = apiInterface.getAllCities();

       call.enqueue(new Callback<List<CityModel>>() {

           @Override

           public void onResponse(Call<List<CityModel>> call, Response<List<CityModel>> response) {

               Log.e(“Response”,“::”+response.body());

           }

           @Override

           public void onFailure(Call<List<CityModel>> call, Throwable t) {

           }

       });

   }

   private void loadLocalView() {

       recyclerView = findViewById(R.id.recyclerview);

       mAddNewWord = findViewById(R.id.fab);

       //Use ViewModelProviders to associate your ViewModel with your UI controller.

       // When your app first starts, the ViewModelProviders class creates the ViewModel.

       // When the activity is destroyed, for example through a configuration change, the ViewModel persists.

       // When the activity is re-created, the ViewModelProviders return the existing ViewModel

       mWordViewModel = ViewModelProviders.of(this).get(WordViewModel.class);

       //When the observed data changes while the activity is in the foreground, the onChanged() method is invoked and updates the data cached in the adapter.

       mWordViewModel.getAllWords().observe(this, new Observer<List<WordModel>>() {

           @Override

           public void onChanged(@Nullable List<WordModel> wordModels) {

               // Update the cached copy of the words in the adapter.

                   adapter.setWords(wordModels);

           }

       });

       mAddNewWord.setOnClickListener(this);

   }

   private void loadRecyclerView() {

       adapter = new WordListAdapter(this);

       recyclerView.setAdapter(adapter);

       recyclerView.setLayoutManager(new LinearLayoutManager(this));

   }

   @Override

   public void onClick(View v) {

       switch (v.getId())

       {

           case R.id.fab:

               Intent intent = new Intent(MainActivity.this, AddWordActivity.class);

               startActivityForResult(intent, NEW_WORD_ACTIVITY_REQUEST_CODE);

               break;

       }

   }

   public void onActivityResult(int requestCode, int resultCode, Intent data) {

       super.onActivityResult(requestCode, resultCode, data);

       if (requestCode == NEW_WORD_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {

           WordModel word = new WordModel(data.getStringExtra(AddWordActivity.EXTRA_REPLY));

           mWordViewModel.insert(word);

       } else {

           Toast.makeText(

                   getApplicationContext(),

                   R.string.empty_not_saved,

                   Toast.LENGTH_LONG).show();

       }

   }

}

________________________________________________________________________________________________________________

Outsource Android Developer: HIRE ANDROID DEVELOPER

Author

  • Sagar Nagda is the Founder and Owner of Nimap Infotech, a leading IT outsourcing and project management company specializing in web and mobile app development. With an MBA from Bocconi University, Italy, and a Digital Marketing specialization from UCLA, Sagar blends business acumen with digital expertise. He has organically scaled Nimap Infotech, serving 500+ clients with over 1200 projects delivered.

    View all posts

Accelerate Success, with Innovative Software Solutions.

By submitting this form, you agree to our Privacy Policy

Related articles