Table of Contents
ToggleLive 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
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