Android Architecture Components. Part 1. Introduction

    image

    At Google I / O 2017, a set of libraries was introduced called Android Architecture Components. In a few words - this is a series of auxiliary libraries that are designed to help with such things as designing, testing and maintaining applications. The fact that the Android development team began to focus on architecture cannot but rejoice, because the problem is really relevant. After all, initially no design requirements or guidelines were provided, and the developer had to build on his previous experience. Which, in turn, caused difficulties in maintaining the project, as well as dubious solutions for OS-specific situations. In fact, these are not the first steps in this direction. Earlier, Google introduced the android-architecture repository with examples of the use of different architectural concepts. We hope that the development will be further and maybe on the next Google I / O we can see a full-fledged framework.

    In general, Android Architecture Components can be divided into four blocks: Lifecycles, LiveData, ViewModel and Room Persistence.

    Lifecycle component - designed to simplify the work with the life cycle. Key concepts such as LifecycleOwner and LifecycleObserver are highlighted.

    LifecycleOwner is an interface with one getLifecycle () method that returns the state of a life cycle. It is an abstraction of the owner of the life cycle (Activity, Fragment). For simplicity, the LifecycleActivity and LifecycleFragment classes have been added.

    LifecycleObserver - an interface that identifies the owner's lifecycle listener. It has no methods, it is tied to OnLifecycleEvent, which in turn allows you to track the life cycle.

    What does this give us?

    The purpose of this component is to save the developer from writing routine code and make it more readable. A quite common situation is when a number of processes work in our application that depend on the stage of the life cycle. Be it media playback, location, communication with the service, etc. As a result, we have to manually monitor the status and notify our process about it. Which is inconvenient for two reasons, cluttering up the main class (Activity or Fragment) and reducing modularity, because we need to take care of state transfer support. With the help of this component, we can shift all responsibility to our component and all that is needed is to declare our class of interest as observer and pass the owner link to it in the onCreate () method. In general, it looks like this:

    class MyActivity extends LifecycleActivity {
        private PlayerWrapper mPlayerWrapper;
        public void onCreate(…) {
           mPlayerWrapper = new PlayerWrapper (this, getLifecycle());
        }
    }
    

    class PlayerWrapper implements LifecycleObserver {
       private PlayerController mController;
       public PlayerWrapper(Context context, Lifecycle lifecycle) {
          //init controller
       }
       @OnLifecycleEvent(Lifecycle.Event.ON_START)
       void start() {
         mController.play();
       }
       @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
       void stop() {
          mController.stop();
       }
    }
    

    Our observer is absolutely aware of the state and can independently process its change.

    Component LiveData - is a holder class for storing an object, and also allows you to subscribe to it. LiveData knows about the life cycle and allows you to ignore it.

    To implement the component, we need to extend the LiveData class. To work, we need to know only three methods from this class.

    onActive () - this method is called when our instance has an active observer (s). In it, we must initiate the service or operation that interests us.

    onInactive () - called when LiveData has no active listeners. Accordingly, you need to stop our service or operation.

    setValue () - call if the data has changed and LiveData informs the listeners about it.

    class ChatLiveDataHolder extends LiveData{
       private static ChatLiveDataHolder sInstance;
       private ChatManager mChatManager;
       private ChatListener mChatListener = new ChatListener(){
          @Override
          public void newMessage(Message message){
             setValue(message);
          }
       }
       public static ChatLiveDataHolder get() {
          if (sInstance == null) {
             sInstance = new ChatLiveDataHolder();
          }
          return sInstance;
       }
       private ChatLiveDataHolder(){
          //init mChatManager and set listener
       }
       @Override
       protected void onActive() {
          mChatManager.start();
       }
       @Override
       protected void onInactive() {
          mChatManager.stop();
       }
    }
    

    If you noticed, then we implemented our class as Singleton. This gives us the opportunity to use LiveData in other Activity, Fragment, etc. without reinitialization, if we do not need it.

    In order to sign a listener, there are no problems either. All you need to do is call the observe method on our LiveData instance, pass LifeCycleOwner and the Observer interface implementation to it. The Observer interface has only one onChanged (T t) method, using it LiveData will inform listeners about a change in data (calling the setValue (T t) method in LiveData).

    What does this give us?

    There are really a lot of advantages, starting from protection against memory leaks (an Observer is connected to its Lifecycle and is automatically unsubscribed when its Lifecycle is destroyed), protection against a stopped Activity (if Lifecycle is inactive (stopped), notifications will not be sent to Observer) . With functional features, this is a single access to data (using singleton) and saving our data (for operations such as re-creating activity, fragment). In general, the purpose is the same, to save the developer from the routine work associated with the life cycle.

    ViewModel component - designed to store and manage data that is associated with the view.

    The task of this component is to help the developer abstract data and provide their storage between operations such as re-creating Activity. If we need to save a small data set, such as item in the RadioButtonGroup or the entered data, the Bundle in onSaveInstanceState () is great for us. But if this is a large list for example: users, products, a catalog of something, we had to re-get this list. In this case, ViewModel is our main assistant. A feature of this component is that it binds to Activity and automatically saves its state during operations such as onCofigurationChange ().

    The ViewModel class is an abstract class, but has no abstract methods. To implement our class, we only need to inherit from ViewModel and describe the data that we want to store and the methods for obtaining them.

    public class OurModel extends ViewModel {
        private List userList;
        public List  getUserList() {
            return userList;
        }
         public void setUserList(List list) {
            this.userList = list;
        }
    }
    

    And that’s all, our holder for userList is ready. In order to use our holder, you need to call instance of our model in the onCreate (..) method of activity:

    @Override
    protected void onCreate(Bundle savedInstanceState){
       // init UI etc.
       OurModel model = ViewModelProviders.of(this).get(OurModel.class);
       If (model.getUserList()==null){
          downloadData();
       } else {
          showData();
       }
    }
    

    Using ViewModelProviders, we take an instance of our model. And with the help of the if construct, we see if we already have data in our model or not yet.

    What does this give us?

    Like the previous components, this one helps us deal with the features and related problems of the Android life cycle. In this case, this is a separation of our data representation model from Activity and providing a secure mechanism for their storage. Also, when used in conjunction with LiveData, it is not a problem to implement asynchronous requests.

    Room Persistence Component - Offers an abstraction layer over SQLite, offering a simpler and more advanced way to manage it.

    In general, we got the default ORM, this component can be divided into three parts: Entity, DAO (Data Access Object), Database.

    Entity is an object representation of a table. Using annotations, we can easily and easily describe our fields.

    To create our Entity we need to create a POJO (Plain Old Java Object) class. Mark class with Entity annotation .

    Example:

    @Entity(tableName = «book»)
    class Book{
       @PrimaryKey
       private int id;
       @ColumnInfo(name = «title»)
       private  String title;
       @ColumnInfo(name = «author_id»)
       private int authorId;
       …
       //Get and set for fields
    }
    

    @PrimaryKey - To indicate a key. @ColumnInfo - for linking a field in a table. Creating get and set methods is not necessary, but then you need to provide access to the variables using the public modifier.

    The establishment of relationships is also declared in the body of the Entity annotation : Entity (foreignKeys = @ForeignKey (entity = Other.class, parentColumns = "id", childColumns = "author_id"))

    DAO - An interface that describes methods for accessing the database.

    For implementation, we create an interface that we mark with the DAO annotation and declare our methods. The main annotations for the methods are Insert Update Delete Query , they do not need comments.

    Example:

    @Dao
    public interface OurDao {
       @Insert
       public void insertBook(Book book);
       @Update
       public void updateBook(Book book);
       @Delete
       public void deleteBook(Book book);
    @Query(«SELECT * FROM book»)
       public Book[] loadAllBooks();
    }
    

    In the normal state, an attempt to access the database from the main thread will end with an Exception. If you are still confident in your actions, then you can use the allowMainThreadQueries () method. For asynchronous access, it is recommended to use LiveData or RxJava.

    Database - used to create the Database Holder and is an access point to connecting to the database.

    To create our class, you need to inherit from RoomDatabase and use the Database annotation , to which we pass parameters such as the entity used and the version of the database. In the body, we describe abstract methods for accessing our DAOs.

    @Database(entities = {User.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
       public abstract OurDao ourDao();
    }
    

    To create an instance for our database, use the code:

    AppDatabase db = Room.databaseBuilder(getApplicationContext(),  AppDatabase.class, «database-name»).build();
    

    For storage of db recommend using singleton.

    What does this give us?

    Simplification of work with the database, there is no need to use third-party ORMs.
    In addition to the NestedObject buns described above, parameters in queries, argument collections, TypeConverter, database migration, etc. These topics are not included in the scope of this material and will be considered later.

    To the end of the material I want to add a few more words about architecture. With the introduction of Android Architecture Components, a solution architecture was proposed, which for most developers is already familiar in one form or another. The bottom line is that we divide the architecture into 3 main layers: View (Activity / Fragment), which communicates with ViewModel, and ViewModel works directly with Repository. For clarity, I’ll give a picture from developer.android.com.

    image

    It looks absolutely simple, but as in fact we will consider in the following articles.
    Where we first analyze each component in more detail and at the end we implement the application, as they say with the database and ... connection to the service.

    Android Architecture Components. Part 1. Introduction
    Android Architecture Components. Part 2. Lifecycle
    Android Architecture Components. Part 3. LiveData
    Android Architecture Components. Part 4. ViewModel

    Also popular now: