preface

Some time ago, I saw an interview question on Weibo, which required me to develop a simple gank. IO client within a certain period of time. Although I had no intention of seeking a job, I felt it was good for practice, so I tried it.

GitHub Repo: Unixzii/Android-Proficiency-Exercise Recommend reading this article!!

The screenshot of App operation is as follows:




Requirements:

  • You can call the API to get the data
  • Load images asynchronously and cache them
  • Pull down to refresh, pull up to load more
  • Data can be cached to a database for offline browsing

In fact, at first glance, it seemed to be a very simple small project. I wrote a version quickly within 5 hours, and launched PR to the original Repo. However, the code of this version only completed the function, that is, to achieve the interface and function required by the topic, but the performance and code were not optimal. Today, I spent nearly 4 hours for a reconstruction, and now it is a relatively perfect state.

Development mode

I’m an iOS developer, and I study Android just as a hobby, but app development is basically the same, the only difference is API and platform differences.

I adopted a very common MVP development mode for this app. The application is divided into three modules:

  • Main (Toolbar, TabLayout, Fragment Container), which coordinates the initialization of submodules and the loading of labels.
  • Entity (the content interface corresponding to the selected label in Main), which is used by Main to load display data.
  • The WebView, which I implemented very briefly, is mainly used to display articles.

For each module, I write a Contract Contract class that specifies the interaction between Presenter and View. The Contract class includes Presenter and View as two internal interfaces. This makes it very convenient for multiple people to develop modules independently of each other. Each part is developed without knowing the implementation of the other part, just by calling the methods in the interface.

View & Presenter

Entity module Contract class:

public interface EntityContract { interface View { int STATE_LOADING_IDLE = 0; int STATE_LOADING_REFRESHING = 1; int STATE_LOADING_RESERVING = 2; void setLoading(int state); void addEntities(List entities); void clearEntities(); void showNetworkError(); void runOnUiThread(Runnable runnable); } abstract class Presenter extends BasePresenter { abstract void setCategory(String categoryName); abstract String getCategory(); abstract void refresh(); abstract void reserve(); }}Copy the code

The functions provided by View and Presenter are obvious. In this case, BasePresenter is a generic abstract class that implements attaching and detaching and provides a convenient way to obtain a View.

Here I just use Fragment to realize the View, and RecyclerView is used for the list. In fact, the list control also belongs to a small MVC component, and Adapter is used as Model. So how can we refine the implementation of this part? The Model layer of the entire APP I write relatively simple, basically is the Bean class, can be directly handed to the View layer as a ViewModel, and the interface of the View layer is also very simple, in addition to some state interface, the rest is the interface to operate these ViewModels. We can either add ViewModels to the View or we can clear it out. For ViewModels that have been added to the View, the View is responsible for presenting the data without having to interact with the Presenter again.

Presenter is responsible for loading data, processing data, caching, etc. When we create Presenter we don’t have to worry about the Context, I create an Application singleton, Presenter doesn’t have to do any UI operations, Therefore, the Application Context can be used for database, disk, network, and so on. When creating a Presenter, we can configure the required Retrofit, OkHttpClient, database helper classes, etc. Of course, we can also use a singleton here and inject it with Dagger2.

When the Presenter is attached to the View, we load the cache, because the View must be empty, so we perform the load logic:




The Presenter cache is an array list that exists inside the Presenter class. It is a level 1 cache. After the Fragment destroyView, the Presenter class will not be destroyed. We can just pull out the cached data and display it to the View.

If there is no cache in the Presenter, it tries to read cached data from the database (as a secondary cache), and makes a network request if there is no cache in the database either.

Network & Cache

The network request aspect uses Retrofit + RxJava + Gson, and there are many articles on this topic that I don’t want to go into too much detail in this article. Let’s talk about caches. There are two main types of caches:

  • Database cache
  • The Response cache

Database caching is a bit trickier, but the benefits are obvious: data reads and writes are more flexible and controllable; The Response cache is to store the server’s Response (JSON, XML, BLOB, Protobuf, etc.) with Hash algorithm, which can be directly retrieved when the same API is requested next time. The advantage is that the implementation is simple, and the loading logic does not need to be differentiated, and the network request can be processed every time. But the disadvantage is not flexible enough, poor scalability.

I used database storage, which simply encapsulated the native database operation classes in Android, and then wrote a DAO to hide the SQL implementation details, because some dry data contains image arrays, so one table is definitely not enough, we need to build a second table to hold image URL data. Since we don’t manipulate the image URL data directly, the image table operations can be encapsulated in a DAO along with the dry table, making it much easier for Presenter to manipulate the database.

The final structure of the entire Gank. IO App is as follows, which is very simple and clear:




conclusion

The whole application development down also encountered a lot of small problems, although the application requirements are very simple, but can skillfully develop such a small application in a limited time is not a very simple thing, it requires us to stand in a higher level to design the entire application architecture, business logic and so on. I spent a lot of time working with apis and detailed features, but occasionally I had to do a little piece of software like this, and each time IT was different.