Have you heard about God Activity or God Fragment in the world of Android Development ?
This is basically a single class which is used to do all the work itself, instead of delegating work to other classes. Mostly new developers, while in their early career in Android app development, write almost all logical code inside one Activity or Fragment which results in a massive code which is hard to maintain and update. Recently in Google IO, Android team introduced architecture components that help Android developers to swiftly develop their applications with good architecture base. Android team introduced the following four concepts:
- Handling Lifecycle
- LiveData
- ViewModel
- Room Persistence Library
All of these classes can be used together or separately. We will go through all of them one by one. First, we need to add the dependencies of these components. Please note that Architecture Components are still in alpha and changes are expected in the future releases.
Handling Lifecycle
We usually write some classes that really depend on Activity life cycle and we have to add a manual function call from Activity. With the help of this new API, we can easily do this job with fewer lines of code.
Lifecycle is a class that holds the information of life-cycle of components like Activity or Fragment and provide the event and status enumerations.
Let’s add the required dependencies into gradle.
Add the Google Maven repository
Open your project build file and add the following reference:
1 2 3 4 5 6 |
allprojects { repositories { jcenter() maven { url 'https://maven.google.com' } } } |
Add Architecture Components
Open your app module build file and add the following reference:
1 2 3 4 5 6 |
compile "android.arch.lifecycle:runtime:1.0.0-alpha3" compile "android.arch.lifecycle:extensions:1.0.0-alpha3" annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha3" //for room compile "android.arch.persistence.room:runtime:1.0.0-alpha3" annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha3" |
After adding these dependencies you are now ready to use them. Let’s consider this tiny example to understand the box.
Suppose we need some location manager class that needs activity life-cycle awareness, we just need to implement LifecycleObserver and add annotation on method that we want to call or notify when life-cycle event happens i.e onResume(), onPause().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class LocationUpdateListener implements LifecycleObserver { private static final String TAG = "LocationUpdateListener"; LocationUpdateListener(Context context){ //any thing need to initialize } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) void onResume(){ Log.v(TAG,"onResume"); } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) void onPause(){ Log.v(TAG,"onPause"); } } |
In your Activity class, you have to extend LifecycleActivity class which may be replaced in future releases and add Lifecycle classes in support AppCompatActivity and Fragment.
1 2 3 4 5 6 7 8 9 |
public class MainActivity extends LifecycleActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //This line of code make magic getLifecycle().addObserver( new LocationUpdateListener(this)); } } |
The getLifecycle() returns the life cycle of provider i.e MainActivity in this example and add your class that need to get life-cycle event and state by using addObserver() method. After running the application you will see the log of LocationUpdateListener class when you resume and pause your Activity.
Now our LocationUpdateListener class is aware of Activity life-cycle.
Live Data
Live Data is a data holder class that always keeps its values updated and allows them to be observed. For example, you are retrieving the list of movies from back-end API and you want to update it after every minute while the user is using the App and need to show updated listed to user.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class MoviesLiveData extends LiveData<List<Movies>> { MoviesLiveData(){ //Just for example purpose i am generating movie list from loop final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { List<Movies> moviesList = new ArrayList<Movies>(); for (int i = 0; i < 5; i++) { Movies movies = new Movies(); movies.setName("Movies name "+i); moviesList.add(movies); } setValue(moviesList); handler.postDelayed(this,60000); } },0); } } |
In the above code, I am generating movies list from loop and repeating this after one minute. In your Activity code, you just need to add the following code to observe these changes from MoviesLiveData class:
1 2 3 4 5 6 7 8 |
MoviesLiveData moviesLiveData = new MoviesLiveData(); moviesLiveData.observe(this, new Observer<List<Movies>>() { @Override public void onChanged(@Nullable List<Movies> movies) { Log.v("MainActivity","movies -> "+movies.toString()); //update your UI views from here. } }); |
The onChanged() will call whenever new changes occur in MoviesLiveData by using setValue() method.
ViewModel
As we know in Android, we use Activity and Fragment as UI controller and need to handle the configuration changes when user changes the device’s orientation or Activity restart by Android Framework. ViewModel is a class which is responsible to manage the logic of UI, update and maintain the data whenever the configuration changes occur. For example, you are fetching the list of Movies from back-end API and displaying inside your Activity. Whenever your screen orientation changes, all loaded data inside the Activity is deleted and you have to fetch it again. By using this new ViewModel class you don’t have to worry about these data losses. ViewModel retains your data and returns to your view as soon as view recreated. ViewModel class retains the memory as soon as LifeCycle scope exists.
Here is the example code for ViewModel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class MoviesViewModel extends ViewModel { private MutableLiveData<List<Movies>> moviesList; public LiveData<List<Movies>> getUsers() { if (moviesList == null) { moviesList = new MutableLiveData<>(); loadMoviesList(); } return moviesList; } private void loadMoviesList() { // suppose doing some async task to get movies list new Handler().postDelayed(new Runnable() { @Override public void run() { List<Movies> moviesItems = new ArrayList<>(); for (int i = 0; i < 5; i++) { Movies movies = new Movies(); movies.setName("Movies name " + i); moviesItems.add(movies); } // notify LifeCycleProvider i.e Activity or Fragment moviesList.setValue(moviesItems); } },3000); } } |
In your Activity class you just need to add the scope of LifeCycle provider.
1 2 3 4 5 6 7 |
MoviesViewModel viewModel = ViewModelProviders.of(this).get(MoviesViewModel.class); viewModel.getUsers().observe(this, new Observer<List<Movies>>() { @Override public void onChanged(@Nullable List<Movies> movies) { //update UI } }); |
By using ViewModel class, you can easily separate your view logic from Activity and Fragment class and prevent them to become God Activity or Fragment.
Android Room Persistence Library
Room is the abstraction layer over the SQLite API and provides the easy API to deal with database in Android Framework. Mostly, people ( like me 😛 ) use open source libraries like Ormlite, ActiveAndroid and GreenDao etc to handle SQLite Database in Android. The most common Android App use case is to cache the data retrieved from the REST API for later use when there is no network connection so that the user can browse through the app and sync new data whenever Internet connection becomes available. Room contains three basic components which are as follows:
- DataBase (DataBase component is used to hold the database information)
- Entities ( Entities component is used to hold database rows)
- DAO ( Data Active Object component is used to hold the method from which we can access the database or query data)
Entities
Any class which represents a database table. We simply add @Entity annotation on the top of class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@Entity public class Movie { @PrimaryKey @ColumnInfo(name = "id") private int id; @ColumnInfo(name = "name") private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Movie{" + "id=" + id + ", name='" + name + '\'' + '}'; } } |
Primary Key
Each Entity should have one primary key. You can add a Primary key by using @PrimaryKey annotation.
1 2 3 |
@PrimaryKey @ColumnInfo(name = "id") private int id; |
If you want to make composite primary key then you need to add primaryKeys in @Entity annotation on top of class.
1 2 3 4 5 |
@Entity(primaryKeys = {"id", "serialNumber"}) class Movie { public int id; public int serialNumber; } |
Relationship
Relationship is the beauty of relational database and the Room can take care of your Entity relationship with @ForeignKey annotation. Suppose we have Rating class that holds rating information of specific Movie.
1 2 3 4 5 6 7 |
@Entity(foreignKeys = @ForeignKey(entity = Movie.class, parentColumns = "id", childColumns = "movie_id")) class Rating { @PrimaryKey public int ratingId; public float rating; @ColumnInfo(name = "movies_id") public int id; } |
Nested Object
You can merge the members of two classes into one Entity by using @Embedded annotation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class ReleaseDate{ @ColumnInfo(name="releaseDay") private int releaseDay; @ColumnInfo(name="releaseMonth") private int releaseMonth; @ColumnInfo(name="releaseYear") private int releaseYear } @Entity public class Movie { @PrimaryKey @ColumnInfo(name = "id") private int id; @ColumnInfo(name = "name") private String name; @Embedded private ReleaseDate releaseDate; } |
The total fields of the Movie Entity will be id, name, releaseDay, releaseMonth and releaseYear.
Indices and uniqueness
You can add index on field to make your search fast and also add uniqueness constraint on fields.
1 2 3 4 5 6 7 8 9 |
@Entity(indices = {@Index(value = {"name"}, unique = true)}) public class Movie { @PrimaryKey @ColumnInfo(name = "id") private int id; @ColumnInfo(name = "name") private String name; } |
DAO (Data Active Object)
DAO is used to access your data from SQLite. We just need to add interface and annotation on the top and methods for insert, delete and update operations and their annotation on the method name.
1 2 3 4 5 6 7 8 9 10 11 |
@Dao public interface MovieDao { @Query("SELECT * FROM movie") List<Movie> getAllMovie(); @Insert void insertMovie(Movie movie); @Insert void insertMovie(List<Movie> movie); @Delete void delete(Movie movie); } |
Database
Database component is used to hold the database information and also the information of Entities and DAO objects.
1 2 3 4 |
@Database(entities = {Movie.class},version = 1) public abstract class ApplicationDataBase extends RoomDatabase { public abstract MovieDao getMovieDao(); } |
Use the ApplicationDatabase to get the different DAO reference to perform SQL operation.
1 2 3 4 5 6 7 8 9 10 11 |
//Create the database reference ApplicationDataBase db = Room.databaseBuilder(getApplicationContext(), ApplicationDataBase.class, "movies-database").build(); //now get the movies dao MovieDao dao = db.getMovieDao(); //call this method of dao on background thread dao.insertMovies(new Movie()); . . . dao.getAllMovies(); |
All methods of DAO should be called from the background thread. It is not allowed to call DAO method on main thread and if you do, Android Framework will through an exception.
Final Words:
With the help of these Android Architecture Components, you can develop efficient and well-structured Android Applications. You can easily split the code into small modules which can be tested and modified easily. By using Room library, you can easily integrate SQLite inside your Android application with few lines of code. It reduces a lot of code that saves developer time and helps them to focus on the logic.
Reference:
- https://developer.android.com/topic/libraries/architecture/index.html
- https://android-developers.googleblog.com/2017/05/android-and-architecture.html