This one simple change for better class naming will blow your mind

英文链接:https://www.novoda.com/blog/better-class-naming/

Single Responsibility1, Beware the Share2, Boyscout Rule3. These are some conventions that guide good practice in software development and I believe naming can benefit from these ideas and other practices. This blog post will discuss class naming using Model View Presenter as the example and show you one change you can do to make your code cleaner and more readable.

MVP4 is being used as one example of a design pattern that requires thoughtful naming, this article doesn’t give you direction for what your Model, View or Presenter should do as behaviour, but we can all agree that you usually have an interface for each of these ideas. Infact the main benefits of this article can be taken to naming any grouping of classes and you can reinterpret these ideas for other class naming.

Many people try to come up with a great name all at once. This is hard and rarely works well. The problem is that naming is design: it is picking the correct place for each thing and creating the right abstraction.

This quote comes from an amazing eight part blog post5 that talks about how naming can go through seven stages. As a TLDR; The stages include Missing name, Nonsense naming, Honest naming, Honest and Complete, Does the Right Thing, Intent, Domain Abstracted naming. I believe many people change variable names and go through the stages as discussed maybe not hitting every stage and maybe not getting to the end, but still, variables are renamed often. If these names move towards the right side of this scale, this is movement towards more expressive and readable code.

I want to skip variable naming and move straight on to class naming. Variables get renamed an order of magnitude more often than Classes get renamed. There seems to be a bigger barrier to class renaming, in that it will likely touch more code, affect more people, ruffle more feathers, and so it is done less often. Let’s drop this as an excuse not to strive for clean code and accept class naming as fair game.

Consider a class that implements the interface TweetsView, one bad way of naming this implementer is like this TweetsViewImpl implements TweetsView. Impl?? Impl!! Naming an implementation is an important opportunity to convey some meaning about what that specific implementation does. It’s lazy to just go with Impl and not think about what your class does.

My interface name describes the behaviour of my class. I can’t think of anything more to add to that, I’ll just use Impl.

Next logical step people usually take is, they know Impl is bad naming, but still can’t think of a name because the interface TweetsView seems exactly the right name; its a view of the tweets. They avoid the Impl code smell with something just as bad: DefaultTweetsView, SimpleTweetsView or flip it round TweetsView implements ITweetsView (this just moves the problem rather than resolves it, but a good hint of what is to come 🙂 ).

You want your classes to be named after the behaviour they exhibit, following the Single Responsibility Principle you should know what this responsibility/behaviour is.

There are two solutions here:

  • Only 1 implementer of the interface? Get rid of the interface! Over abstraction is unnecessary and confusing.
  • Understand the problem space and name explicitly according to behaviour TrendingTweetsView implements TweetsView

Now thinking about class naming and interfaces from another perspective. If we are using MVP and have an interface for TweetsModel, TweetsView, TweetsPresenter. Then these three interfaces are actually intertwined and should be considering as a system. For example, someone new to the code base, seeing a class implementing TweetsView could think this is just some type of custom view (not MVP), they could then see the other code implementing TweetsModel and still consider this as some other pattern, it’s not until they also notice implements TweetsPresenter that they’ll put the three together. It would be nice if we could make this grouping explicit in the code, lets see an example.

public interface TweetsMvp {

    interface Model {
      ...    
    }

    interface View {
      ...    
    }

    interface Presenter {
      ...    
    }
}

Awesome, we have declared our triad of interfaces under one common name. This fixes the problem of understanding the system. Any legacy developer reading this code or reading one of the implementations can see this interface comes from this triad and that it’s clear intention is to be used in the MVP pattern.

public class TweetsModel implements TweetsMvp.Model {  
  ...
}

public class TweetsView implements TweetsMvp.View {  
  ...    
}

public class TweetsPresenter implements TweetsMvp.Presenter {  
  ...    
}

When we implement the triad of interfaces it further backs up the knowledge that this class is being used in the MVP pattern. Further we have resolved our Impl naming issue. We can have our view of the tweets called TweetsView and this implements an MVP View of Tweets; implements TweetsMvp.View.

This wouldn’t be possible if you just called the individual interfaces Model, View and Presenter. Firstly you usually have multiple MVP interfaces in a codebase and so such generic naming would get very confusing very quickly. Secondly on Android View is already used for the UI system and so one of the two View classes/interfaces would have to be fully qualified which adds noise to the code.

I hope you find the grouping concept useful even if my poor textual explanation doesn’t get you, then the code example should make it obvious.

Always remember to consider your systems as a whole, what parts of the system are interconnected and should these relationships be made explicit. Naming plays an important role in your codebase and getting it right is hard. If ever stuck, think where you are up to on the seven stages of naming; missing name, nonsense naming, honest naming, honest and complete, does the right thing, intent, domain abstracted and what other entities changing this name might effect.

Architecting Modern Mobile Applications

英文链接:https://medium.com/@laanayabdrzak/architecting-modern-mobile-applications-bf896120f0c2#.v4llfasfc

Hey there! After my post on Android Development : Some of the best practices, I decided to give you taste of what I consider a good approach when it comes to architecting modern mobile applications (android in this case).

The first resource I would like to present is a superb blogpost by uncle bob

Good Architecture is :

– Independent of UI

– Independent of Frameworks

– Independent of any agency externe

– Independent of Database

– Testable

Why to invest into architecture ?

I would like to start by talking why I think that apps architecture is an important topic to discuss and why it is worth to invest time and resources into building one. It is sometimes pretty easy for team to start together working on an application, but this approach cannot scale, It does not work well, so let’s face it.

Integrating Model View Presenter

In the past few years, several architectural patterns such as MVP orMVVM have been gaining popularity within the Android community. There are more and more people talking about it.

So why use MVP?

In Android we have a problem arising from the fact that Android activities are closely coupled to both interface and data access mechanisms. The main issue with this approach is that the View layer had too manyresponsibilities, so it becomes impossible to maintain and extend. Ideally the MVP pattern would achieve the same logic that might have been completely different and interchangeable views. MVP makes views independent from the data source, it divide the application into at least three different layers, which let us test them independently.

– The model is the data that will be displayed in the view (user interface).
– The view is an interface that displays data (the model) and routes user commands (events) to the Presenter to act upon that data
– The Presenter is the middle-man and has references to both, view and model.

ReactiveX approach: RxJava/ RxAndroid

– Developing an Android app that has lots of network connections, user interactions, and animations often means writing code that is full of nested callbacks, wich I called callback hell. ReactiveX offers an alternative approach that is both clear and concise, to manage asynchronous tasks and events.

RxJava is a JVM implementation of Reactive Extensions, developed by NetFlix, and is very popular among Java developers.

RxJava is the new hotness amongst Android developers these days. I know it can be difficult to approach initially when you come from an imperative world. But believe me once you get your hands dirty you gonna love the way that RxJava can remove the need for having nested callbacks, it’s so awesome!

Dependency Injection approach: Dagger 2

Generally in Clean Architecture, code is separated into layers in an onion shape with one dependency rule: The inner layers should not know anything about the outer layers. Meaning that the dependencies should point inwards. at this point Dagger 2 came with this rules:

– Components reuse, since dependencies can be injected andconfigured externally.

– When injecting abstractions as collaborators, we can just change the implementation of any object without having to make a lot of changes in our codebase, since that object instantiation resides in one place isolated and decoupled.

Dependencies can be injected into a component: it is possible toinject mock implementations of these dependencies which makes testingeasier.

At least the advantages for using Dagger 2:

Simplifies access to shared instances. Just as the ButterKnife library

Easy configuration of complex dependencies

Easier unit and integration testing

Scoped instances Not only can you easily manage instances that can last the entire application lifecycle

Lambda expressions approach: Retrolambda

Is a Java library for using Lambda expression syntax in Android and other pre-JDK8 platforms. It helps keep your code tight and readable especially if you use a functional style with for example with RxJava.

Package organization approach: Package By Feature not Layer

The first question in building an application is How do I divide it up into packages?. For typical business applications, there seems to be two ways of answering this question.

Package By Feature — In package-by-feature, the package names correspond to important, high-level aspects of the problem domain. Each package usually contains only the items related to that particular feature, and no other feature. For example:

abderrazak.com.recycleviewcardview
├─ data
│   ├─ local
│   ├─ model
│   └─ remote
├─ injection
│   ├─ component
│   └─ module
├─ ui
│   ├─ main
│   ├─ detail
│   └─ etc..
├─ util
└─ views
    ├─ adapters
    └─ widgets

Gains and Benefits

By looking at the structure you can already tell what the app is all about (figure above)

Higher modularity

Easier code navigation

Higher level of abstraction

Separates both features and layers

– More readable and maintainable structure

– More cohesion

– Much easier to scale

Less chance to accidentally modify unrelated classes or files

– Much easier to add or remove application features

– And much more reusable modules

Package By Layer — The competing package-by-layer style is different. In package-by-layer, the highest level packages reflect the various applicationlayers, instead of features, as in:

abderrazak.com.recycleviewcardview
   ├
   ├─ model
   ├─ activities
   ├─ services
   ├─ fragments
   ├─ util
   └─ etc..

Here, each feature has its implementation spread out over multiple directories, over what might be loosely called implementation categories. Each directory contains items that usually aren’t closely related to each other. This results in packages with low cohesion and low modularity, with high coupling between packages. As a result, editing a feature involves editing files across different directories. In addition, deleting a feature can almost never be performed in a single operation.

Organization build logic approach: Gradle

On Android, we use gradle, which is the official build tool system for android development. The idea here is to go through a bunch of advantage of this great build automation system

– Build different flavours or variants of your app

– Make simple script-like tasks

– Manage and download dependencies

– Customize keystores

– Android Project Structure

– More..

Testing approach: Espresso/ JUnit/ Mockito/ Robolectric

Testing your app is an integral part of the app development process. Testing allows you to verify the correctness, functional behavior, and usability of your app before it is released publicly.

Presentation layer: UI tests with Espresso 2 and Android Instrumentation.

Domain layer: JUnit + Mockito since it is a regular Java module.

Data layer: Migrated test battery to use Robolectric 3 + JUnit + Mockito. Tests for this layer used to live in a separate Android Module, since back then (at the moment of the first version of the example), there was no built-in unit test support and setting up a framework like robolectric was complicated and required a serie of hacks to make it work properly.


Fortunately, app architecture is a really hot topic lately, and there are more than enough articles and blog posts describing different approaches for building different architectures in Android from classical concepts such asMVP, MVVM, MVC to some fancy new approaches like the Square way with Mortar and Flow.

Hopefully that this article gonna helps you to achieve a better architecture for your future apps. Stay tuned for more posts, and don’t forget to share it if you liked it.

M — Model in MVC, MVP, MVVC in Android

英文链接:https://medium.com/@artem_zin/m-model-from-mvc-mvp-in-android-flow-and-mortar-bd1e50c45395#.e4g779otj

Android Fragments in trouble last year, more and more developers talking about their problems and guys from Square (as always) have a solution — Flow and Mortar.

Today I found my old comment to “Advocating Against Android Fragments

https://twitter.com/artem_zin/status/570191985623085056

I found it because I’ve read post from Android Weekly: “An Investigation into Flow and Mortar”.

I won’t talk about Flow and Mortar (just read manuals, posts and try them). I want to notice one LITTLE THING.

Here is the picture from that post noticed in Android Weekly:

http://www.bignerdranch.com/blog/an-investigation-into-flow-and-mortar/

Unfortunately, a lot of developers really see Model layer like this.

Model is not JSON or XML or SQL (which is query language btw).

JSON, XML, etc is just data format, not Model. It is one of the representations of your entities.

Model is a layer, class, object, that manipulates these entities.

Q: What can we see in many apps?

A: Spaghetti code, when all “business logic” is placed into Activities and/or Fragments or now in Flow & Mortar’s Presenters with 300+ lines of code.

With a popular solutions like RxJava it will be reactive spaghetti code! (Actually, it’s better than callbackHelled spaghetti code).

What I want to say? Please, create Models.

Step by step guide:

For example: you need to get list of users from your API.

When you’ll see A/F/P — it’s Activity/Fragment/Presenter.

  1. Create some user entity representation, class or interface (in Java) — User
  2. Write (or use autogenerated) parser from required data format, such asJSON or XML, etc
  3. Write a call to API in your A/F/P (Activity/Fragment/Presenter)
  4. Now delete call to API from your A/F/P
  5. Think about problem in step 3

If you usually write things like API calls, direct Database / ContentProvider / SharedPreferences access in your A/F/P, please stop doing it.

There are many reasons why writing such code in A/F/P is bad practice:

  1. It’s hard to test, because you have to instantiate A/F/P instance and emulate it’s state to just execute some piece of useful code. Of course nobody (98%) of Android Developers writes tests…
  2. Sooner or later you’ll break one of the most important rules: “Don’t repeat yourself”, because later you will need to do same thing in another place of application. I guarantee this.
  3. When you will need to switch to another REST framework (Retrofit?), HTTP client (OkHttp?), Database (Realm?), Parser (Gson?) or another popular thing you’ll have to change a lot of A/F/P classes and then test them, test them again and again (now you’ll think about unit testing).
  4. Reading and, especially, changing classes with more than 300 lines of code is hard and consumes a lot of time which can be spent on more useful things like life. Nobody likes spaghetti code, but a lot of us write it.

Unfortunately, even guys from Google write apps in such bad manner, just see code samples on d.android.com or apps from AOSP or Google I/O app sources.

Model layer is solution

Better variant of getting list of users from API:

  1. Create some user entity representation, class or interface (in Java) — User
  2. Write (or use autogenerated) parser from required data format, such asJSON or XML, etc
  3. Create Model class, for example UserModel
  4. Create a method in UserModel, like “getUsers()” that performs API call and parsing and returns a result, I suggest Observable from RxJava because it’s very flexible and nice, but you can use callbacks, or other mechanisms you want.
  5. Now just create an instance of UserModel in your A/F/P and ask it for a list of users!
  6. Have a nice day ☺

Why it’s better than doing same thing in A/F/P?

  1. You can easily add/change behavior: add caching layer (for example via Database), make additional data transformation (when API changed, etc) or any other additional behavior in one place
  2. You can easily use same Model from many places in the application without repeating yourself
  3. Your A/F/P will be very small, just about 150–200 lines of code on the average
  4. You can create unit tests for Model
  5. You can use Dependency Injection for injecting Models into your A/F/Ps and make architecture even better and cleaner
  6. Clean code

That’s all, hope your life will become better.

P.S. this post can be applied to any kind of development, such as client-side: iOS, Windows Phone or Backend and others. But I wanted to talk about Android Development, because it’s a real problem in our world.

You can follow me in Twitter: @artem_zin


Btw, you can take a look at StorIO — Modern API for SQLiteDatabase and ContentProvider with RxJava support and convenient builders

https://github.com/pushtorefresh/storio

We are working on release 1.0.0 and we will be glad to hear some feedback before release.

Approaching Android with MVVM

英文地址:https://labs.ribot.co.uk/approaching-android-with-mvvm-8ceec02d5442#.tqhiallpe

he Data Binding library for android is something that I’ve been keen to check out for a short while. I decided to experiment with it using the Model-View-ViewModel architectural approach. To do this, I took a HackerNews Reader app that @matto1990 and I worked on together, re-implementing what I could using this approach.

This article references a sample app to demonstrate the implementation of using an MVVM approach. To help with your understanding, I suggest you take a look at the repository for this sample app here.


What is MVVM?

Model-View-ViewModel is an architecural approach used to abstract the state and behaviour of a view, which allows us to separate the development of the UI from the business logic. This is accomplished by the introduction of a ViewModel, whos responsibility is to expose the data objects of a model and handle any of the applications logic involved in the display of a view.

This approach (MVVM) is made up of three core components, each with it’s own distinct and separate role:

  • Model – Data model containing business and validation logic
  • View – Defines the structure, layout and appearance of a view on screen
  • ViewModel – Acts a link between the View and Model, dealing with any view logic

So how does this differ from the MVC approach that we’re used to? The architecture for MVC is as follows:

  • The View sits at the top of the architure with the Controller below it, followed by the Model
  • The Controller is aware of both the View and Model
  • The View is aware of just the Model and is notified whenever there are changes to it

In MVVM the architecture is similar, but there are a few distinct differences:

  • The Controller is replaced by a View Model, which sits below the UI layer
  • This View Model exposes the data and command objects that the Viewrequires
  • The View Model receives its data from the Model

You can see here that the two approaches use a similar architecture, with the addition of a View Model and the way that it introduces a different approach to the communication between components. The architecture introduces two-way communication between its components, whereas MVC is only capable of one-way communication.

In a nutshell, MVVM is a progression of the MVC architecture – using an additonal layer of non-visual components on top of the Model (but below the View) to map data closer to the View components in the architecture. We’ll take more of a look at the nature of MVVM over the next few sections.

The Hacker News reader

As previously mentioned, I took an old project of mine and stripped it back for use with this article. The features of this sample application consist of:

  • Retrieval of Posts
  • Viewing a single Post
  • Viewing comments for a Post
  • Viewing a selected authors Posts

This was done in the hope that it would reduce the codebase, hence making it a little easier to follow and understand how the implementation operates. The screens of the app that we’re working with are as shown below:

MVVM has been used to implement the two screens shown here

The main part of the application that I’m going to be looking at is the listing of Posts, shown on the left. The comments screen works in pretty much the same way, with a few slight differences (which we’ll look at later).

Displaying Posts

A Post is an item within a RecyclerView, displayed within a card.

Each Post instance is displayed in a recycler view within a card view, as shown on the left.

Using MVVM we will be able to abstract the different layers that make up this card, meaning that each MVVM component will only be dealing with its assigned responsibility. Using these different components introduced with MVVM, working together they are able to construct the Post card instance. So how can we break this up?

How some parts of the Post card are built in the sample app, using MVVM

Model

Quite simply put, the Model consists of the business logic belonging to a Post. This includes different properties such as the id, name, text etc. The code below shows a reduced version of this class:

public class Post {

    public Long id;
    public String by;
    public Long time;
    public ArrayList<Long> kids;
    public String url;
    public Long score;
    public String title;
    public String text;
    @SerializedName("type")
    public PostType postType;

    public enum PostType {
        @SerializedName("story")
        STORY("story"),
        @SerializedName("ask")
        ASK("ask"),
        @SerializedName("job")
        JOB("job");

        private String string;

        PostType(String string) {
            this.string = string;
        }

        public static PostType fromString(String string) {
            if (string != null) {
                for (PostType postType : PostType.values()) {
                    if (string.equalsIgnoreCase(postType.string)) return postType;
                }
            }
            return null;
        }
    }

    public Post() { }
    
}
The Post Model, stripped back of Parcelable and other methods for readability

Here you can see that all our Post Model contains is it’s properties, no other logic has been placed in this class – that’ll be dealt with by the other components.

View

Our View is responsible for defining the layout, appearance and structure of its components. The View itself will be (ideally) constructed completely of XML, however if any java code is used then it should not consist of any business logic. The View retrieves its data from a View Model through the use of binding. Then at run time, the UI content is set and can be updated when the View Model properties flag any change notification events.

To begin with, we created a custom adapter to use with our RecyclerView. For this, we needed to make a create a BindingHolder to keep a reference to our Binding.

public static class BindingHolder extends RecyclerView.ViewHolder {
    private ItemPostBinding binding;

    public BindingHolder(ItemPostBinding binding) {
        super(binding.cardView);
        this.binding = binding;
    }
}

The onBindViewHolder() method is where the actual binding of the ViewModel and View takes place. We create a new ItemPostBinding (generated from our item_post layout) and set the View Model to a new instance of our PostViewModel class.

ItemPostBinding postBinding = holder.binding;
postBinding.setViewModel(new PostViewModel(mContext,  
                             mPosts.get(position), mIsUserPosts));

Other than the standard adapter views, that’s pretty much it! The full PostAdapter class is displayed below:

public class PostAdapter extends RecyclerView.Adapter<PostAdapter.BindingHolder> {
    private List<Post> mPosts;
    private Context mContext;
    private boolean mIsUserPosts;

    public PostAdapter(Context context, boolean isUserPosts) {
        mContext = context;
        mIsUserPosts = isUserPosts;
        mPosts = new ArrayList<>();
    }

    @Override
    public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ItemPostBinding postBinding = DataBindingUtil.inflate(
                LayoutInflater.from(parent.getContext()),
                R.layout.item_post,
                parent,
                false);
        return new BindingHolder(postBinding);
    }

    @Override
    public void onBindViewHolder(BindingHolder holder, int position) {
        ItemPostBinding postBinding = holder.binding;
        postBinding.setViewModel(new PostViewModel(mContext, mPosts.get(position), mIsUserPosts));
    }

    @Override
    public int getItemCount() {
        return mPosts.size();
    }

    public void setItems(List<Post> posts) {
        mPosts = posts;
        notifyDataSetChanged();
    }

    public void addItem(Post post) {
        mPosts.add(post);
        notifyDataSetChanged();
    }

    public static class BindingHolder extends RecyclerView.ViewHolder {
        private ItemPostBinding binding;

        public BindingHolder(ItemPostBinding binding) {
            super(binding.cardView);
            this.binding = binding;
        }
    }

}

Moving on to our XML layout file, we first begin by wrapping our entire layout in a <layout> tag and declare our ViewModel using the <data> tag:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="viewModel" type="com.hitherejoe.mvvm_hackernews.viewModel.PostViewModel" /></data>
<!-- Other layout views -->
</layout>

Declaring our View Model is required to allow us to reference it throughout our layout file. I’ve made use of the ViewModel in several places within theitem_post layout:

  • androidText – It’s possible to set the content of a text view by referencing the corresponding method in our ViewModel. You can see below the use of @{viewModel.postTitle}, this references the getPostTitle() method in our ViewModel – which returns us the title of the corresponding post instance.
  • onClick – We can also reference click events from our layout file. As shown in the layout file, @{viewModel.onClickPost} is used to reference the onClickPost() method in our ViewModel, which returns an OnClickListener containing the click event.
  • visibility – The ability to open the comments activity for a post depends on whether the post has any comments or not. This is done by checking the size of the comments list and setting the visibility based on the result, which should take place in the ViewModel. Here, we use thegetCommentsVisiblity() method which returns the calculated visibility.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable name="viewModel" type="com.hitherejoe.mvvm_hackernews.viewModel.PostViewModel" />
    </data>

    <android.support.v7.widget.CardView
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="2dp"
        android:layout_marginBottom="2dp"
        card_view:cardCornerRadius="2dp"
        card_view:cardUseCompatPadding="true">

        <LinearLayout
            android:id="@+id/container_post"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:orientation="vertical"
            android:onClick="@{viewModel.onClickPost}">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:padding="16dp"
                android:background="@drawable/touchable_background_white">

                <TextView
                    android:id="@+id/text_post_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="8dp"
                    android:text="@{viewModel.postTitle}"
                    android:textColor="@color/black_87pc"
                    android:textSize="@dimen/text_large_title"
                    android:onClick="@{viewModel.onClickPost}"/>

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <TextView
                        android:id="@+id/text_post_points"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentLeft="true"
                        android:text="@{viewModel.postScore}"
                        android:textSize="@dimen/text_body"
                        android:textColor="@color/hn_orange" />

                    <TextView
                        android:id="@+id/text_post_author"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_toRightOf="@+id/text_post_points"
                        android:text="@{viewModel.postAuthor}"
                        android:textColor="@color/black_87pc"
                        android:textSize="@dimen/text_body"
                        android:bufferType="spannable"
                        android:onClick="@{viewModel.onClickAuthor}"/>

                </RelativeLayout>

            </LinearLayout>

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="@color/light_grey" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:background="@color/white">

                <TextView
                    android:id="@+id/text_view_post"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="16dp"
                    android:background="@drawable/touchable_background_white"
                    android:clickable="true"
                    android:textColor="@color/black"
                    android:textSize="@dimen/text_small_body"
                    android:textStyle="bold"
                    android:text="@string/view_button"
                    android:onClick="@{viewModel.onClickPost}"/>

                <TextView
                    android:id="@+id/text_view_comments"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="16dp"
                    android:background="@drawable/touchable_background_white"
                    android:clickable="true"
                    android:textColor="@color/hn_orange"
                    android:textSize="@dimen/text_small_body"
                    android:text="@string/comments_button"
                    android:onClick="@{viewModel.onClickComments}"
                    android:visibility="@{viewModel.commentsVisibility}"/>

            </LinearLayout>

        </LinearLayout>

    </android.support.v7.widget.CardView>

</layout>

That’s great isn’t it? This allows us to abstract the display logic from our layout file, handing it over to our ViewModel to take care of it for us.

ViewModel

The ViewModel is the component which acts as the link between the View and the Model, giving it the responsibility of all of the logic relating to our View. The ViewModel is responsible for accessing the methods and properties of the Model, which is then made available to the View. Within our ViewModel, this data can be returned as is or formatted to again remove this responsibility from other components.

In our case, the PostViewModel uses the Post object to handle the display of content on the CardView of a Post instance. Within this class (below) you can see a whole bunch of methods, each corresponding to a different property of our Post View.

  • getPostTitle() – This uses the Post instance to return the Post title
  • getPostAuthor() – This method begins by retreiving a String from the app resources and formatting it with the author of the Post instance. Then if our isUserPosts equates to true we underline the text, finally returning our content String
  • getCommentsVisibility() – This method returns the value that should be used for the comment TextViews visibility
  • onClickPost() – This method returns a click event when the corresponding view is pressed

These samples show the different kinds of logic that can currently be handled by our ViewModel. Below shows the complete PostViewModel class and its methods which are referenced from our item_post view.

public class PostViewModel extends BaseObservable {

    private Context context;
    private Post post;
    private Boolean isUserPosts;

    public PostViewModel(Context context, Post post, boolean isUserPosts) {
        this.context = context;
        this.post = post;
        this.isUserPosts = isUserPosts;
    }

    public String getPostScore() {
        return String.valueOf(post.score) + context.getString(R.string.story_points);
    }

    public String getPostTitle() {
        return post.title;
    }

    public Spannable getPostAuthor() {
        String author = context.getString(R.string.text_post_author, post.by);
        SpannableString content = new SpannableString(author);
        int index = author.indexOf(post.by);
        if (!isUserPosts) content.setSpan(new UnderlineSpan(), index, post.by.length() + index, 0);
        return content;
    }

    public int getCommentsVisibility() {
        return  post.postType == Post.PostType.STORY && post.kids == null ? View.GONE : View.VISIBLE;
    }

    public View.OnClickListener onClickPost() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Post.PostType postType = post.postType;
                if (postType == Post.PostType.JOB || postType == Post.PostType.STORY) {
                    launchStoryActivity();
                } else if (postType == Post.PostType.ASK) {
                    launchCommentsActivity();
                }
            }
        };
    }

    public View.OnClickListener onClickAuthor() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                context.startActivity(UserActivity.getStartIntent(context, post.by));
            }
        };
    }

    public View.OnClickListener onClickComments() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                launchCommentsActivity();
            }
        };
    }

    private void launchStoryActivity() {
        context.startActivity(ViewStoryActivity.getStartIntent(context, post));
    }

    private void launchCommentsActivity() {
        context.startActivity(CommentsActivity.getStartIntent(context, post));
    }
}

Great, huh? As you can see, our PostViewModel takes care of:

  • Providing the Post objects properties to be displayed in our view
  • Carrying out any required formatting on these properties
  • Returning click events to any views using the onClick attribute
  • Handling the visibility of any views based on Post properties

Testing the ViewModel

One of the great things about MVVM is that our View Model is now extremely easy to unit test. For the PostViewModel, a simple test class was created to test that the methods in the ViewModel were implemented correctly.

  • shouldGetPostScore() – Test the getPostScore() method, to ensure that the score for the Post is correctly formatted as a String and returned.
  • shouldGetPostTitle() – Test the getPostTitle() method, to ensure that the correct Post title is returned.
  • shouldGetPostAuthor() – Test the getPostAuthor() method, to ensure that a correctly formatted string using the Post author is returned.
  • shouldGetCommentsVisiblity() – Test that the getCommentsVisibility() method returns the correct visibility for the ‘Comments’ button on the Post card. We pass ArrayLists of different states to ensure that the correct visibility is returned for each case.
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = DefaultConfig.EMULATE_SDK, manifest = DefaultConfig.MANIFEST)
public class PostViewModelTest {

    private Context mContext;
    private PostViewModel mPostViewModel;
    private Post mPost;

    @Before
    public void setUp() {
        mContext = RuntimeEnvironment.application;
        mPost = MockModelsUtil.createMockStory();
        mPostViewModel = new PostViewModel(mContext, mPost, false);
    }

    @Test
    public void shouldGetPostScore() throws Exception {
        String postScore = mPost.score + mContext.getResources().getString(R.string.story_points);
        assertEquals(mPostViewModel.getPostScore(), postScore);
    }

    @Test
    public void shouldGetPostTitle() throws Exception {
        assertEquals(mPostViewModel.getPostTitle(), mPost.title);
    }

    @Test
    public void shouldGetPostAuthor() throws Exception {
        String author = mContext.getString(R.string.text_post_author, mPost.by);
        assertEquals(mPostViewModel.getPostAuthor().toString(), author);
    }

    @Test
    public void shouldGetCommentsVisibility() throws Exception {
        // Our mock post is of the type story, so this should return gone
        mPost.kids = null;
        assertEquals(mPostViewModel.getCommentsVisibility(), View.GONE);
        mPost.kids = new ArrayList<>();
        assertEquals(mPostViewModel.getCommentsVisibility(), View.VISIBLE);
        mPost.kids = null;
        mPost.postType = Post.PostType.ASK;
        assertEquals(mPostViewModel.getCommentsVisibility(), View.VISIBLE);
    }
}

And now we know that our ViewModel is working as it should, great!

Comments

The approach used for comments is very similar to that of the Post instances, however there is one difference that I would like to point out.

Two different view models are used regarding the comments, theCommentHeaderViewModel and CommentViewModel. If you look at theCommentAdapter then you’ll notice two different view types, which are:

private static final int VIEW_TYPE_COMMENT = 0;    
private static final int VIEW_TYPE_HEADER = 1;

If the Post type is an Ask post, then we show a header section at the top of the screen containing the question which was asked – the comments are displayed as normal below. You’ll notice in the onCreateViewHolder()method we inflate the layout based on the VIEW_TYPE that we are currently dealing with, this simply returns one of our two different layouts.

if (viewType == VIEW_TYPE_HEADER) {
    ItemCommentsHeaderBinding commentsHeaderBinding =     
    DataBindingUtil.inflate(
            LayoutInflater.from(parent.getContext()),
            R.layout.item_comments_header,
            parent,
            false);
    return new BindingHolder(commentsHeaderBinding);
} else {
    ItemCommentBinding commentBinding = 
        DataBindingUtil.inflate(
            LayoutInflater.from(parent.getContext()),
            R.layout.item_comment,
            parent,
            false);
    return new BindingHolder(commentBinding);
}

Then in our onBindViewHolder() method we create the binding depending on the type view that we’re dealing with. This is because we’re using a slightly different View Model for the cases when there is a header section (for our ASK post question text) used.

if (getItemViewType(position) == VIEW_TYPE_HEADER) {
    ItemCommentsHeaderBinding commentsHeaderBinding = 
                        (ItemCommentsHeaderBinding) holder.binding;
    commentsHeaderBinding.setViewModel(new   
                          CommentHeaderViewModel(mContext, mPost));
} else {
    int actualPosition = (postHasText()) ? position - 1 : position;
    ItemCommentBinding commentsBinding = 
                               (ItemCommentBinding) holder.binding;
    mComments.get(actualPosition).isTopLevelComment = 
                                               actualPosition == 0;
    commentsBinding.setViewModel(new CommentViewModel(
                         mContext, mComments.get(actualPosition)));
}

And that’s pretty much all that is different about it, the comments section just has two different ViewModel types available – the one chosen is dependent on whether the post is an ASK post or not.

To conclude…

The data binding library, if used correctly, has the potential to really change the way in which we develop applications. There are other ways in which we could make use of data binding in our applications, using an MVVM structure is just one of the ways in which we can do so.

For example, we could simply reference our Model in the layout file and access its properties through a variable reference:

<data>
    <variable name="post" type="your.package.name.model.Post"/>
</data>
<TextView 
    ...
    android:text="@{post.title}"/>

This is simple to do and could help to remove some basic display logic from adapters and/or classes. Whilst this is nice, a similar approach could result in the following:

<data>
    <import type="android.view.View"/>
</data>
<TextView 
    ...
    android:visibility="@{post.hasComments ? View.Visible :      
    View.Gone}"/>

For me, this is where Data Binding could have a negative effect on its usage. This is moving a Views display logic into the View itself. Not only do I find this messy, but it would also make testing / debugging more difficult by mixing logic and layout code together.


It’s still too early to know if this approach is the correct way of developing an application, but this experiment has given me a chance to look at one of the possibilities for future projects. It’s something I definitely want to play around with more. If you wish to read more about the Data Binding library, you can do so here. Microsoft has also written a short and easy to understand article on MVVM here.

I’d love to hear your thoughts on this experiment, if you’ve got any comments and/or suggestions then feel free to leave a response or drop me a tweet!

Building an Android App Using the MVP Pattern (Model-View-Presenter)

英文链接:https://medium.com/android-news/building-an-android-app-using-the-mvp-pattern-model-view-presenter-e38822bcece7#.hwm7g52vi

实例项目地址:https://gist.github.com/tanayagrawal

Building Android applications is fun and challenging. We at SocialCops built Collect — a mobile-based data collection tool, currently being used by nearly 150 organizations around the world. The ever-increasing number of users, encouraging as it was, made it difficult to scale up Collect with the existing code structure.

That was when we decided to move to the Model-View-Presenter (MVP) design pattern to solve the existing scale-related issues in Collect and, most importantly, improve the core functionality of the app — data collection.

What Is the MVP Pattern?

The MVP Pattern is a software design pattern commonly used for developing Android applications. It has gained importance over the last couple of years due to the need for developing clean applications that are easy to scale, test, and maintain. The MVP pattern fills this need by enabling the development of highly decoupled applications.

Why Use the MVP Pattern?

In a typical Model-View design pattern, the views and data layers of the application are tightly coupled and hence there is no layer of abstraction between the two layers. Therefore, for all practical purposes, there is only one layer handling both the views and their interaction with different data sources. This leads to redundancy in code and difficulty in scaling.

To overcome all the above drawbacks of a tightly coupled design, the MVP Pattern is used.

It is always a good practice to separate the data layer from the views of the app. The data can then be modeled separately in a different layer to cater to multiple views.

  • Easy Data Modeling: It is always a good practice to separate the data layer from the views of the app. The data can then be modeled separately in a different layer to cater to multiple views. This is where the presentation layer comes in handy.
  • Easy Debugging: By creating three different layers of abstraction it becomes easier to test and debug each layer in isolation and narrow the source of a bug down to one of the three layers.
  • Increased Code Re-Usability: The developer has to write a lot less code because a single data source serves all the views. This saves bandwidth since similar data doesn’t have to be queried more than once.

Left: Typical Android App Structure. Right: Decoupled MVP Pattern

Layers in the MVP Pattern

Let’s dissect the architecture to look at the functions and components of each layer and how they interact with each other.

View Layer

The View Layer is only responsible for handling different views visible to the user, such as activities, fragments, and dialogs.

This is the only layer that consists of different Android components and is completely orchestrated by the Presentation Layer. The View Layer delegates all the user events that occur on it to its corresponding presenter and then waits for the presenter to respond with the appropriate data.

public interface LoginView {
  
  String getUsername();
  
  String getPassword();
  
  void navigateToMainActivity();
  
  void showError(String message);
}
public class LoginActivity implements LoginView{

private LoginPresenterInterface iLoginPresenter;

@BindView(R.id.username_textView)
EditText username;
@BindView(R.id.password_textView)
EditText password;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        Butterknife.bind(this);
        iLoginPresenter = new LoginPresenter(this);
    
      }
    /**
     * Detect login button click
     */
    @OnClick(R.id.login_button)
    public void onLoginButtonPressed(){
      iLoginPresenter.validateCredentials();
    }
    /**
     * Fetch username
     */
    @Override
    public String getUsername() {
      return username.getText().toString();
    }
    /**
     * Fetch password
     */
    @Override
    public String getPassword() {
      return password.getText.toString();
    }
    
    /**
     * Take the user to main activity after successful authentication
     */
    @Override
    public void navigateToMainActivity(){
      startActivity(new Intent(this, MainActivity.class));
    }
    /**
     * Show error messages
     */
    @Override
    public void showError(String message){
      Toast.makeText(this, message, Toast.LENGTH_LONG);
    }
}

Presentation Layer

The Presentation Layer, aka the Presenter, holds all the business logic for the application. It acts as a channel of communication between the View Layer and the Data Layer.

The Presenter too has its own interface through which both the View and Data Layers communicate.

The View Layer delegates all the events that occur on the device’s screen to the Presentation Layer. The Presenter interprets each event as a data requirement. It then fetches the required data from the Data Layer, models it, and presents it to the View Layer.

Unlike the Data Layer, the Presentation Layer is not common to the whole application and is different for each view on the mobile device. The Presenter cannot communicate with views other than its own and vice versa.

public interface LoginPresenterInterface {
  
  void validateCredentials();
  
}
public class LoginPresenter implements LoginPresenterInterface, IListener<User> {
  
  LoginView iLoginView;
  LoginServiceInterface iLoginService;
  
  public LoginPresenter(LoginView loginView){
    iLoginView = loginView;
    iLoginService = new LoginService();
  }
  
  @Override
  public void validateCredentials(){
    String username = iLoginView.getUsername();
    /**
     * Check if username is empty or not
     */
    if(username.isEmpty()){
      iLoginView.showError("Username cannot be empty");
      return;
    }
    
    String password = iLoginView.getPassword();
    /**
     * Check if password is empty or not
     */
    if(password.isEmpty()){
      iLoginView.showError("Password cannot be empty");
      return;
    }
    
    /**
     * Call login service to authenticate user credentials from the backend
     */
    iLoginService.validateCredentialsFromBackendService(username, password, this);
  }
  
  @Override
  public void onSuccess(User user){
    /**
     * After successful authentication take the user to the main activity
     */
    iLoginView.navigateToMainActivity();
  }
  
  @Override
  public void onFailure(String errorMessage){
    /**
     * Show error message if the authentication fails
     */
    iLoginView.showError(errorMessage);
  }

}

Data Layer

The Data Layer is responsible for all the data requirements of the application.

It consists of 4 different components:

  • CRUD operations from a locally embedded database
  • Data storage and retrieval from the device’s internal/external memory
  • Network and API calls
  • Different entity models

Each component has an interface associated with it. The interface acts as a gateway between the Presentation Layer and the Data Layer. Each interface consists of method declarations that are implemented by their respective components.

public interface LoginServiceInterface {
  
  void validateCredentialsFromBackendService(String username, String password, IListener<User> userCallback);
  
}
public class LoginService implements LoginServiceInterface {
  
  public LoginService() {
    
  }
  
  public void validateCredentialsFromBackendService(String username, String password, IListener<User> userCallback){
    
    /**
     * Dummy function which authenticates the user 
     * And we finally recieve a onResponse/onFailure depending on whether the user has been authenticated
     */
    
      @Override
      public void onResponse(Response<User> response){
        if(response.code() == 200) {
          /**
           * If the response code is 200(success), send the user data to the presenter.
           */ 
          usercallback.onSuccess(response.body());
        }
      }
      
      @Override
      public void onFailure(Throwable t){
        /**
         * If authentication fails, send the error message to the presenter
         */
        userCallback.onFailure(t.toString);
      }

  }
}

Bringing All Layers of the MVP Pattern Together

Here’s a graphic representation of how all three views come together in the broader architecture:

A good practice to move data objects between different layers is through using listeners and not making methods return data objects.

public interface IListener<T> {

    void onSuccess(T t);

    void onFailure(String errorMessage);
}
 Testing Apps Built Using the MVP Pattern

Building your application in 3 different layers helps you to test each layer in isolation. Moreover, with the MVP Pattern, testing can be automated to a large extent. There are libraries available to make this process easy for you.

Testing is mainly divided into two parts:

  • Unit Testing: used for testing the Presentation and Data Layers
  • Instrumentation Testing: used for testing the View Layer

Unit testing for the Presentation Layer and the Data Layer can be done on the JVM (Java Virtual Machine) itself and does not require any device. This is because both these layers are completely written in native Java and do not contain any Android concepts. Since the Presenter interacts with the view through an interface, the presenter treats the view as an abstract concept and does not have any knowledge of Android components in the view. At SocialCops, we use JUnit4 for writing unit tests.

Instrumentation tests are done for the View Layer, which requires an Android device. We use Espresso to write instrumentation tests.

You might be wondering about how we test each layer in isolation without requiring input from the other two layers. For this, we use Mockito to make mock objects in Java.

The MVP Pattern is an important design pattern for developing Android applications. It helps developers build scalable, maintainable, and test-ready applications without any extra effort.

Initially, it might seem pretty difficult during implementation since you, as the developer, will have to create different classes and interfaces for each layer. However, once you get the hang of it, trust us that it will be your go-to way of developing elegant Android applications.

Originally published at blog.socialcops.com on May 11, 2016.

Architecting Android with Data Binding and MVVM in mind

英文链接:https://medium.com/cobe-mobile/architecting-android-with-data-binding-and-mvvm-in-mind-8874bbec0b0d#.574kr3pj7

作者总结了3个Android中使用的架构MVC, MVP, MVVM, 其实他本人更倾向于并一直在用的架构是MVP!

When Google announced Data Binding Library at last year Google I/O, I was thinking “oh man, this is the next big thing in Android development”. And yeah, the hype was real (for the next two weeks), and it all kinda disappeared. My thoughts are that developers were so into MVP and Rx that they did not have any time to experiment with Data Binding. And also, you cannot “just” use Data Binding, you need to spend some time learning it and change your application’s architecture to write a testable Android Application with Data Binding in mind.


The purpose of this post is to give you my personal opinion about different types of architecture that are used in Android projects. Also, we will see what architecture suits Data Binding the best.


Model View Controller (God objects)

Most of the Android applications I have seen so far has been made in traditional “Model-View-Controller” approach. This approach is the fastest one and includes 1000+ lines of code in a single Activity/Fragment (God Object) and code is very messy and buggy. Those classes have references to everything and do everything.

In this case, every Activity/Fragment in the application acts as aController Layer (click listeners, item click listeners, text watchers…) andModel Layer (RestApi calls, Database calls, business logic…). XML layout is actually a View Layer.

Typical MVC based Android Application structure

I personally know a couple of Android developers who quit their job because they needed to implement new feature in those classes.

This architecture is not readable, not testable, and developers do not like working with this architecture.

Model View Presenter

This is how I see good Android application architecture. This architecture is becoming day by day more popular in the Android world, because every layer in this architecture has its own job.

Example of a good Android Architecture (MVP + Rx)

In the example above, DataManager is the Model Layer. It holds reference to RestApis (like Retrofit), database (SQLite), etc. A typical scenario is that model layer gets data from a backend, saves data, and prepares (transforms) data for the Presentation layer.

public class DataManager {

    private final SomeInteractor someInteractor;
    private final SomeDatabase someDatabase;

    public DataManager(SomeInteractor someInteractor, SomeDatabase someDatabase) {
        this.someInteractor = someInteractor;
        this.someDatabase = someDatabase;
    }

    public void requestSomething(long someId, final Observer<PresentationResponseModel> observer) {
       //request data from backend, save into database, transform (prepare) data for the presentation model, return to presentation model
       ...
    }
}

Presenter Layer is a “middle man” between the View Layer and Model Layer. The presenter does not know anything about Activity/Fragment/View, and has a reference to the Model Layer.

public class SomePresenterImpl implements SomePresenter {

    private final SomeView someView;
    private final DataManager dataManager;

    public SomePresenterImpl(SomeView someView, DataManager dataManager) {
        this.someView = someView;
        this.dataManager = dataManager;
    }
public void getSomething(long someId){
        //get object response from model layer, and return it to View Layer 
   }
}

View Layer is a combination of Activity/Fragment with implementation of the View Interface and XML layout. Activity/Fragment holds a (usually soft) reference to Presenter, and calls the right methods on the presenter on user interaction.

public interface SomeView {

    void showTitle(String title);

    ...
}

This architecture is readable, testable, and developers are happy to work with this architecture.

Model View ViewModel

This is where an actual Data Binding gets in place. MVVM architecture separates UI (View) logic from the Business logic.

Example of a MVVM implementation in Android

In this example, DataManager is a Model Layer. Like in MVP, DataManager holds a reference to the RestApi (like Retrofit), database (SQLite), etc. Typical scenario is that model layer gets data from the backend and saves data. The difference between MVP and MVVM from the perspective of the Model Layer is that in MVVM architecture DataManager returns response to Activity/Fragment instead to Presenter. That means that Activity/Fragment is aware of business logic (POJO).

public class DataManager {

    private SomeInteractor someInteractor;

    public DataManager(SomeInteractor someInteractor) {
        this.someInteractor = someInteractor;
    }

    public void getSomething(@NonNull final ResponseListener<List<SomeResponse>> listener) {
        //get data from backend and return data to activity/fragment
    }
}

View Layer is a combination of Activity/Fragment with XML and binding. Typical scenario is that Activity requests data from the backend, gets data (POJO) and forwards it to ViewModel Layer. ViewModel Layer updates the UI with the new data.

public class SomeFragment extends BaseFragment implements ResponseListener<SomeResponse> {
    private SomeFragmentBinding mSomeFragmentBinding;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mSomeFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_something, container, false);
        return mSomeFragmentBinding.getRoot();
    }

And XML layout…

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="someViewModel"
            type="com.android.something.SomeViewModel" />
    </data>
<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{someViewModel.startDetailsActivity()}"     
    android:text="@{someViewModel.someTitle}" />
</layout>

ViewModel is the middle man between the View Layer and the model (POJO). It receives data from Model Layer and updates the View Layer. Also, it manipulates the model state (fields in POJO objects) as a result from user interaction from the View Layer.

public class SomeViewModel {

   private final SomeResponse something;
   private final Context context;
   public SomeViewModel(Context from, SomeResponse something) {
       this.something = something;
       this.context = from;
    }

    public String getName() {
        return something.getName();
    }

    public OnClickListener startDetailsActivity() {
        //returns onClick listener that starts details activity
    }
}

This architecture is partially readable, partially testable, and most developers likes working with this architecture.


To conclude this post, from my personal point of view where I still learning different architectural approaches, I would definitely always choose Model View Presenter approach no matter of project requirements, and I think most developers share the same opinion. When developing Android application, MVP approach feels natural with Android, it can be fully testable, business logic is separated from the Android world and you know exactly what each layer in MVP architecture is doing.

[转载]实战解析Android架构设计原则

嘿!经过一段时间收集了大量反馈意见后,我认为应该来说说这个话题了。我会在这里给出我认为构建现代移动应用(Android)的好方法,这会是另一番体味。

开始之前,假设你已经阅读过我之前撰写的文章“ Architecting Android…The clean way ?”。如果还没有阅读过,为了更好地理解这篇文章,应借此机会读一读:

实战解析Android架构设计原则

架构演变

演变意味着一个循序渐进的过程,由某些状态改变到另一种不同的状态,且新状态通常更好或更复杂。

照这么一说,软件是随着时间发展和改变的,是架构上的发展和改变。实际上,好的软件设计必须能够帮助我们发展和扩充解决方案,保持其健壮性,而不 必每件事都重写代码(虽然在某些情况下重写的方法更好,但是那是另一篇文章的话题,所以相信我,让我们聚焦在前面所讨论的话题上)。

在这篇文章中,我将讲解我认为是必需的和重要的要点,为了保持基本代码条理清晰,要记住下面这张图片,我们开始吧!

实战解析Android架构设计原则

响应式方法:RxJava

因为已经 有很多这方面的文章 ,还有这方面 做得很好、令人景仰的人 ,所以我不打算在这里讨论RxJava的好处( 我假设您已经对它有所体验了 )。但是,我将指出在Android应用程序开发方面的有趣之处,以及如何帮助我形成第一个清晰的架构的方法。

首先,我选择了一种响应式的模式通过转换usecase(在这个清晰的架构命名规则中,其被称为interactor)返回Observables<T>,表示所有底层都遵循这一链条,也返回Observables<T> 。

实战解析Android架构设计原则

正如你所看到的,所有用例继承这个抽象类,并实现抽象方法buildUseCaseObservable()。该方法将建立一个Observables<T>,它承担了繁重的工作,还要返回所需的数据。

需要强调是,在execute()方法中,要确保Observables<T> 是在独立线程执行,因此,要尽可能减轻阻止android主线程的程度。其结果就是会通过android主线程调度程序将主线程压入线程队列的尾部(push back)。

到目前为止,我们的Observables<T>启动并且运行了。但是,正如你所知,必须要观察它所发出的数据序列。要做到这一点, 我改进了presenters(MVP模式表现层的一部分),把它变成了观察者(Subscribers),它通过用例对发出的项目做出“react”, 以便更新用户界面。

观察者是这样的:

实战解析Android架构设计原则

每个观察者都是每个presenter的内部类,并实现了一个Defaultsubscriber<T>接口,创建了基本的默认错误处理。

将所有的片段放在一起后,通过下面的图,你可以获得完整的概念:

实战解析Android架构设计原则

让我们列举一些摆脱基于RxJava方法的好处:

在观察者(Subscribers)与被观察者(Observables)之间去耦合:更加易于维护和测试。

  • 简化异步任务:如果要求多个异步执行时,如果需要一个以上异步执行的级别,Java的thread和future的操作和同步比较复杂,因此 通过使用调度程序,我们可以很方便地(不需要额外工作)在后台与主线程之间跳转,特别是当我们需要更新UI时。还可以避免“回调的坑”—— 它使我们代码可读性差,且难以跟进。
  • 数据转换/组成:在不影响客户端情况下,我们能够整合多个Observables<T>,使解决方案更灵活。
  • 错误处理:在任何Observables<T>内发生错误时,就向消费者发出信号。

从我的角度看有一点不足,甚至要为此需要付出代价,那些还不熟悉概念的开发人员还是要遵循学习曲线。但你从中得到了极有价值的东西。为了成功而reactive起来吧!

依赖注入:Dagger 2

关于依赖注入, 因为我已经写了一篇完整的文章 ,我不想说太多。强烈建议你阅读它,这样我们就可以接着说下面的内容了。

值得一提的是,通过实现一个像Dagger 2那样的依赖注入框架我们能够获得:

  • 组件重用,因为依赖的对象可以在外部注入和配置。
  • 当注入对象作为协作者(collaborators)时,由于对象的实例存在于在一个隔离和解耦地方,这样在我们的代码库中,就不需要做很多的改变,就可以改变任何对象的实现。
  • 依赖可以注入到一个组件:这些将这些模拟实现的依赖对象注入成为可能,这使得测试更容易。

Lambda表达式:Retrolambda

没有人会抱怨在代码中使用Java 8的lambada表达式,甚至在简化并摆脱了很多样板代码以后,使用得更多,如你看到这段代码:

实战解析Android架构设计原则

然而,我百感交集,为什么呢?我们曾在 @SoundCloud 讨论 Retrolambada ,主要是是否使用它,结果是:

1. 赞成的理由:

  • Lambda表达式和方法引用
  • “try-with-resources”语句
  • 使用karma做开发

2. 反对的理由:

  • Java 8 API的意外使用
  • 十分令人反感的第三方库
  • 要与Android一起使用的第三方插件Gradle

最后,我们认定它不能为我们解决任何问题:你的代码看起来很好且具有可读性,但这不是我们与之共存的东西,由于现在所有功能最强大的IDE都包含代码折叠式选项,这就涵盖这一需求了,至少是一个可接受的方式。

说实话,尽管我可能会在业余时间的项目中使用它,但在这里使用它的主要原因是尝试和体验Android中Lambda表达式。是否使用它由你自己决定。在这里我只是展示我的视野。当然,对于这样一个了不起的工作,这个 的作者值得我的称赞。

测试方法

在测试方面,与示例的第一个版本相关的部分变化不大:

  • 表现层:用Espresso 2和Android Instrumentation测试框架测试UI。
  • 领域层:JUnit + Mockito —— 它是Java的标准模块。
  • 数据层:将测试组合换成了Robolectric 3 + JUnit + Mockito。这一层的测试曾经存在于单独的Android模块。由于当时(当前示例程序的第一个版本)没有内置单元测试的支持,也没有建立像 robolectric那样的框架,该框架比较复杂,需要一群黑客的帮忙才能让其正常工作。

幸运的是,这都是过去的一部分,而现在所有都是即刻可用,这样我可以把它们重新放到数据模块内,专门为其默认的测试路径:src/test/java。

包的组织

我认为一个好的架构关键因素之一是代码/包的组织:程序员浏览源代码遇到的第一件事情就是包结构。一切从它流出,一切依赖于它。

我们能够辨别出将应用程序封装进入包(package)的2个路径:

  • 按层分包:每一个包(package)中包含的项通常不是彼此密切相关的。这样包的内聚性低、模块化程度低,包之间偶合度高。因此,编辑某个特性要编辑来自不同包的文件。另外,单次操作几乎不可能删除掉某个功能特性。
  • 按特性分包:用包来体现特性集。把所有相关某一特性(且仅特性相关)的项放入一个包中。这样包的内聚性高,模块化程度高,包之间偶合度低。紧密相关的项放在一起。它们没有分散到整个应用程序中。

我的建议是去掉按特性分包,会带来的好处有以下主要几点:

  • 模块化程度更高
  • 代码导航更容易
  • 功能特性的作用域范围最小化了

如果与功能特性团队一起工作(就像我们在@SoundCloud的所作所为),也会是非常有趣的事情。代码的所有权会更容易组织,也更容易被模块化。在许多开发人员共用一个代码库的成长型组织当中,这是一种成功。

实战解析Android架构设计原则

如你所见,我的方法看起来就像按层分包:这里我可能会犯错(例如,在“users”下组织一切),但在这种情况下我会原谅自己,因为这是个以学习为目的的例子,而且我想显示的是清晰架构方法的主要概念。领会其意,切勿盲目模仿:-)。

还需要做的事:组织构建逻辑

我们都知道,房子是从地基上建立起来的。软件开发也是这样,我想说的是,从我的角度来看,构建系统(及其组织)是软件架构的重要部分。

在Android平台上,我们采用Gradle,它事实上是一种与平台无关的构建系统,功能非常强大。这里的想法是通过一些提示和技巧,让你组织构建应用程序时能够得到简化。

  • 在单独的gradle构建文件中按功能对内容进行分组

实战解析Android架构设计原则

实战解析Android架构设计原则

因此,你可以用“apply from: ‘buildsystem/ci.gradle’”插入到任何Gradle建立的文件中进行配置。不要把所有都放置在一个build.gradle文件中,否则就是去创建一个怪物,这是教训。

  • 创建依赖关系图

实战解析Android架构设计原则

实战解析Android架构设计原则

如果想在项目的不同模块间重用相同的组件版本,这很好;否则就要在不同的模块间使用不同的版本的组件依赖。另外一点,你是在同一个地方控制依赖关系,像组件版本发生冲突这样的事情一眼就能看出来。

结语

到目前为止讲了那么多,一句话,要记住没有灵丹妙药。但好的软件架构会帮助代码保持清晰和健壮,还可以保持代码的可扩展性,易于维护。

我想指出一些事情。面对软件存在的问题,要报以本应当解决的态度:

  • 遵守SOLID原则
  • 不要过度思考(不过度工程化)
  • 务实
  • 尽可能降低(Android)框架中模块的依赖性

源代码

  1. Clean architecture github repository – master branch
  2. Clean architecture github repository – releases

延伸阅读

  1. Architecting Android..the clean way
  2. Tasting Dagger 2 on Android
  3. The Mayans Lost Guide to RxJava on Android
  4. It is about philosophy: Culture of a good programmer

参考资料

  1. RxJava wiki by Netflix
  2. Framework bound by Uncle Bob
  3. Gradle user guide
  4. Package by feature, not layer

[转]Facebook移动架构:Android Flux架构详解

要为Android应用找到一个好的架构不是一件容易的事情。谷歌似乎不太在乎这个事情,因此在设计模式上,除了Activity 生命周期管理之外,再也没有官方的推荐。

但是,为你的应用打造一个架构是非常重要的。不管你是否喜欢,任何应用最终都会有一个架构。因此你最好是成为一个架构的奠基人,而不是等着它出现。

今天: Clean Architecture

目前的趋势是采用Uncle Bob在2012年对web应用提出的建议: Clean Architecture

但是我发现Clean Architecture对于绝大多数安卓应用来说都有点过度设计了。

通常移动应用要比web应用的生命短。移动端技术的发展太快,以至于今天发行的app可能在一年后已经完全过时。

移动应用所做的事情很少。绝大多数的用例都只是数据信息流的消费。从API获取数据,显示数据给用户,很少有输入与写入。

所以它的业务逻辑并不复杂。至少不如后端一样的复杂。虽然你要处理很多平台上的问题:内存,存储,暂停,恢复,网络,定位等等,但是这些都不是业务逻辑。所有app都有这些东西。

因此,绝大多数app似乎都无法从类似于复杂的分层或者工作执行优先级队列中获益。

他们也许只是需要一种组织代码的简单方式,能高效的一起工作,更容易的发现bug。

Flux 架构介绍

Flux 架构 被Facebook使用来构建他们的客户端web应用。跟Clean Architecture一样,它不是为移动应用设计的,但是它的特性和简单可以让我们很好的在安卓项目中采用。

安卓中的Flux架构

要理解Flux,有两个关键的特点

  • 数据流总是单向的一个单向的数据流 是 Flux 架构的核心,也是它简单易学的原因。就如下面讨论的,在进行应用测试的时候,它提供了非常大的帮助。
  • 应用被分成三个主要部分:
    • View: 应用的界面。这里创建响应用户操作的action。
    • Dispatcher: 中心枢纽,传递所有的action,负责把它们运达每个Store。
    • Store: 维护一个特定application domain的状态。它们根据当前状态响应action,执行业务逻辑,同时在完成的时候发出一个change事件。这个事件用于view更新其界面。

这三个部分都是通过Action来通信的:一个简单的基本对象,以类型来区分,包含了和操作相关的数据。

Flux Android 架构

在Android开发中使用Flux设计规范的目的是建立一个在简单性与易扩展易测试之间都比较平衡的架构。

第一步是找到Flux元素和安卓app组件之间的映射。

其中两个元素非常容易找到与实现。

  • View: Activity o或者Fragment
  • Dispatcher: 一个事件总线( event bus),在我的例子中将使用Otto,但是其它任何实现都应该是ok的。

Actions

Actions也不复杂。它们的实现和POJO一样简单,有两个主要属性:

  • Type: 一个String,定义了事件的类型。
  • Data: 一个map,装载了本次操作。

比如,一个显示用户详情的典型action如下:

Bundle data = new Bundle(); 
data.put("USER_ID", id); 
Action action = new ViewAction("SHOW_USER", data);

Stores

这可能是Flux理论中最难的部分。

如果你之前使用过Clean Architecture,你可能难以接受。因为Stores承担了原本被分成多层的责任。

Stores包含了application的状态与它的业务逻辑。它们类似于rich data models但是可以管理多个对象的状态,而不仅仅是一个对象。

Stores响应Dispatcher发出的Action,执行业务逻辑并发送change事件。

Stores的唯一输出是这单一的事件:change。其它对Store内部状态感兴趣的组件必须监听这个事件,同时使用它获取需要的数据。

系统中不再需要任何其它组建去了解application的任何状态信息。

最后,stores必须对外公开一个获取application状态的接口。这样,view元素可以查询Stores然后相应的更新UI。

安卓中的Flux架构

比如,在一个Pub Discovery App 中,SearchStore被用来跟踪被搜索的item,搜索结果以及搜索历史。在同一个应用中,一个ReviewedStore同样包含了浏览pub的列表以及必要的逻辑比如根据review排序。

但是有一个重要的概念需要记住:Stores并不是仓库。它们的职责不是从一个外部源(API或者数据库)获取数据,而是跟踪actions提供的数据。

那么,Flux application是如何获得数据的呢?

网络请求与异步调用

在第一幅Flux示意图中我有意跳过了一部分:网络调用。接下来的示意图完善第一幅图并添加了更多细节:

安卓中的Flux架构

异步网络调用是被一个Actions Creator触发的。一个Network 适配器完成相应API的异步调用并且返回结果给Actions Creator。

最终Actions Creator分发带有返回数据的相应类型的Action。

把所有网络工作和异步工作独立于Stores之外有两个主要的优点:

  • 你的Stores是完全同步的:这让Store中的逻辑更容易跟踪。Bug也更容易跟踪。同时,因为所有的状态变化都是同步的,那么Store的测试变会的非常简单:启动actions然后等待期望的结果。
  • 所有的action都是从一个Action Creator触发的:在一处单一的点创建与发起所有用户操作可以大大简化寻找错误的过程。忘掉在多个类中寻找某个操作的源头吧 ,所有的事情都是在这里发生的。同时,因为异步调用发生在这之前,所有来自于ActionCreator的东西都是同步的。这大大提高了代码的可跟踪与可测试性。

演示代码:To-Do应用

在这个例子中,你将看到一个使用Flux架构的典型的To-Do应用。

我让项目尽量简单,只演示这个架构如何能够产生组织良好的app。

对于实现的一些评价:

  • Dispatcher的实现是通过Otto Bus。但是几乎任何bus都是可以的。Flux架构本身在事件上有一定限制,我在这里没有采用。原本Flux的定义中,前一个事件没有完成之前就开始分发下一个事件是不允许的,会抛出一个异常。为了让项目简单,我没有采用。
  • 有一个ActionsCreator类帮助创建Action,并把它们post给Dispatcher。这在Flux中时相当普遍的模式,可以让事情变的有序。
  • Actions类型只是String常量。也许这不是最好的实现,但是它快速并且有助于事情的简单化。

同样的还有Actions数据:它们只是以String类型为key,Object为值的HashMap。这会导致Stores中转换成实际数据的时候发生丑陋的类型转换。而且显然这也不是类型安全的,但这也是为了让我们的例子更好理解。

总结

在安卓应用中其实不存在最佳架构的说法。不过存在适合你当前app的最佳架构。这个架构可以让你和团队其他成员协作起来更轻松,按时完成项目,尽可能的保持高质量与较少的bug。

我相信Flux对于以上提到的特点都有很好的支持。

源码

https://github.com/lgvalle/android-flux-todo-app

扩展阅读:

感谢

特别感谢我们的同事Michele Bertoli 花时间向我介绍Flux与校对这篇文章。

英文原文:Flux Architecture on Android

本文地址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0816/3311.html