Before we begin, I assume you have read my previous article “Architecting Android…The clean way?” If you haven’t read it, you should take this opportunity to read it to better understand this article:
Evolution implies a gradual process of change from some state to a different state, where the new state is usually better or more complex. With that said, software evolves and changes over time, architecturally. In fact, good software design must help us evolve and scale solutions, keeping them robust, without having to rewrite everything (although in some cases rewriting is better, but that's a topic for another article, so trust me, let's focus on the topic discussed above). In this article, I will go through the points that I think are necessary and important. To keep the basic code clear, keep the following picture in mind. Let’s get started!
I am not going to discuss the benefits of RxJava here (I assume you already have some experience with it) as there are already many articles on this topic and there are great and impressive people doing this. However, I will point out the interesting aspects of it in terms of Android application development and how it helped me to form a clear architectural approach. First, I chose a reactive pattern by converting the usecase (called interactor in this clear architectural naming convention) to return Observables<T>, indicating that all underlying layers follow this chain and also return Observables<T>. As you can see, all use cases inherit from this abstract class and implement the abstract method buildUseCaseObservable(). This method will build an Observables<T>, which does the heavy lifting and returns the required data. It is important to emphasize that in the execute() method, we must ensure that Observables<T> is executed in a separate thread, so as to minimize the degree of blocking the Android main thread. As a result, the main thread will be pushed back to the end of the thread queue by the Android main thread scheduler. So far, we have our Observables<T> up and running. However, as you know, the sequence of data emitted must be observed. To do this, I improved presenters (part of the presentation layer of the MVP pattern) and turned them into observers (Subscribers), which "react" to the emitted items through use cases in order to update the user interface. The observer is this: Each observer is an inner class of each presenter and implements a Defaultsubscriber<T> interface, which creates basic default error handling. Putting all the pieces together, you can get the full concept from the following diagram: Let’s list some benefits of moving away from an RxJava based approach: Decoupling between Observers and Observables: Easier to maintain and test. Simplify asynchronous tasks: If multiple asynchronous executions are required, if more than one level of asynchronous execution is required, the operation and synchronization of Java's thread and future are more complicated, so by using a scheduler, we can easily (without extra work) jump between the background and the main thread, especially when we need to update the UI. It can also avoid the "callback pit" - it makes our code less readable and difficult to follow. From my perspective there is a downside, and even a price to pay, in that developers who are not yet familiar with the concepts still have to follow a learning curve. But you get something extremely valuable out of it. Be reactive for success! I don’t want to say too much about dependency injection since I’ve already written a whole article about it. I highly recommend you to read it so that we can continue with the following content. It is worth mentioning that by implementing a dependency injection framework like Dagger 2 we can gain: Component reuse, because dependent objects can be injected and configured externally. Lambda Expressions: Retrolambda No one will complain about using Java 8 lambda expressions in your code, and even more so after simplifying it and getting rid of a lot of boilerplate code, as you can see in this code: However, I have mixed feelings about this, why? We were discussing Retrolambada at @SoundCloud, mainly whether to use it, and the result was: 1. Reasons for support: Lambda expressions and method references 2. Reasons for objection: Unexpected use of Java 8 APIs Very offensive third-party libraries Third-party plugins to be used with Android Gradle Finally, we decided that it doesn't solve any problems for us: your code looks nice and readable, but it's not something we want to live with, and since all the most powerful IDEs now include code folding options, this need is covered, at least in an acceptable way. To be honest, although I might use it in a project in my spare time, the main reason for using it here is to try and experience Lambda expressions in Android. It is up to you to use it or not. I am just showing my vision here. Of course, the author of this library deserves my praise for such an amazing work. In terms of testing, not much has changed since the first version of the sample: Presentation layer: Test the UI with Espresso 2 and Android Instrumentation testing framework. Luckily, this is all part of the past, and now everything is available out of the box, so I can move them back into the data module, specifically for its default test path: src/test/java. I think one of the key factors of a good architecture is code/package organization: the first thing a programmer encounters browsing source code is the package structure. Everything flows from it, everything depends on it. We can identify two paths for packaging applications into packages: Package by layer: The items contained in each package are usually not closely related to each other. This leads to low cohesion and modularity of the packages, and high coupling between packages. Therefore, editing a feature requires editing files from different packages. In addition, it is almost impossible to delete a feature in a single operation. My suggestion is to remove the feature-based packaging, which will bring the following main benefits: More modularity. Easier code navigation. Minimized scope of features. It can also be really fun to work with feature teams (like we do at @SoundCloud). Code ownership is easier to organize and modularize. This is a win in growing organizations where many developers share a codebase. As you can see, my approach looks like packaging by layers: I could make mistakes here (for example, organizing everything under "users"), but I'll forgive myself in this case, because this is an example for learning purposes, and I want to show the main concept of a clear architectural approach. Get the idea, don't copy blindly :-). As we all know, a house is built from the foundation. The same is true for software development, and I would say that from my point of view, the build system (and its organization) is an important part of software architecture. On the Android platform, we use Gradle, which is actually a platform-agnostic build system with very powerful features. The idea here is to simplify the organization of your application building through some tips and tricks. Grouping stuff by functionality in separate gradle build files Therefore, you can insert it into any Gradle build file with "apply from: 'buildsystem/ci.gradle'" to configure it. Don't put everything in one build.gradle file, otherwise you will create a monster, this is the lesson. Creating a dependency graph This is great if you want to reuse the same component version between different modules of your project; otherwise, you have to use different versions of component dependencies between different modules. Another point is that you control dependencies in the same place, and things like component version conflicts can be seen at a glance. Having said so much so far, in one sentence, remember that there is no panacea. But a good software architecture will help the code remain clear and robust, and also keep the code scalable and easy to maintain. I want to point out a few things. When faced with software problems, treat them as if they should be solved: Follow SOLID principles <br /> Don’t overthink (don’t over-engineer) |
<<: Microsoft releases Android desktop app
>>: The correct way to use the prompt box in iOS9
Whether you are at work or at home recently In fa...
James Webb Space Telescope Observes Jet Stream St...
[[384879]] According to the Legal Daily on March ...
As the saying goes, food is the most important th...
For quite a long time, consumers' choices for...
What is the core competitiveness of event plannin...
introduction: When I was setting the topic, I wan...
Compared with the ground, the microgravity enviro...
© The Independent Leviathan Press: The toilets we...
Image source: Pixel Addict - CC BY 2.0 A mysterio...
I have said before that we should consider the lo...
Recently, the Chinese Academy of Sciences announc...
Many people write brilliant copy and make beautif...
Goose eggs, as a nutritious egg, rarely appear on...