Retrofit + Gson で、 API にアクセス & JSON のパージング処理を簡潔に実装する

  • 10
    Like
  • 0
    Comment
More than 1 year has passed since last update.

動作例:

retrofit_sample.gif

  • コードは GitHub に置きました。
  • 以下の手順で動作確認可能です。
    1. AndroidStudio 起動
    2. File > New > Project from Version Control > GitHub を選択。 Git Repository URL を https://github.com/Hachimori/AndroidSamples.git に指定してリポジトリをクローンする
    3. File > Open として、RetrofitSample のプロジェクトを開く

概要

  • Retrofit: Android の HTTP クライアントライブラリ
  • Gson: JSON を Java のオブジェクトに変換するライブラリ
  • Retrofit と Gson を組み合わせて、 GitHub API にアクセス & 取得したデータを表示する方法をメモ

導入

手順

  • build.gradle (Module: app) に追加
dependencies {
    // ... 中略 ...

    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'

    // ... 中略 ...
}

ProGuard

  • proguard-rules.pro に追加
# Retrofit
-dontnote retrofit2.Platform
-dontnote retrofit2.Platform$IOS$MainThreadExecutor
-dontwarn retrofit2.Platform$Java8
-keepattributes Signature
-keepattributes Exceptions

GitHub の API

実装例

Java 側の実装

API のエンドポイントを定義

  • 以下のような Java Interface を実装
  public interface GitHubService {

      /**
       * ユーザの情報を取得する
       * https://developer.github.com/v3/users/#get-a-single-user
       */
      @GET("users/{username}")
      Call<User> getUser(@Path("username") String user);

      /**
       * ユーザのリポジトリ一覧を取得する
       * https://developer.github.com/v3/repos/#list-user-repositories
       */
      @GET("users/{user}/repos")
      Call<List<Repos>> listRepos(@Path("user") String user);

      /**
       * 
       * リポジトリのコミットを列挙する
       * https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
       */
      @GET("repos/{owner}/{repos}/commits")
      Call<List<Commits>> listCommit(@Path("owner") String owner, @Path("repos") String repos);
  }
  • ポイント

    • アノテーション内の文字列に API エンドポイントを記述
    • "{ }" で囲った文字列は、メソッドの引数として渡した文字列に置き換えられる
  • 参考

API から取得したデータを Java オブジェクトとして格納するクラスを定義

  • 以下のようなクラスを定義
    • ユーザの情報を格納するクラス
  public class User {
      private String name;
      private String company;
      private String email;
      private String bio;
      private String created_at;
      private String updated_at;

      public String getName() {
          return name;
      }

      public String getCompany() {
          return company;
      }

      public String getEmail() {
          return email;
      }

      public String getBio() {
          return bio;
      }

      public String getCreatedAt() {
          return created_at;
      }

      public String getUpdatedAt() {
          return updated_at;
      }
  }
  • ユーザのリポジトリ情報を格納するクラス
  public class Repos {

      private String id;
      private String name;
      private String full_name;
      private String url;
      private String description;

      public String getId() {
          return id;
      }

      public String getName() {
          return name;
      }

      public String getFullName() {
          return full_name;
      }

      public String getUrl() {
          return url;
      }

      public String getDescription() {
          return description;
      }
  }
  • リポジトリのコミット情報を格納するクラス
  public class Commits {
      private String url;
      private String sha;
      private Commit commit;

      public String getUrl() {
          return url;
      }

      public String getSha() {
          return sha;
      }

      public String getMessage() {
          return commit.getMessage();
      }

      public String getCommiterName() {
          return commit.getCommiterName();
      }

      public String getCommiterEmail() {
          return commit.getCommiterEmail();
      }

      public String getCommitDate() {
          return commit.getCommitDate();
      }

      private class Commit {
          private String message;
          private Committer committer;

          public String getMessage() {
              return message;
          }

          public String getCommiterName() {
              return committer.getName();
          }

          public String getCommiterEmail() {
              return committer.getEmail();
          }

          public String getCommitDate() {
              return committer.getDate();
          }

          private class Committer {
              private String name;
              private String email;
              private String date;

              public String getName() {
                  return name;
              }

              public String getEmail() {
                  return email;
              }

              public String getDate() {
                  return date;
              }
          }
      }
  }

GitHub API にアクセスし、データを取得する処理の実装

 Retrofit retrofit = new Retrofit.Builder()
         .baseUrl("https://api.github.com")
         .addConverterFactory(GsonConverterFactory.create())
         .build();
 GitHubService service = retrofit.create(GitHubService.class);

 Call<User> userCall = service.getUser("ユーザ名");
 userCall.enqueue(new Callback<User>() {
     @Override
     public void onResponse(Call<User> call, Response<User> response) {

         // ユーザ情報の取得 (https://api.github.com/users/"ユーザ名" にアクセスした時のデータが取得される)
         User user = response.body();

         // ...任意の処理...
     }

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

         // ...失敗した時の処理...
     }
 });

ExpandableListView を利用し、親グループ:リポジトリ一覧, 子グループ:各リポジトリのコミット一覧 の折り畳めるリストを表示する

Java 側の実装例

  • フィールド宣言
    /**
     * リポジトリ/コミットのリスト
     */
    @BindView(R.id.repos_commit_list) ExpandableListView mReposCommitList;


    /**
     * リポジトリ/コミットのリストの Adapter
     */
    private ReposCommitAdapter mReposCommitAdapter;

    private Retrofit mRetrofit;
    private GitHubService mService;
  • リポジトリ一覧, 各リポジトリのコミット一覧を取得し、 ExpandListView にセットする処理の実装
    • ※冒頭に載せた gif によるデモの実装の簡略版です
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.retrofit_sample, container, false);
        ButterKnife.bind(this, view);

        mRetrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        mService = mRetrofit.create(GitHubService.class);

        // リポジトリ一覧, 各リポジトリのコミット一覧
        final List<Repos> reposList = new ArrayList<>();
        final List<List<Commits>> commitsList = new ArrayList<>();

        // Adapter の生成
        mReposCommitAdapter = new ReposCommitAdapter(getContext(), reposList, commitsList);
        mReposCommitList.setAdapter(mReposCommitAdapter);

        // リポジトリ一覧/各リポジトリのコミット一覧の取得し、データを ExpandListView にセットする
        new AsyncTask<String, Void, Void>() {

            @Override
            protected Void doInBackground(String... params) {

                // ユーザ名
                String userName = params[0];

                try {
                    // GitHub API にアクセスして、ユーザの GitHub リポジトリ一覧の取得
                    Call<List<Repos>> reposCall = mService.listRepos(userName);
                    reposList.addAll(reposCall.execute().body());

                    // GitHub API にアクセスして、各リポジトリのコミット一覧を取得
                    for (Repos repos : reposList) {
                        String reposName = repos.getName();
                        Call<List<Commits>> commitsCall = mService.listCommit(userName, reposName);
                        commitsList.add(commitsCall.execute().body());
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                // データ更新の通知
                mReposCommitAdapter.notifyDataSetChanged();
            }
        }.execute("Hachimori");

        return view;
    }
  • ExpandableListView の Adapter の実装
    public class ReposCommitAdapter extends BaseExpandableListAdapter {

        private Context mContext;
        private List<Repos> mReposList;
        private List<List<Commits>> mCommitList;

        public ReposCommitAdapter(Context context, List<Repos> reposList, List<List<Commits>> commitList) {
            mContext = context;
            mReposList = reposList;
            mCommitList = commitList;
        }

        @Override
        public int getGroupCount() {
            return mReposList.size();
        }

        @Override
        public int getChildrenCount(int groupPosition) {
            return mCommitList.get(groupPosition).size();
        }

        @Override
        public Repos getGroup(int groupPosition) {
            return mReposList.get(groupPosition);
        }

        @Override
        public Commits getChild(int groupPosition, int childPosition) {
            return mCommitList.get(groupPosition).get(childPosition);
        }

        @Override
        public long getGroupId(int groupPosition) {
            return groupPosition;
        }

        @Override
        public long getChildId(int groupPosition, int childPosition) {
            return childPosition;
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }

        /**
         * 親アイテムの取得
         */
        @Override
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {

            View v = LayoutInflater
                    .from(mContext)
                    .inflate(R.layout.repository_list_cell, null);

            // 親アイテムを取得
            Repos repos = mReposList.get(groupPosition);

            // リポジトリ名をセット
            ((TextView) v.findViewById(R.id.repository_name)).setText(repos.getName());

            // リポジトリの説明をセット
            ((TextView) v.findViewById(R.id.repository_description)).setText(repos.getDescription());

            // URLをセット
            ((TextView) v.findViewById(R.id.repository_url)).setText(repos.getUrl());

            return v;
        }

        /**
         * 子アイテムの取得
         */
        @Override
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
                                 View convertView, ViewGroup parent) {

            View v = LayoutInflater
                    .from(mContext)
                    .inflate(R.layout.commit_list_cell, null);

            // 子アイテムを取得
            Commits commit = mCommitList.get(groupPosition).get(childPosition);

            // Commit ID をセット
            ((TextView) v.findViewById(R.id.commit_id)).setText(commit.getSha());

            // 名前をセット
            ((TextView) v.findViewById(R.id.committer_name)).setText(commit.getCommiterName());

            // Email をセット 
            ((TextView) v.findViewById(R.id.committer_email)).setText("(" + commit.getCommiterEmail() + ")");

            // 日付をセット
            ((TextView) v.findViewById(R.id.commit_date)).setText(commit.getCommitDate());

            // 説明をセット
            ((TextView) v.findViewById(R.id.commit_description)).setText(commit.getMessage());

            return v;
        }

        @Override
        public boolean isChildSelectable(int groupPosition, int childPosition) {
            return true;
        }
    }

レイアウトファイルの実装

  • ExpandableListView のレイアウトファイル (retrofit_sample.xml の一部)
  <ExpandableListView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:id="@+id/repos_commit_list"/>
  • リポジトリ (親グループ要素) のレイアウトファイル (repository_list_cell.xml)
  <?xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:padding="10dp">

      <!-- リポジトリ名 -->
      <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_marginBottom="10dp"
          android:id="@+id/repository_name"
          android:textColor="@android:color/black"
          android:textSize="24sp"/>

      <!-- リポジトリの説明 -->
      <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_marginLeft="10dp"
          android:layout_marginBottom="10dp"
          android:id="@+id/repository_description"
          android:textSize="18sp"/>

      <!-- リポジトリの URL -->
      <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:id="@+id/repository_url"
          android:textSize="12sp"/>
  </LinearLayout>
  • リポジトリのコミット (子グループ要素) のレイアウトファイル (commit_list_cell.xml)
  <?xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:padding="30dp">

      <!-- Commit ID -->
      <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:id="@+id/commit_id"/>

      <LinearLayout
          android:layout_width="wrap_content"
          android:layout_height="wrap_content">

          <!-- 名前 -->
          <TextView
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:id="@+id/committer_name"/>

          <!-- Email -->
          <TextView
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:id="@+id/committer_email"/>
      </LinearLayout>

      <!-- 日付 -->
      <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:id="@+id/commit_date"/>

      <!-- 説明 -->
      <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_marginTop="20dp"
          android:layout_marginLeft="20dp"
          android:id="@+id/commit_description"/>
  </LinearLayout>