CleanArchitecture + ModelView(MVVM) + DataBindingを試してみる

基本的には、以前書いものにCleanArchitectureとModelView(MVVM)の構成を適用しただけになります。
> MVP + Dagger2 + Retrofit2 + RxAndroidの構成で通信を試してみる
http://qiita.com/MuuKojima/items/8843c9451339a8b68f22

今回の内容としてはUSDからJPYに変換した為替レートを受け取りTextViewに表示するだけです。
> 使用API: http://fixer.io/

実行結果

![スクリーンショット 2016-11-24 午後11.31.10.png](https://qiita-image-store.s3.amazonaws.com/0/33553/21fd96ad-2332-ab3f-3357-89002e0564f1.png "スクリーンショット 2016-11-24 午後11.31.10.png")

実際のリクエストです。
> http://api.fixer.io/latest?base=USD&symbols=JPY

下記のように1件JPYのレートが入っています。

```Json:今回の例
{
base: "USD",
date: "2016-11-21",
rates: {
		JPY: 110.61
	}
}
```

パッケージの構成は`data`、`ui`、`binding`、`di`に分割
`data`にmodelの部分をまとめる
`ui`にactivity、 view、presenter、viewModelをまとめる
`di`にDaggerで必要なmoduleをまとめる
`binding`にDataBindingで使うCallBackを入れる 

パッケージの切り方は`レイヤー`ではなく、`フィーチャー`で切り分けています。

APIから返ってきた値を一行表示するだけなのに、すごいクラス数になってますね...
どうしてもクラス数が多くなるのがCleanArchitectureのデメリットのようです...

```
|- binding
|	|- OnFieldChangedCallback
|	
|- data
|	|- exchange
| 	|	|- repository
|	|	|	|- ExchangeRateApi
|	|	|	|- ExchangeRateRepositoryImpl
|	|	|	|- ExchangeRepositoryModule
|	|	|	|
|	|	|- usecase
|	|	|	|- GetExchangeRate
|	|	|	|
|	|	|- CountryCode
|	|	|- ExchangeRateRepository
|	|	|- ExchangeRateResponse
|	|	
|	|- PostExecutionThread
|	|- UseCase
|	
|- di
|	|- ApiModule
|	|- AppComponent
|	|- JobExecutor
|	|- PerActivity
|	|- RepositoryModule
|	|- UIThread
|  	
|- ui
|	|- exchange
|		|-ExchangeRateActivity
|		|-ExchangeRateComponent
|		|-ExchangeRatePresenter
|		|-ExchangeRateView
|		|-ExchangeRateViewModel
|
|- AppApplication
```

## CleanArchitecture基本ルール(一方向からのアクセス)

![clean_architecture_evolution.png](https://qiita-image-store.s3.amazonaws.com/0/33553/74d18a4b-0cbf-daf6-bbe3-cf1408c5ae5a.png "clean_architecture_evolution.png")

※参照 http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/

この基本ルールに則って、
今回はの順にアクセスしていきます。
`ExchangeRateActivity(View)`
↓
`ExchangeRatePresenter(Presenter)`
↓
`GetExchangeRate(UseCase)`
↓
`ExchangeRateRepository(Repository)`
↓
`ExchangeRateApi(Cloud)`

DB(Disk)の方は今回はやりませんが、Repositoryに追加すれば特に他を変えずに追加する事ができ、API経由から取ってくるか、DBから取ってくるかといった事は上の方のクラスは知らずに済みます


かなり長期戦ですが...
それでは、まずrootの`build.gradle`の設定から

```gradle:build.gradle
buildscript {
    dependencies {
       // 追加
       classpath 'com.uphyca.gradle:gradle-android-apt-plugin:0.9.4'
    }
}
```

appの`build.gradle`

```gradle:build.gradle
apply plugin: 'com.android.application'
// 追加 ↑の下に入れる事
apply plugin: 'android-apt'

android {
    // 追加 ここに追加することでDataBindingが使えます
    dataBinding {
        enabled = true
    }
}

dependencies {

    //////// 追加 ////////

    // RxAndroid
    compile 'io.reactivex:rxjava:1.1.0'
    compile 'io.reactivex:rxandroid:1.1.0'
    // Dagger
    compile 'com.google.dagger:dagger:2.2'
    apt 'com.google.dagger:dagger-compiler:2.2'
    // Retrofit
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    compile 'com.squareup.retrofit2:converter-gson:2.0.2'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
    // OkHttp
    compile 'com.squareup.okhttp3:okhttp:3.2.0'
    compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
    // Gson
    compile 'com.google.code.gson:gson:2.6.2'
}
```

APIと通信するので、`AndroidManifest.xml`にインターネットパーミッションを1行追加

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kojimation.com.retrofitsample">
    
    <!-- 追加 -->
    <uses-permission android:name="android.permission.INTERNET" />
    
</manifest>
```

`data`パッケージを作成し、その中に`exchange`パッケージを作成。
`exchange`パッケージ内にAPIから返って来るレスポンス用のオブジェクトを2クラス作成

```java:CountryCode.java

public class CountryCode {
    private float JPY;

    public float getJPY() {
        return JPY;
    }
}
```

```java:ExchangeRateResponse.java

public class ExchangeRateResponse {
    private String base;
    private String date;
    private CountryCode rates;

    public String getBase() {
        return base;
    }

    public String getDate() {
        return date;
    }

    public CountryCode getRates() {
        return rates;
    }
}
```

## Repository

`exchange`パッケージ内に`repository`パッケージを作成
`repository`はデータのCRUD操作を提供するクラス
今回使う為替レートを取得する`ExchangeRateApi`を作成
rxじゃないObservableをimportしないように注意する

```java:ExchangeRateApi.java
public interface ExchangeRateApi {
    String URL = "/latest";

    @GET(URL)
    Observable<ExchangeRateResponse> getExchangeRate(@Query("base") String base,
                                                     @Query("symbols") String symbols);
}
```

`ExchangeRateApi`を使うためのインターフェース`ExchangeRateRepository`を作成

```java:ExchangeRateRepository.java
public interface ExchangeRateRepository {
    /**
     * 為替レートを取得
     *
     * @param base
     * @param symbols
     * @return
     */
    Observable<ExchangeRateResponse> getExchangeRate(String base, String symbols);
}
```

`ExchangeRateApi`を実際に使う実体クラスの`ExchangeRateRepositoryImpl`を作成
今回はAPI通信だが、DBから値を取る場合もここに記述

```java:ExchangeRateRepositoryImpl.java
@Singleton
/* package */ class ExchangeRateRepositoryImpl implements ExchangeRateRepository {

    private final ExchangeRateApi mExchangeRateApi;

    @Inject
    public ExchangeRateRepositoryImpl(ExchangeRateApi mExchangeRateApi) {
        this.mExchangeRateApi = mExchangeRateApi;
    }

    @Override
    public Observable<ExchangeRateResponse> getExchangeRate(String base, String symbols) {
        return mExchangeRateApi.getExchangeRate(base, symbols);
    }
}
```

`ExchangeRateRepository `を提供する`ExchangeRepositoryModule`を作成
Moduleクラスは@Moduleを付け、クラス名の接尾辞をModuleとするのが決まりである
ここまでで`Repository`パッケージは完成

```java:ExchangeRepositoryModule.java
@Module
public class ExchangeRepositoryModule {
    @Singleton
    @Provides
    public ExchangeRateRepository provideExchangeRateRepository(ExchangeRateRepositoryImpl exchangeRateRepository) {
        return exchangeRateRepository;
    }

    @Provides
    @Singleton
    public ExchangeRateApi provideExchangeRateApi(Retrofit retrofit) {
        return retrofit.create(ExchangeRateApi.class);
    }
}
```
Repositryを使うためのUseCaseの基底クラス`UseCase`を作成
`subscribeOn()` Main以外のスレッドで実行
`observeOn()` Mainのスレッドを指定
こうすることで例えば、バックグラウンドスレッドで処理したものをメインスレッドで受け取る事が可能になる

```java:UseCase.java
public abstract class UseCase<T> {
    protected final Executor threadExecutor;
    protected final PostExecutionThread postExecutionThread;

    protected UseCase(Executor threadExecutor, PostExecutionThread postExecutionThread) {
        this.threadExecutor = threadExecutor;
        this.postExecutionThread = postExecutionThread;
    }

    protected Observable<T> bindUIThread(Observable<T> useCaseeObservable) {
        return useCaseeObservable
                .subscribeOn(Schedulers.from(threadExecutor))
                .observeOn(postExecutionThread.getScheduler());
    }
}
```

Schedulerのinterfaceを作成
rxじゃないSchedulerをimportしないように注意

```java:PostExecutionThread.java
public interface PostExecutionThread {
    Scheduler getScheduler();
}
```

## UseCase

`exchange`パッケージ内に`usecase`パッケージ作成
`usecase`にはビジネスロジックを入れる(ex.お気に入り、ログイン、)
`ExchangeRateRepository`を使うためのユースケース`GetExchangeRate`を追加
ここまでで`data`パッケージ内はすべてが完成

```java:GetExchangeRate.java
public class GetExchangeRate extends UseCase<ExchangeRateResponse> {

    private final ExchangeRateRepository mExchangeRateRepository;

    @Inject
    protected GetExchangeRate(Executor threadExecutor, PostExecutionThread postExecutionThread, ExchangeRateRepository exchangeRateRepository) {
        super(threadExecutor, postExecutionThread);
        this.mExchangeRateRepository = exchangeRateRepository;
    }

    public Observable<ExchangeRateResponse> execute(String base, String symbols) {
        return bindUIThread(mExchangeRateRepository.getExchangeRate(base, symbols));
    }
}
```


通信結果をメインスレッドで受け取るように`di`パッケージを作成し`UIThread`を作成

```java:UIThread.java
@Singleton
/* package */ class UIThread implements PostExecutionThread {

    @Inject
    public UIThread() {
    }

    @Override
    public Scheduler getScheduler() {
        // RxのメソッドでMainスレッドを取得することができる
        return AndroidSchedulers.mainThread();
    }
}
```

`Executor`を継承した`JobExecutor`を作成
ココのExecutorクラスをそのままコピぺさせて頂きました
https://github.com/android10/Android-CleanArchitecture/blob/master/data/src/main/java/com/fernandocejas/android10/sample/data/executor/JobExecutor.java


```java:JobExecutor.java
@Singleton
public class JobExecutor implements Executor {

    private static final int INITIAL_POOL_SIZE = 3;
    private static final int MAX_POOL_SIZE = 5;

    // Sets the amount of time an idle thread waits before terminating
    private static final int KEEP_ALIVE_TIME = 10;

    // Sets the Time Unit to seconds
    private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;

    private final BlockingQueue<Runnable> workQueue;

    private final ThreadPoolExecutor threadPoolExecutor;

    private final ThreadFactory threadFactory;

    @Inject
    public JobExecutor() {
        this.workQueue = new LinkedBlockingQueue<>();
        this.threadFactory = new JobThreadFactory();
        this.threadPoolExecutor = new ThreadPoolExecutor(INITIAL_POOL_SIZE, MAX_POOL_SIZE,
                KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, this.workQueue, this.threadFactory);
    }

    @Override public void execute(Runnable runnable) {
        if (runnable == null) {
            throw new IllegalArgumentException("Runnable to execute cannot be null");
        }
        this.threadPoolExecutor.execute(runnable);
    }

    private static class JobThreadFactory implements ThreadFactory {
        private static final String THREAD_NAME = "android_";
        private int counter = 0;

        @Override public Thread newThread(Runnable runnable) {
            return new Thread(runnable, THREAD_NAME + counter++);
        }
    }
}
```

`ExchangeRepositoryModule`を提供する`RepositoryModule`を作成

```java:RepositoryModule.java
@Module(includes = {ExchangeRepositoryModule.class})
public class RepositoryModule {

    @Provides
    @Singleton
    public PostExecutionThread providePostExecutionThread(UIThread uiThread) {
        return uiThread;
    }

    @Provides
    @Singleton
    public Executor provideExecutor(JobExecutor jobExecutor) {
        return jobExecutor;
    }
}
```

`RepositoryModule`を使用して通信する`ApiModule`を作成

```java:ApiModule.java
@Module(includes = {RepositoryModule.class})
public class ApiModule {

    @Provides
    @Singleton
    Gson provideGson() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        return gsonBuilder.create();
    }

    @Provides
    @Singleton
    OkHttpClient provideOkhttpClient() {
        OkHttpClient.Builder client = new OkHttpClient.Builder();
        client.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
        return client.build();
    }

    @Provides
    @Singleton
    Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
        return new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                // ベースのURLの設定
                .baseUrl("http://api.fixer.io")
                .client(okHttpClient)
                .build();
    }
}
```

モジュールとinterfaceを紐付ける`AppComponent`を作成

```java:AppComponent.java
@Singleton
@Component(modules = {ApiModule.class})
public interface AppComponent {

    ExchangeRateRepository exchangeRateRepository();

    Executor executor();

    PostExecutionThread postExecutionThread();
}
```

Injectする際に使用するカスタムアノテーションを作成
スコープをActivityが生きてるまでとする
命名は特に決まっていないが、ググると`PerActivity`等を推奨していたので、その通りに作成
`di`パッケージは完成

```Java:PerActivity.java
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {}
```

ここでもう一度、実行もしくはrebuildする
(DaggerAppComponentが生成される)
`Application`クラスを継承した`AppApplication`を作成
`Activity`や`Fragment`から`getAppComponent()`で`AppComponent`を取得し、`inject()`する

```java:AppApplication.java
public class AppApplication extends Application {

    private AppComponent mAppComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        mAppComponent = DaggerAppComponent.builder().build();
    }

    public AppComponent getAppComponent() {
        return mAppComponent;
    }
}
```

Applicationクラスを使うので、AndroidManifest.xmlに  `android:name=".AppApplication"` を一行追記

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kojimation.com.daggerretrofitrxandorid">

    <!-- 追加 -->
    <uses-permission android:name="android.permission.INTERNET" />
	
	<!-- このタグの中に追加 -->
    <application
        android:name=".AppApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
```

`binding`パッケージを作成し、DataBindingで値の変更通知を受け取れるように`OnFieldChangedCallback`を追加
`Activity`や`Fragment`から使う
変数の型が例えばListの場合は`OnListChangedCallback`を継承したり、型がbooleanの場合は`OnBooleanChangedCallback`使ったりして、臨機応変に対応する

```java:OnFieldChangedCallback.java
public abstract class OnFieldChangedCallback<T> extends ObservableField.OnPropertyChangedCallback {
    @Override
    public void onPropertyChanged(Observable sender, int propertyId) {
        onChanged(((ObservableField<T>)sender).get());
    }

    protected abstract void onChanged(T field);
}
```

## View
ここからView側を作成していく

`ui`パッケージを作成し、`exchange`パッケージを中に作成(今は`exchange`だけなので、`ui`のパッケージはいらないですが..)
Viewのインターフェースとして`ExchangeRateView`を作成

```java:ExchangeRateView.java
/* package */  interface ExchangeRateView {
    void bindExchangeRate(ExchangeRateResponse exchangeRateResponse);
}

```

## ViewModel

Viewの状態はすべて、このViewModelが管理する(変数を所持したりなど)
変数が変更された場合はビューへの通知はDataBindingがやってくれる

`ExchangeRateView `をimplementsした`ExchangeRateViewModel`を作成

```java:ExchangeRateViewModel.java
@PerActivity
/* package */ class ExchangeRateViewModel implements ExchangeRateView {

    private final ObservableField<ExchangeRateResponse> mExchangeRateResponse = new ObservableField<>();

    @Inject
    public ExchangeRateViewModel() {
    }

    @Override
    public void bindExchangeRate(ExchangeRateResponse exchangeRateResponse) {
        mExchangeRateResponse.set(exchangeRateResponse);
    }

    // observe

    public void onExchangeRateChanged(OnFieldChangedCallback<ExchangeRateResponse> callback) {
        addCallback(mExchangeRateResponse, callback);
    }

    protected final <T> void addCallback(final ObservableField<T> observable, final OnFieldChangedCallback<T> callback) {
        observable.addOnPropertyChangedCallback(callback);
    }
}
```

## Presenter

PresenterにView側のロジックを集約し、
View側はPresenterを通して、Data側のUseCaseにアクセスする
Presenter役として、`ExchangeRatePresenter`を作成
Viewのセットや、Subscriptionの登録など、上位クラスとしてBaseViewなど作ってまとめた方がいいが、
一旦これだけなので、ここに書いておく

```java:ExchangeRatePresenter.java
@PerActivity
/* package */ class ExchangeRatePresenter {
    private final CompositeSubscription mSubscriptions = new CompositeSubscription();
    private final GetExchangeRate mGetExchangeRate;
    private ExchangeRateView mView;

    @Inject
    public ExchangeRatePresenter(GetExchangeRate getExchangeRate) {
        this.mGetExchangeRate = getExchangeRate;
    }

    public void getExchangeRate() {
    	 // USDを基準にJPYのレートを取得
        addSubscription(mGetExchangeRate.execute("USD", "JPY")
                .subscribe(
                        new Observer<ExchangeRateResponse>() {
                            @Override
                            public void onCompleted() {

                            }

                            @Override
                            public void onError(Throwable e) {
                                Log.d("通信 -> ", "失敗" + e.toString());
                            }

                            @Override
                            public void onNext(ExchangeRateResponse exchangeRateResponse) {
                                mView.bindExchangeRate(exchangeRateResponse);
                            }
                        }
                ));
    }

    public void setView(ExchangeRateView view) {
        mView = view;
    }

    private void addSubscription(Subscription s) {
        mSubscriptions.add(s);
    }

    protected void unsubscribe() {
        mSubscriptions.clear();
    }
}
```

`ExchangeRateComponent`を作成

```java:ExchangeRateComponent.java
@PerActivity
@Component(dependencies = {AppComponent.class})
/* package */ interface ExchangeRateComponent {
    void inject(ExchangeRateActivity activity);
}

```

ここでもう一度、実行もしくはrebuildする
(DaggerExchangeRateComponentが生成される)
`java:ExchangeRateActivity`作成

```java:ExchangeRateActivity
public class ExchangeRateActivity extends AppCompatActivity {

    @Inject
    ExchangeRatePresenter mPresenter;
    @Inject
    ExchangeRateViewModel mModel;

    private TextView mTextView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_exchange_rate);

        mTextView = (TextView) findViewById(R.id.txt_jpy);

        DaggerExchangeRateComponent.builder()
                .appComponent(((AppApplication) getApplicationContext()).getAppComponent())
                .build()
                .inject(this);
                
        // Presenter/ViewModelの設定
        mPresenter.setView(mModel);
        
        mModel.onExchangeRateChanged(new OnExchangeRateChanged());
        mPresenter.getExchangeRate();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.unsubscribe();
    }

    private class OnExchangeRateChanged extends OnFieldChangedCallback<ExchangeRateResponse> {
        @Override
        protected void onChanged(ExchangeRateResponse exchangeRateResponse) {
            mTextView.setText("JPY: " + String.valueOf(exchangeRateResponse.getRates().getJPY()));
        }
    }
}
```

TextViewを追加

```xml:activity_exchange_rate.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_exchange_rate"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <!-- 追加 -->
    <TextView
        android:id="@+id/txt_jpy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>
```

実行してみる

![スクリーンショット 2016-11-24 午後11.31.10.png](https://qiita-image-store.s3.amazonaws.com/0/33553/21fd96ad-2332-ab3f-3357-89002e0564f1.png "スクリーンショット 2016-11-24 午後11.31.10.png")

Githubにサンプルを置いておきます
https://github.com/MuuKojima/CleanArchitectureMVVMDataBinding