📜 ⬆️ ⬇️

Android Architecture Components. Part 1. Introduction

image

At Google I / O 2017, a set of libraries called the Android Architecture Components was introduced. In a few words, this is a series of supporting libraries that are designed to help with such things as designing, testing, and maintaining applications. The fact that the Android development team has begun to focus on architecture cannot but rejoice, since the problem is really urgent. After all, initially no requirements or design guidelines were provided, and the developer had to build on his previous experience. That, in turn, caused difficulties in supporting 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 at the next Google I / O we will be able to see a full-fledged framework.

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

Component Lifecycle - designed to simplify work with the life cycle. Key concepts such as LifecycleOwner and LifecycleObserver are highlighted.
')
LifecycleOwner is an interface with one getLifecycle () method that returns a lifecycle state. It is an abstraction of the owner of the life cycle (Activity, Fragment). For simplicity, the LifecycleActivity and LifecycleFragment classes have been added.

LifecycleObserver - interface, denotes the listener of the owner's life cycle. It has no methods, 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. Quite a common situation when a number of processes work in our application, which depend on the stage of the life cycle. Whether it is media playback, location, communication with the service, etc. As a result, we have to manually monitor the status and notify our process about it. What is inconvenient for two reasons is cluttering up the main class (Activity or Fragment) and reducing modularity, because we need to take care of the state transfer support. With the help of this component, we can transfer all responsibility to our component and all that is needed for this is to declare our class of interest as an observer and pass to it in the onCreate () method a reference to the owner. 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 condition and can independently handle 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 it to abstract.

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

onActive () - this method is called when our instance has an active observator. In it, we must initiate the service or operation of interest to us.

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

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

 class ChatLiveDataHolder extends LiveData<Message>{ 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, we implemented our class as Singleton. This gives us the opportunity to use LiveData in other Activities, Fragment, etc. without reinitialization, if we do not need it.

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

What does this give us?

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

Component ViewModel - 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 its storage between such operations as re-creating an Activity. If we need to save a small data set, such as an item in RadioButtonGroup or the data entered, the Bundle in onSaveInstanceState () is great for us. But if this is a big list for example: users, products, a catalog of something, we had to get this list again. In this case, the ViewModel is our main helper. A feature of this component is that it binds to an Activity and automatically saves its state during operations such as onCofigurationChange ().

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

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

And that's all, our holder for userList is ready. In order to use our holder it is necessary in the onCreate (..) method to activate an instance of our model:

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

With the help of ViewModelProviders, we take an instance of our model. A c using if constructions, see if we already have data in our model or not yet.

What does this give us?

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

Component Room Persistence - Offers a level of abstraction over SQLite, offering a simpler and more advanced way to manage.

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

Entity - an object representation of the table. Using annotations you can easily and without unnecessary code to describe our fields.

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

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 designate a key. @ColumnInfo - for linking fields in the table. Creating get and set methods is not necessary, but then you need to provide access to variables using the public modifier.

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

DAO - Interface that describes the methods of access to the database.

To implement, 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 ends Exception. If you are still confident in your actions, you can use the method allowMainThreadQueries (). For asynchronous access, it is recommended to use LiveData or RxJava.

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

To create our class, we need to inherit from RoomDatabase and use the Database annotation, with which we pass parameters such as the used entity and the base version. In the body we describe abstract methods of access to our DAO.

 @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 db storage it is recommended to use singleton.

What does this give us?

Simplification of work with the database, there is no need to use third-party ORM.
In addition to the above described NestedObjects, parameters in queries, collections of arguments, TypeConverter, database migration, etc. These topics are not included in the scopes of this material and will be discussed later.

By 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 the ViewModel, and the ViewModel works directly with the Repository. For clarity, I will translate a picture from developer.android.com.

image

It looks absolutely simple, but in fact we will look at the following articles.
Where at first we will examine each component in more detail and at the end we will 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

Source: https://habr.com/ru/post/332562/


All Articles