Androidアプリ開発 Retrofit エラーハンドリングを実装する

2017年01月15日(編集2017年01月15日)
このエントリーをはてなブックマークに追加

AndroidアプリでREST形式のAPIを利用する場合、Retrofitを使うと便利です。
この記事は、Retrofitでエラーハンドリングを実装する方法を記載した記事です。

環境はAndroid 7.1 (API level 25) です。

環境

  • OS X El Capitan
  • android sdk 25
  • Oracle jdk version 1.8.0_72
  • Android Studio 2.2.2

難易度

中級者向け

サンプルコード

Android-Library-Demo

Retrofit

Retrofitは、REST APIをJavaインターフェースに変更することができるライブラリです。
今回の記事で説明するエラーハンドリングの実装は、ネットワーク接続の方法を理解している必要があります。理解不足の場合、まずはこちらの記事を読んで理解してください。

図1 sample app

エラーハンドリング実装

Retrofitのエラーハンドリングの実装をします。
まず、サービスのコールバックインターフェースを作成します。

{project_folder}/api/ServiceCallback.java
public interface ServiceCallback<T> {
    /**
     * Called for [200, 300) responses.
     */
    void success(Response<T> response);

    /**
     * Called for 401 responses.
     */
    void unauthenticated(Response<?> response);

    /**
     * Called for [400, 500) responses, except 401.
     */
    void clientError(Response<?> response);

    /**
     * Called for [500, 600) response.
     */
    void serverError(Response<?> response);

    /**
     * Called for network errors while making the call.
     */
    void networkError(IOException e);

    /**
     * Called for unexpected errors while making the call.
     */
    void unexpectedError(Throwable t);
}
        

上記のインターフェースは、Responseの結果に必要なメソッドを用意しています。
インターフェースは、メソッドの宣言のみ行います。

続いて作成したServiceCallbackをActivityで実装します。

{project_folder}/ui/RetrofitErrorAndLogActivity.java
public class RetrofitErrorAndLogActivity extends AppCompatActivity implements ServiceCallback<Weather> {

    public static final String TAG = "RetrofitActivity";
    public static final String API_URL = "http://weather.livedoor.com/forecast/webservice/json/";
    private TextView result;

    // 省略 code omit //

   +@Override
    public void success(Response<Weather> response) {
        result.setText(response.body().link);
    }

    @Override
    public void unauthenticated(Response<?> response) {

    }

    @Override
    public void clientError(Response<?> response) {

    }

    @Override
    public void serverError(Response<?> response) {

    }

    @Override
    public void networkError(IOException e) {
        Log.d(TAG, "networkError");
    }

    @Override
    public void unexpectedError(Throwable t) {

    }
}
        

■ 実装の解説

1. Generic

型変数T(Type)を具体的な値で置き換えます。
Response bodyの型はWeatherオブジェクトです。なので、Weatherオブジェクトで置き換えます。

public class RetrofitActivity extends AppCompatActivity implements ServiceCallback<Weather>
        

最後に、CallインターフェースのenqueueメソッドにCallbackインターフェースを引き渡します。

{project_folder}/ui/RetrofitErrorAndLogActivity.java
public class RetrofitErrorAndLogActivity extends AppCompatActivity implements ServiceCallback<Weather> {

    public static final String TAG = "RetrofitErrorAndLogActivity";
    public static final String API_URL = "http://weather.livedoor.com/forecast/webservice/json/";
    private TextView result;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_retrofit_error_and_log);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        result = (TextView) findViewById(R.id.result);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
    }

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

        try {
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(API_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();

            LivedoorWeatherWebService service = retrofit.create(LivedoorWeatherWebService.class);

            Call<Weather> call = service.webservice("400040");
           +call.enqueue(new Callback<Weather>() {
                @Override
                public void onResponse(Call<Weather> call, Response<Weather> response) {
                    // TODO if 'callbackExecutor' is not null, the 'callback' methods should be executed
                    // on that executor by submitting a Runnable. This is left as an exercise for the reader.

                    int code = response.code();
                    if (code >= 200 && code < 300) {
                        success(response);
                    } else if (code == 401) {
                        unauthenticated(response);
                    } else if (code >= 400 && code < 500) {
                        clientError(response);
                    } else if (code >= 500 && code < 600) {
                        serverError(response);
                    } else {
                        unexpectedError(new RuntimeException("Unexpected response " + response));
                    }
                }

                @Override
                public void onFailure(Call<Weather> call, Throwable t) {
                    // TODO if 'callbackExecutor' is not null, the 'callback' methods should be executed
                    // on that executor by submitting a Runnable. This is left as an exercise for the reader.

                    if (t instanceof IOException) {
                        networkError((IOException) t);
                    } else {
                        unexpectedError(t);
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void success(Response<Weather> response) {
        result.setText(response.body().link);
    }

    @Override
    public void unauthenticated(Response<?> response) {

    }

    @Override
    public void clientError(Response<?> response) {

    }

    @Override
    public void serverError(Response<?> response) {

    }

    @Override
    public void networkError(IOException e) {
         result.setText("networkError : " + e.getMessage());
    }

    @Override
    public void unexpectedError(Throwable t) {

    }

}
        

■ 実装の解説

1. enqueue

Callインターフェースのenqueueメソッドは、リクエストを非同期で送信します。なので、非同期のスレッド処理を開発者が記述する必要がありません。
引数にはCallbackインターフェースを受け取ります。
このサンプルでは、Callback処理を匿名クラスで記載します。実際は、別クラスを用意しても、Activity内の変数でCallbackインターフェースを宣言しても良いでしょう。

call.enqueue(new Callback<Weather>() {
    @Override
    public void onResponse(Call<Weather> call, Response<Weather> response) {
        // TODO if 'callbackExecutor' is not null, the 'callback' methods should be executed
        // on that executor by submitting a Runnable. This is left as an exercise for the reader.

        int code = response.code();
        if (code >= 200 && code < 300) {
            success(response);
        } else if (code == 401) {
            unauthenticated(response);
        } else if (code >= 400 && code < 500) {
            clientError(response);
        } else if (code >= 500 && code < 600) {
            serverError(response);
        } else {
            unexpectedError(new RuntimeException("Unexpected response " + response));
        }
    }

    @Override
    public void onFailure(Call<Weather> call, Throwable t) {
        // TODO if 'callbackExecutor' is not null, the 'callback' methods should be executed
        // on that executor by submitting a Runnable. This is left as an exercise for the reader.

        if (t instanceof IOException) {
            networkError((IOException) t);
        } else {
            unexpectedError(t);
        }
    }
});
        

取得したステータスコードでcallbackに処理を振り分けています。

ビルド

上記のコードの動作を確認してみましょう。
ネットワークエラーを検知するために、機内モードで接続します。

図2 sample app error network

エラーメッセージが表示されました。

実装のポイント

実装のポイントは、callback処理です。

サービスの結果を受け取るインターフェースを用意することで、ネットワーク処理の実装忘れを防ぐことができます。

結論

Retrofitは細かくエラーを処理することができます。さらに慣れてきたら、addCallAdapterFactoryを使ってハンドリングしても良いと思います。

関連記事

タグ検索で調べてみよう

Android7.0 ライブラリ