【Android】Retrofit2のCall.enqueue()は非同期で処理されるの巻

# はじめに

皆さん、ごきげんよう！れぶです！

先日`Retrofit2`ライブラリを使って、API通信した結果を`RecyclerView`でリスト表示しました。その際に自身が悩んだ箇所が1点あったので、今回の記事で原因と解決方法を記述していきます。

自身のためでもありますが、自分と同じ悩みを持つ方々にも刺さる記事になれば幸いです。
それでは、参りましょう！！

# 開発環境

* MacBook Air
* Android Studio Bumblebee | 2021.1.1 Patch 1
* Java 8
* compileSdkVersion 31
* minSdkVersion 21

# 動作イメージ

完成イメージは以下になります。
![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456250/be906014-7488-9013-7a48-343e36a93976.gif)
入力された郵便番号をもとにAPI通信（[zipcloud](http://zipcloud.ibsnet.co.jp/doc/api)さんのAPIを利用）を行い、町域名をRecyclerViewでリスト表示するサンプルプログラムになります。

# 悩んだ箇所

`Searchview`(アプリ一番上の入力欄)に7桁の郵便番号を入力しEnterキーを押しても、一回でリアルタイムに反映されない現象が起きました。

具体的には以下の流れになります。

⑴　実在する7桁の郵便番号を入力しEnterキーを押す▶️反映されない
⑵　再度、実在する7桁の郵便番号を入力しEnterキーを押す▶️⑴で入力した郵便番号の町域名が反映される

つまり、**見た目的に1回分ずれてRecyclerviewにデータが反映されてしまいます。**

# 原因

色々考えた結果、以下の`getAddress`メソッド内に問題があることを発見しました。
`CityRepository.java`（一部省略）ではAPI通信を行い、取得したデータを`getAddress`メソッドで返しています。

```CityRepository.java
private ApiInterface service;
private List<Address> address;

public List<Address> getAddress(String zipcode) {

        //APIを呼び出し
        service.getCity(zipcode).enqueue(new Callback() {

            //リクエスト成功時
            @Override
            public void onResponse(Call call, Response response) {

                Log.i("message2", "Successed to request");

                //通信結果を受け取る
                City city = (City) response.body();
                address = city.getResults();
            }

            @Override
            public void onFailure(Call call, Throwable t) {

                Log.i("message2", "error:" + t);
            }
        });

        Log.i("message2", "Successed to request2");
        return address;
    }
```

ログを見ると、「Successed to request2」→「Successed to request」の順で出力してしまいます。（想定してた処理実行の順番は逆）。

つまり、**`Call.enqueue()`が非同期で処理を行うメソッド**であることが、今回の現象の根本的な原因でした。非同期処理は上から順には処理されません。上記コードでは`Call.enqueue()`よりも先にreturn文が実行されます。

1回分ずれて表示されてしまう流れは以下になります。

❶　ユーザーアクション（1回目の郵便番号の入力）
❷　前回のaddress（初回はなし）をreturn文で返す
❸　❷をView側に反映する
❹　Call.enqueue()が実行され、addressに値がセットされる
❺　ユーザーアクション（2回目の郵便番号の入力）
❻　❹をreturn文で返す
❼　❻をView側に反映する

# 解決方法

結論、`Call.execute()`を使うことです。

`Call.enqueue()`が**非同期**で処理されてしまうのであれば、`Call.execute()`で**同期**処理を行えば処理実行の順番ずれは起きないという考えです。すると、ずれがなくRecyclerviewにデータが反映されました。

```CityRepository.java
private ApiInterface service;
private static final int NUMBER_OF_THREADS = 4;
public static final ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS);

public List<Address> getAddress(String zipcode)  {
        try {
            //通信結果を受け取る
            Response<City> response = service.getCity(zipcode).execute();

            if(response.isSuccessful()) {

                City city = response.body();
                List<Address> address = city.getResults();
                return address;

            } else {
                Log.d("message2", "error_code" + response.code());
            }

        } catch (IOException e) {
            Log.i("message2", "error:" + e.getMessage());
        }
        return null;
    }
```

処理の流れは以下になります。
❶　ユーザーアクション（郵便番号の入力）
❷　Call.execute()が実行され、結果を受け取る
❸　❷をreturn文で返す
❸　❸をView側に反映する

上記コードでは、同期処理なので勿論上から処理されます。

ただし、`Call.execute()`の実行はメインスレッドではできません。**メインスレッドとは別にスレッドを立てて、その中で実行**します。以下のコードでは、`ViewModel`クラス内で`executorService.execute()`を使って非同期的に実行しています。（今回は、`Repository（Model）`で取得した値を`ViewModel`経由で`View`に反映）

※因みに`Call.enqueue()`は元々非同期メソッドであるため、別にスレッドを立てて実行する必要はありません。

```CityViewModel.java
public class CityViewModel extends AndroidViewModel {

    private MutableLiveData<List<Address>> address = new MutableLiveData<List<Address>>();
    private CityRepository repository;

    public CityViewModel(@NonNull Application application) {
        super(application);
        repository = new CityRepository();
    }

    public LiveData<List<Address>> getAddress() { return address; }

    //取得したデータを設定
    //非同期で実行
    public void searchAddress(String zipcode) {

        CityRepository.executorService.execute(() -> {
            address.postValue(repository.getAddress(zipcode));
        });
    }
}
```

# サンプルコード
とここで、自身が書いたコードを[GitHub](https://github.com/seiyaleb/MVVMTrialApp2)に載っけているので、参考の一つにしてみてください。
# おわりに
今回は、Retrofit2を使用した際に自身が躓いた箇所の原因と対処法を記しました。

* **Call.enqueue()→非同期メソッド、Call.execute()→同期メソッド**
* **Call.execute()はメインスレッドでは実行できない**
* **非同期処理は上から順に処理されない**

この辺りを注意して実装していくのが良いのかなと感じました。この記事が参考になると光栄です。以上です。ありがとうございました！

# 参考サイト
* [【Android】MVVMで、Retrofit2 + Kotlin CoroutinesでHttp通信でqiitaの記事検索するやつ作った](https://qiita.com/yonce/items/eb4ba7cf38e52be6e16f)
* [Let's はじめてのRetrofit for Android in Kotlin（サンプルコード付き）](https://qiita.com/naoi/items/5036adc8d33638911deb)
* [Retrofit2 + Java でAPIコールを実装する](http://kenjinakamu.com/archives/26)
* [【Android】Recyclerviewに、Retrofit2で取得したデータをリアルタイムで反映させたい](https://teratail.com/questions/el5macqhz6ziya)
* [同期・非同期処理の動きについて簡単に理解する](https://komari.co.jp/blog/10223/)