最近のAndroidネイティブ開発まとめ(2017/4版)

2017/04/23更新

* buildConfigFieldについて追記
* ライブラリ情報更新
* 通信処理（RxJava2+RxAndroid+Retrofit2）について追記

# はじめに
最近Androidネイティブ開発で消耗しています。
モダンなAndroidネイティブ開発に関してまとめてみました。

# 基本

## AndroidManifest.xml
主に次の項目を設定する

* アプリの権限の設定
* ApplicationおよびActivityの初期設定
* BroadcastReceiverの設定

アプリのパーミッション（権限）を追加したい場合はapplicationタグの前にuser-permissonタグを追記

```xml
<uses-permission android:name="android.permission.INTERNET" />
```

上記は一番よく使う通信を許可するためのパーミッション
name属性値に許可したいパーミッションを指定します。

## build.gradle
アプリケーションの次のビルド設定を記載する

* アプリバージョン
* ビルド切り替え設定(ProductFlavor)
* ライブラリ管理
* Proguard設定
* 設定値

特にライブラリを追加するときにはdependencies以下に追記

```
dependencies {
    compile 'ライブラリ'
}
```

## Proguard

Androidパッケージファイルを難読化するためのツール
GooglePlayストアにアップロードされているapkをデコンパイルされても解析されないようにする

bundle.gradleにてProguardファイルを設定
product-project.txtにProguardの設定をします。
（特定のファイルは難読化しないなど）

```
 buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-project.txt'
            ##proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' ←もとから設定してあった
        }
 }
```

サーバ接続情報など固定値の設定パラメータは
buildConfigFieldを利用するとビルド時に固定値が埋め込まれます。
さらにbuildTypes以下のdebug、release内に書くことでデバッグ時、リリース時に固定値を切り替えることができます。

```build.gradle

    defaultConfig {        
        // 固定値パラメータ(共通)
        buildConfigField 'String', 'END_POINT', '"https://randomuser.me/"'
    }
    buildTypes {
        debug {
            // debug時のFabricなどの認証情報
        　  buildConfigField 'String', 'KEY', '"xxxx-xxxx-xxxx-xxxx"'
        }
        release {
            // release時のFabricなどの認証情報
        　  buildConfigField 'String', 'KEY', '"yyyy-yyyy-yyyy-yyyy"'
        }
    }
```

Javaコード側での固定値の取得は次のようになります。

```test.java
String serverUrl = BuildConfig.END_POINT;
```

Proguardには次のように難読化除外のパッケージ指定を主にする。
<font color="red">（指定しない場合は難読化される）</font>

```
-keep class com.hannesdorfmann.fragmentargs.** { *; }
-keepnames class jp.misyobun.lib.versionupdater.** { *; }
```

* -keep:クラスを難読化しない
* -keepnames：変数を難読化しない

keepnamesに関してはきちんとやっておかないとGSONのモデルマッピングに失敗したりとハマります。


## アプリアイコン・アプリ名

アプリ名はstrings.xmlにてapp_name項目で指定

```xml
 <string name="app_name">アプリ名</string>
```

アプリアイコンはAndroidManifest.xmlの
applicationタグのandroid:iconに設定

```xml
 <application
     android:icon="@mipmap/ic_launcher"
```

## テーマ

アプリ全体のデザインを変えるためにはテーマがある
Android一般のHoloやマテリアルデザイン用のMaterialなどがある
AppCompatテーマはAndroid OS5(Lolipop)以前にマテリアルデザインを
割り当てるためのテーマで、アクションバーが不要な場合(後で自作したい場合など)は
次のような設定をstyles.xmlに設定する

```xml
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>
```

colorPrimary:アクションバーの背景色
colorPrimaryDark:ステータスバーの背景色
navigationBarColor:ナビゲーションバーの背景色
windowBackground:アプリケーションボディの背景色
textColorPrimary:アクションバーのタイトル文字色
colorAccent:コントロールの基本色

![ThemeColors.png](https://qiita-image-store.s3.amazonaws.com/0/55077/80d2eeb8-f79d-0b15-00eb-3433330a820b.png)

AndroidManifest.xmlのapplicationタグにテーマが設定されている

```xml
<application
        android:theme="@style/AppTheme">
```

## ライフサイクル

### Activityのライフサイクル

![7264706d-ccd7-b57a-3eca-cdf4c6675a3d.png](https://qiita-image-store.s3.amazonaws.com/0/55077/0ae7bc79-72c3-a756-2235-df7e77d43498.png)

### Fragmentのライフサイクル

![a23fa119-b41b-f7a2-b940-5ca1a79157e4.png](https://qiita-image-store.s3.amazonaws.com/0/55077/8cfb3253-d901-8830-c78f-9cba3abe20dc.png)


## 画面設計
AndroidではFragment移動時にはBackStackという領域にFragmentを保存する。
遷移元から２方向以上で循環できるような構造があると厄介なことになる。
TabとかBottom Navigationとかだと一つのActivity内で複数のFragmentを保持するため、この問題が起こりやすい。（タブ内のFragmentの2階層以上深い階層にTabやBottom Navigationが存在する場合）
上記の場合、BackStackをタブ別に持つなどの実装になるため、実装は非常に複雑。
そもそもAndroidの画面遷移構造に適していない。

ちなみにBackボタン（BackStack）とUpボタンの挙動は次のようなガイドラインに沿うのが推奨されています。
[Back ボタンと Up ボタンを使用したナビゲーション](https://developer.android.com/design/patterns/navigation.html?hl=ja)

![navigation_between_siblings_gmail.png](https://qiita-image-store.s3.amazonaws.com/0/55077/a813943e-5a18-c9e8-3b7b-3c4fd1cc44c6.png)


# 設計・ライブラリ

## Context、MultiDex、アプリケーション共通

### Context
Contextはアプリケーションの各所で必要になる
ApplicationのContext（getApplicationContext()）やActivityのContext(getContext())などがある
正確な違いは不透明だが
ActivityのContextを利用するとライフサイクル終了時に破棄されるため
タイミングによってはNull Pointer Exception(以下NPE)になる
ApplicationのContextを参照するようにしたほうが安全
また、利用するContextによって作られる標準のDialogやListViewコントロールのデザインはAndroidManifest.xmlのThemeに引っ張られる模様
[Android:引数はthisか？getApplicationContextか？ActivityとApplicationの違い](http://yuki312.blogspot.jp/2012/02/thisgetapplicationcontextactivityapplic.html)

### Multidex
Androidではdexと呼ばれる方式でパッケージ生成を行う
ただし、1パッケージあたり65kが上限という問題があるため
メソッド数が増えてくると引っかかる可能性が出てくる
MultidexApplicationクラスを継承し、設定することで回避できる

### アプリケーション共通

MultiDexApplicationクラスを利用するためには
build.gradleのdepandenciesにmultidexライブラリを追加

```
dependencies {
    compile 'com.android.support:multidex:1.0.0'
}
```

MultiDexApplicationクラスを拡張したMainAppliationクラスを作成する

```java
public class MainApplication extends MultiDexApplication {

    private static Context context;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

    // アプリContext取得
    public static Context getContext(){
        return context;
    }

    @Override
    public void onCreate(){
        super.onCreate();
        context = getApplicationContext();
    }
}
```

上記自作MainApplicationクラスを呼び出すには
AndroidManifest.xmlに自作MainApplicationクラスを指定

```xml
 <application
        android:name=".MainApplication"
```




## ラムダ式
retrolambaを使うと利用できる。
JDK8を使うため、
OracleのサイトからJDK 8をダウンロードする

```
buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'me.tatarka:gradle-retrolambda:3.4.0'
    }
}

// Required because retrolambda is on maven central
repositories {
    mavenCentral()
}

apply plugin: 'me.tatarka.retrolambda'

android {
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}
```

### ラムダ式導入前

```java
Handler handler = new Handler();
handler.post(new Runnable() {
       @Override
       public void run() {
                
       }
});
```

### ラムダ式導入後
抽象メソッドが一つの場合はメソッド名、引数の表記を省略できる
ネストも減り、大分すっきりします。

```java
Handler handler = new Handler();
handler.post(() -> {
    // 処理
});
```


## 通信

OkHttp3+Retrofit2+Gsonが良いです。
GETに関して良い記事が下に
[Retrofit2使ってみる](http://qiita.com/hymmr/items/cbb1013617cd43b8c7c4)

OkHttp3+Retrofit2+Gsonを導入するには次のようにbuild.gradleに記述します。

```build.gradle
dependencies{
    // okhttp3+retrofit2+gson
    compile 'com.squareup.okhttp3:okhttp:3.3.1'
    compile 'com.squareup.retrofit2:retrofit:2.2.0'
    compile 'com.squareup.retrofit2:converter-gson:2.2.0'
    compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
    // GSON
    compile 'com.google.code.gson:gson:2.6.2'
}
```

POSTのmultipart-formでのファイルアップロードに関してあんまり書かれてなかったりするので追記しておきます。
インタフェースにMultiPartアノテーションをつけます。
パラメータはPartアノテーションをつけます。
ファイルのパラメータはフォーマットに注意です。
name.jpgが送信ファイル名になります。

```java
public interface Api {

        @Multipart
        @POST("/api/files.upload")
        Call<Void> uploadFile(@Part("param") RequestBody param,
                              @Part("file\"; filename=name.jpg") RequestBody file // file
　　　　　　　　　　　　　　　　);

}
```

次のように呼び出し

```java
// インタフェース生成
Api api = new Retrofit.Builder()
                .baseUrl("http://xxx")
                .build()
                .create(Api.class);

// 画像ファイル取得
File file = new File("画像パス");

// RequestBodyで送信パラメータ作成
// MediaTypeでMIME指定
Call<Void> request = api.uploadFile(RequestBody.create(MediaType.parse("plain/text"), "パラメータ"),
                RequestBody.create(MediaType.parse("image/jpeg"), file));

request.enqueue(new Callback<Void>() {
            @Override
            public void onResponse(Call<Void> call, Response<Void> response) {
                Log.d("SendRetrofit",response.toString());

            }

            @Override
            public void onFailure(Call<Void> call, Throwable t) {
                Log.e("ErrorRetrofit",t.toString());

            }
});

```

最近では、RxJava2+RxAndroidと組み合わせることで
複数の通信処理（非同期処理）を順次的に処理させる記述が楽になります。
さらに後述のData Bindingsと組み合わせるとMVVMでの実装が可能になります。
参考：[Handling API calls using Retrofit 2 and RxJava 2](https://medium.com/3xplore/handling-api-calls-using-retrofit-2-and-rxjava-2-1871c891b6ae)

RxJava2、RxAndroidを導入するには次のようにします。

```build.gradle
dependencies{
    // RxJava2+RxAndroid
    compile "io.reactivex.rxjava2:rxjava:2.0.9"
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    // Retrofit2のRxJava2プラグイン
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
}
```

ランダムにユーザを生成するサービスのAPIからデータ取得してみます。
[RANDOM USER GENERATOR](https://randomuser.me/)

Retrofit2用のインタフェースを作成します。
Callではなく、Observableで戻り値を定義していることに注意してください

```UserApiClient.java
interface UserApiClient {
   // ランダムユーザ取得API (https://randomuser.me/api)
   @GET("api")
   Observable<JsonObject> getUser();
}
```

OkHttp3、Gson、Retrofit2のインスタンスを生成し、
API呼び出し＆データ取得部分に関しては次のようになります。
Retrofit2生成までは生成処理が重いため、一度生成したら使いまわした方が無難です。

```User.java
public void getUser(){
   // OkHttp3 Client生成
   OkHttpClient.Builder builder = new OkHttpClient.Builder().addInterceptor(
                new Interceptor() {
                    @Override
                    public Response intercept(Interceptor.Chain chain) throws IOException {
                        Request original = chain.request();

                        // リクエスト共通パラメータ追加
                        /*
                        HttpUrl url = original.url().newBuilder()
                                .addQueryParameter("common_param","hogehoge").build();

                        Request request = original.newBuilder().url(url).build();
                        return chain.proceed(request);
                        */
                        return chain.proceed(original);
                    }
                });
        // 読み込みタイムアウト
        builder.readTimeout(3000, TimeUnit.MILLISECONDS);
        // 書き込みタイムアウト
        builder.writeTimeout(3000, TimeUnit.MILLISECONDS);
        // 接続タイムアウト
        builder.connectTimeout(3000,TimeUnit.MILLISECONDS);
   OkHttpClient okHttpClient = builder.build();
   // Gson生成
   Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
   // Retrofit2生成
   Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://randomuser.me")
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
   // Reterofit2のAPIインタフェースを生成し、通信処理
   UserApiClient userApiClient = retrofit.create(UserApiClient.class);
   userApiClient.getUser() // APIコール
                .subscribeOn(Schedulers.computation())
                .observeOn(AndroidSchedulers.mainThread()) // UIスレッドに渡す
                .subscribe(response -> {
                    Log.d("param",response.toString()); // 成功
                },throwable -> {
                    Log.d("error:",throwable.toString()); // エラー
                });
}
```

retrolambdaを使わない場合（Java8を入れたくない場合）は
下記リンクの関数型インタフェースを参考にしてください
[RxJava2での変更点](http://qiita.com/kubode/items/aebef4593e42a3b367be)

上記の例だと次のように書き直せます

```User.java
userApiClient.getUser()
                .subscribeOn(Schedulers.computation())
                .observeOn(AndroidSchedulers.mainThread()) // UIスレッドに渡す
                .subscribe(new Consumer<JsonObject>() {
                    @Override
                    public void accept(@NonNull JsonObject response) throws Exception {
                        Log.d("param",response.toString());
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        Log.d("error:",throwable.toString());
                    }
                });
```

## NPE対策

RxJava2,RxAndroidのOptinalを使うとnullかもしれないオブジェクトの処理を明示的にできる
build.gradleにライブラリを追加

```
dependencies{
    compile "io.reactivex.rxjava2:rxjava:2.0.9"
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
}
```

### Optinal導入前

```java
String strNullable = "456";
int result = 123;
if(strNullable != null ){
    result = Integer.valueOf(strNullable);
}
```

### Optinal導入後
Optinalを使った記述だと
Nullの場合の記述を明示的にできるし
煩雑なif文やfor文が消え去る

```java
String strNullable = "456";
int result = Optional.ofNullable(strNullable)
             .map(Integer::valueOf)
             .orElse(123); // if null
```



## Fragment引数のDI
FragmentArgsを使うとIntentの引数をアノテーションでinjectできるようになる。
build.gradleにてインストール

```build.gradle

dependencies{
    compile 'com.hannesdorfmann.fragmentargs:annotation:3.0.2'
    annotationProcessor 'com.hannesdorfmann.fragmentargs:processor:3.0.2'
    compile 'com.hannesdorfmann.fragmentargs:bundler-parceler:3.0.2'
}

```

MainFragment.javaを作成します。
引数を受け取りたいFragmentにFragmentWithArgsアノテーションをつけます
受け取る引数にはArgアノテーションをつけます。
onCreateにて「Fragmentクラス名Builder」.injectArguments(this)を呼び出します。
required = falseにすることでオプションパラメータにもできます。

```java
@FragmentWithArgs
public class MainFragment extends Fragment{

    @Arg(required = false)
    String age;
    @Arg
    String name;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FragmentArgs.inject(this); // read @Arg fields
    }

    // Fragmentで表示するViewを作成するメソッド
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate(R.layout.fragment_main, container, false);

        TextView nameText = (TextView) view.findViewById(R.id.name);
        nameText.setText(name);
        TextView ageText = (TextView) view.findViewById(R.id.age);
        ageText.setText(age);

        return view;
    }

}
```

MainActivity.javaからは次のように呼び出し
MainFragmentBuilderはビルド時に自動生成されます。

```java
public class MainActivity extends AppCompatActivity {

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

        // ビルダーのコンストラクタ引数の順番は変数のアルファベット順になるので注意
        MainFragment mainFragment = new MainFragmentBuilder("名前")
                .age("年齢") // オプションパラメータ
                .build();
        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.container, mainFragment,MainFragment.class.getName())
                .commit();

    }
}
```


### Parcelerでのモデルクラスシリアライズ
クラスオブジェクトを引数に渡す際はParcelインタフェース実装などでシリアライズが必要です。
Parcelerライブラリのアノテーションを使うとParcelインタフェース実装に必要な煩雑な記述が不要になります。
Parcelerをインストールします。
build.gradleに追記

```

dependencies{
    // Parceler
    compile 'org.parceler:parceler-api:1.1.6'
    annotationProcessor 'org.parceler:parceler:1.1.6'
}

```

Userモデルを定義してみます。
次のParcel化するモデルに関してはParcelアノテーションをつけるだけで大丈夫です。

```java
import org.parceler.Parcel;

@Parcel
public class User {

    String name;
    int age;

    public User() {}

    public User(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public String getName() { return name; }

    public int getAge() { return age; }
}
```

Fragment側ではParcelオブジェクトに関しては
bundler = ParcelerArgsBundler.classを設定します。

```java
@FragmentWithArgs
public class MainFragment extends Fragment{

    @Arg( bundler = ParcelerArgsBundler.class )
    User user;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FragmentArgs.inject(this); // read @Arg fields
    }

    // Fragmentで表示するViewを作成するメソッド
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate(R.layout.fragment_main, container, false);

        TextView nameText = (TextView) view.findViewById(R.id.name);
        nameText.setText(user.getName());
        TextView ageText = (TextView) view.findViewById(R.id.age);
        ageText.setText(String.format("%dさい",user.getAge()));

        return view;
    }
}
```

MainActivityではUserモデルを生成して引数に渡します。

```java
public class MainActivity extends AppCompatActivity {

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

        // 引数の順番は変数のアルファベット順になるので注意
        MainFragment mainFragment = new MainFragmentBuilder(new User(15,"名前"))
                .build();
        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.container, mainFragment,MainFragment.class.getName())
                .commit();

    }
}
```


## ライフサイクル時の変数保存
ライフサイクルで厄介なのがActivityはすぐ死ぬということ
特にActivityの回転を許している場合（デフォルト）
回転するだけでActivityが死んで再表示時にonCreateから再生成される。
この場合、何が厄介かというとEditText入力中に回転などされると
onCreateにてActivityやFragmentのデータが初期化されてしまう。
これを防ぐために、ActivityのonSaveInstanceStateメソッドにて保存しておきたい変数はBundleに保存する。
Icepickというライブラリを使えばもっと楽になる。
build.gradleに追記

```
dependencies{
    // Icepick
    compile 'frankiesardo:icepick:3.2.0'
    provided 'frankiesardo:icepick-processor:3.2.0'
}
```

実際にActivityで状態保存して再生成時に取得するには次のようにする。

```java
@State(UserBundler.class) User user;

@Override
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Icepick.restoreInstanceState(this, savedInstanceState);
        setContentView(R.layout.activity_main);

        if(user == null) {
            user = new User(15, "名前");
        }
        
}

@Override
protected void onSaveInstanceState(Bundle outState) {
     super.onSaveInstanceState(outState);
     Icepick.saveInstanceState(this, outState);
}
```

Parcelerで直列化したUserモデルをBundlerにマッピングするための
UserBundlerクラスは次のように定義する

```java
import android.os.Bundle;
import org.parceler.Parcels;
import icepick.Bundler;

// Icepick
public class UserBundler implements Bundler<User> {
    @Override
    public void put(String s, User example, Bundle bundle) {
        bundle.putParcelable(s, Parcels.wrap(example));
    }

    @Override
    public User get(String s, Bundle bundle) {
        return Parcels.unwrap(bundle.getParcelable(s));
    }
}
```

## クラス変数のDI(Dependency Injection)
クラスのインスタンス生成を外部から生成できるようにする
主にアプリ内共通モデルデータをDIすると分離が楽になる
Dagger2が一番良いと思います。
aptのインストールはFragmentArgsと同じなのでそちらを参考に
build.gradleでDagger2をインストールします。

```
{
    compile 'com.google.dagger:dagger:2.9'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.9'
    provided 'javax.annotation:jsr250-api:1.0'
}
```

インジェクト元のオブジェクトにはModuleアノテーションをつけます。
ここではAppModule.javaを作成します。
インジェクトするオブジェクトを返却するメソッドにはProvidesアノテーションをつけます。

```java
import android.app.Application;
import android.content.Context;
import android.support.multidex.MultiDexApplication;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;

@Module
public class AppModule {
    private final Context appContext;

    public AppModule(Context context) {
        this.appContext = context;
    }

    @Provides
    @Singleton
    Context provideAppContext() {
        return appContext;
    }
}
```

injectする対象をメソッドの引数とするインタフェースを作成します。
Componentアノテーションを指定し、modulesにインジェクト元のモジュールを指定します。

```java
import javax.inject.Singleton;

import dagger.Component;

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {

    void inject(MainActivity mainActivity);

    void inject(MainFragment mainFragment);

}
```

Daggerのモジュールを生成します。
Daggerモジュール名でモジュールのビルダーが自動生成されます。

```java
public class MainApplication extends MultiDexApplication {

    private static AppComponent component;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        
        component = DaggerAppComponent.builder()
                .appModule(new AppModule(getApplicationContext()))
                .build();
    }

    public static AppComponent getAppComponent() {
        return component;
    }
}
```

BaseActivityを生成します。
onCreateメソッドにてinjectメソッドを呼び出します。
継承するActivityクラス側でinjectする処理をします（後述）

```java

public abstract class BaseActivity extends AppCompatActivity {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        inject(MainApplication.getAppComponent());
    }

    protected abstract void inject(AppComponent component);

}
```


MainActivity側ではBaseActivityを継承し
injectメソッドを実装します。
AppComponentモジュールにクラスのインスタンスを渡すことで
AppComponentのProvideアノテーションから返却される
appContextがInjectされ、MainActivity側でContextを利用できるようになります。
つまり、AppComponentを介してAppModule側でインスタンスを返却することで
呼び出し側でnewすることなく、共通変数として使うことができるようになります。

```java
public class MainActivity extends BaseActivity {

    @Inject
    Context appContext;

    @Override
    protected void inject(AppComponent component) {
        component.inject(this);
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.container, new MainFragment(),MainFragment.class.getName())
                .commit();

    }


}
```

Fragmentに関しても同様にBaseFragmentを作成します。

```java
public abstract class BaseFragment extends Fragment {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        inject(MainApplication.getAppComponent());
    }

    protected abstract void inject(AppComponent component);
}

```

MainFragmentはBaseFragmentを継承し、AppComponentのinjectメソッドにて
MainFragmentインスタンスを渡し、外部モジュールのデータをInjectします。

```java
public class MainFragment extends BaseFragment {

    @Inject
    Context appContext;

    @Override
    protected void inject(AppComponent component) {
        component.inject(this);
    }
}
```

## ViewのDI
Android標準のDataBinding機能を使うと自動生成されるBindingオブジェクトでレイアウトファイルのViewの取得やモデルデータのDIができます。
イベント処理もレイアウトファイルから呼び出しの紐づけができます。
参考：[Butter Knife、今までありがとう。 Data Binding、これからよろしく。](http://qiita.com/izumin5210/items/2784576d86ce6b9b51e6)

build.gradleのdataBindingをtrueに設定する。

```
android
{
    dataBinding {
        enabled = true
    }
}
```


### 従来の書き方(Activity)

Activityのレイアウトファイル(activity_main.xml)

```xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            />
</RelativeLayout>
```

MainActivity.javaはこんな感じ

```java
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button b = (Button)findViewById(R.id.button);
        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

    }
```

### DataBindingを使った書き方(Activity)
Activityレイアウトファイル内で
レイアウト全体をlayoutタグでくくります。
ソースファイル側からモデルなどのデータやオブジェクトを
dataタグでバインディング（参照）することができます。

```xml
<?xml version="1.0" encoding="utf-8"?>
<layout>

    <data>
        <variable name="handlers"
            type="com.example.daiki.androidtemplate.BaseHandlers"/>
    </data>


    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            android:onClick="@{handlers::onClick}"
            />

    </RelativeLayout>
</layout>
```

Activity側はDataBindingUtilクラスを使うことでレイアウトファイルをデータバインディングすることができます。

```java
class MainActivity extends AppCompatActivity {
  private ActivityMainBinding binding;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    binding.button.setText("ボタン"); // bindingオブジェクトにレイアウトのViewが保持されている
    // イベントオブジェクトをDI
    binding.setHandlers(new Handlers());
  }

  public interface BaseHandlers {
      void onClick(View view);
  }

  // Event
  public class Handlers implements BaseHandlers {
      public void onClick(View view) {

        if(view.getId() == R.id.button){
        }
      }
  }
}
```

OnCreateメソッドでの煩雑なviewやリソース取得処理、イベント処理がなくなりすっきりしました。

### 従来の書き方(Fragment)

Fragmentの場合、従来は次のようにonCreateViewしていました。

```java
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fancy_fragment, container, false);
    return view;
  }
```

### DataBindingを使った書き方(Fragment)

DataBindingの場合、
DataBindingUtilオブジェクトのinflateメソッドを呼び出し、getRootメソッドを返します。

```java
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_main, container, false);
       
        return binding.getRoot();
    }
```

### DataBindingとDagger2を組み合わせる場合

DataBindingと前述のDagger2は相性が悪いです。
DataBindingをしているActivityやFragment内の変数にDagger2で直接injectすると次のようなコンパイル警告がでます。

```
Generating a MembersInjector for **Fragment. Prefer to run the dagger processor over that class instead.
```

これはコンパイル順番により、
Daggerのコンパイラが自動生成するソースコードとAndroid DataBindingが自動生成するソースコードの整合性が取れなくなるためです。
ActivityやFragmentに直接injectするのではなく、インナークラスや別クラスにinjectさせることで回避することができます。
ビジネスロジックとUIも分離できるため、こちらの方が良いかもしれません。
次のようにFragmentを修正します。

```java
public class MainFragment extends BaseFragment {

    public class Component{
       @Inject
       Context appContext;
    }
    private Component component;
    
    @Override
    protected void inject(AppComponent appComponent) {
        component = new Component();
        appComponent.inject(component);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_main, container, false);

        return binding.getRoot();
    }
}
```

AppComponentを次のように修正します。

```java
import javax.inject.Singleton;

import dagger.Component;

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {

    void inject(MainActivity.Component component);

    void inject(MainFragment.Component component);

}
```





## オンライン画像取得
キャッシュもしてくれるし、利用が簡単なPiccassoが良いです
build.gradleにてライブラリダウンロード

```
dependencies{
    compile 'com.squareup.picasso:picasso:2.5.2'
}
```

ネット上から読み込みは非同期で取得してくれます。
キャッシュクリアも簡単

```java
ImageView imageView = (ImageView)findViewById(R.id.imageView);
String url = "http://i.imgur.com/DvpvklR.png";
// 画像読み込み
Picasso.with(MainApplication.getContext())
       .load(url)
       .into(imageView);
// キャッシュクリア
Picasso.with(MainApplication.getContext()).invalidate(url);
```

ネットワークから画像取得する場合は
AndroidManifest.xmlに通信許可が必要です。

```xml
<uses-permission android:name="android.permission.INTERNET"/>
```


## 横断的データの反映
ActivityやFragmentをまたがって参照されるデータを監視し、更新時に反映したい場合があります。この場合、EventBusというライブラリが便利です。
EventBusはPub/Subの仕組みでデータの変更をSubscriberに一括送信します。
![EventBus-Publish-Subscribe.png](https://qiita-image-store.s3.amazonaws.com/0/55077/71557604-80cd-4981-fb4f-bbeea293ebc4.png)

build.gradleに追加します。

```
dependencies {
      compile 'org.greenrobot:eventbus:3.0.0'
}
```

Publisherを作成します。
EventBus.getDefault().postでPublishします。

```java
public class Publisher{
    public Publisher() {
    }

    public void post(final String msg) {

        new Handler().postDelayed(() -> {
            EventBus.getDefault().post(new MessageEvent(msg));
        }, 3000);
    }

}
```

MessageEventクラスはメッセージを保持する単純なクラスです。

```java
public class MessageEvent {
    public String msg;

    public MessageEvent(String msg){
        this.msg = msg;
    }
}
```

受信側は
EventBus.getDefault().registerメソッドで自身を登録し
Subscribeアノテーションをつけたメソッドでイベントを待ち受けできるようになります。
EventBus.getDefault().unregisterメソッドで登録解除を忘れないようにします。

```java
@Override
public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
}

@Override
public void onStop() {
        EventBus.getDefault().unregister(this);
        super.onStop();
}

public class MainFragment extends Fragment {
    @Subscribe
    public void onEvent(MessageEvent event){
    }
｝
```

呼び出しは次のように行います。

```java
new Publisher().post("テスト");
```

データ取得系の通信処理等でデータ取得時に一斉にActivityやFragmentに反映するときに便利です。


## マテリアルデザイン
デザイン思想だけ投げっぱなしで実装に非常に困る箇所
マテリアルデザイン自体の仕様も完全fixしてるわけではない
Googleの公式サポートライブラリも対応しきれていないし
足りない部分はサードパーティライブラリを利用して補うしかない
FloatingButtonはAndroid4に対応しきれていないし（一部属性値が対応していない）
BottomNavigationもスクロール対応していないため外部ライブラリ使ったほうが良いです。
これに関しては中途半端なもの出してんじゃねぇとGoogleに物申したい
[Material Design？言いたい事はわかった。だがどうやって実装しろと？そんなあなたに贈るMaterial Design対応ライブラリ集](http://qiita.com/kgmyshin/items/9299d785bea654fc98a2)

* Dialog
* BottomNavigation
* Font


```
dependencies{
    // Google公式
    compile 'com.android.support:design:25.0.0'
    // Dialog
    compile 'com.afollestad.material-dialogs:core:0.9.1.0'
    // BottomNavigation
    compile 'com.roughike:bottom-bar:2.0.2'
    // Font
    compile 'uk.co.chrisjenx:calligraphy:2.2.0'
}  
```

Calligraphyに関してはこの記事が参考になります。
[AndroidでNotoフォント・Robotoフォントを使う](http://qiita.com/konifar/items/48f4f30710c9c89c5c0d)


## クラッシュログ
AndroidのOSバージョンや
デバイスハード周りの機能（動画再生、Bluetooth、カメラ）などは機種依存する
全て検証することは実質不可能
事前に対応できれば良いが、後で対応する事態はどうしてもでてくる。

デバイス周りの問題は
FabricのCrashlyticsでクラッシュログを取得できるようにする

インストール方法は下記を参考にしてください
[[Android][Fabric] Crashlytics を使って Android アプリのベータ版を配信する](http://dev.classmethod.jp/smartphone/android/android-fabric-crashlytics-beta-distribution/)


クラッシュはしなくとも機能的に起こってほしくない場合の箇所には
カスタムログを送信できるようにする

参考：[CrashLytics　カスタムレポート拡張　Android](http://aysdaysd1212.hatenablog.com/entry/2015/12/22/152840)


## メモリリーク対策
メモリリーク検出にはLeakCanaryが使える
ActivityやFragmentライフサイクルの終了時には
通信キャンセルおよび不要変数にはnullを代入してGC(ガベージコレクション)の動作を促す

```
dependencies{
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
```

ApplicationクラスのonCreateメソッドにLeakCanaryの初期化処理を追加

```java
public class MainApplication extends MultiDexApplication {

    private static AppComponent component;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(this);
    }
}
```

LeakCanaryアプリでメモリリークが検知できます。
詳細は次記事のほうがわかりやすいです。
[LeakCanaryでメモリリークを検出する](http://qiita.com/rejasupotaro/items/4a8cfe0abda2d83145dd)


## テストアプリ配信
apkがあればぶっちゃけGMail経由でもインストールできるのですが
DeployGateにapkをアップロードしてそこ経由でダウンロードしたほうがURLを教えるだけでインストールできるので楽です

* [DeployGate](https://deploygate.com)

## プラグイン
入れとくといろいろはかどります。

・[Dagger IntelliJ Plugin](https://github.com/square/dagger-intellij-plugin)