Изучаем Retrofit 2

от автора

В мире Android разработки существует множество интересных библиотек, и сегодня мы рассмотрим детище компании SquareRetrofit. Что же это за зверь такой? Retrofit (согласно официальному сайту) — типобезопасный HTTP-клиент для Android и Java. В мире же Android разработки клиент-серверных приложений — незаменимый инструмент для работы с API. Каких-то лет 5 назад Android-разработчикам для работы с сетью приходилось воротить горы кода с обратными вызовами, AsyncTask’ами и прочими «низкоуровневыми» вещами. И компания Square выпустила такую замечательную библиотеку — Retrofit.

В сети Интернет мне не удалось найти внятных туториалов по второй версии бибилиотеки, поэтому сегодня мы будем разбираться с ней на примере приложения, получающего посты с bash.im

Лучше один раз увидеть, чем сто раз услышать

Мы будем создавать приложение, получающее данные от API сайта Umorili, так как только они предоставляют данные с баша в удобном для парсинга виде. Вот так будет выглядеть конечный вариант:
Дизайном, конечно, не блещет

Ну что, дети, вы готовы?

Зависимости

Библиотеку Retrofit можно подключить тремя способами: Gradle, Maven, Jar. Опишем каждый способ.

Gradle

В большинстве случаев для сборки приложений под Android используется именно этот инструмент, поэтому, если вы не уверены, берите этот вариант 🙂 (здесь и далее для зависимостей будут использоваться Gradle)
Для подключения в файл build.gradle модуля приложения в раздел dependencies вставляем строчку

compile 'com.squareup.retrofit2:retrofit:2.1.0'

Maven

Если кто-то использует эту систему зависимостей и сборки, то фрагмент зависимости будет выглядеть так

<dependency>   <groupId>com.squareup.retrofit2</groupId>   <artifactId>retrofit</artifactId>   <version>2.1.0</version> </dependency> 

Jar

Не приветствую использование этого варианта, но некоторые любят его.
Скачиваем с официального сайта jar файл (ссылка) и кидаем его в папку libs.

Помимо самой библиотеки нам понадобится парсер JSON и RecyclerView-v7, поэтому подключим их

compile 'com.squareup.retrofit2:converter-gson:2.1.0' //Конвертер JSON, можно, если предпочитаете, использовать Jackson compile 'com.android.support:recyclerview-v7:25.0.0' //RecyclerView 

С зависимостями разобрались, теперь перейдем к самой сладкой части — разработке. Перво-наперво нам нужно описать запросы к API, поэтому

Описание запросов к API

Retrofit позволяет сделать полноценный REST-клиент, который может выполнять POST, GET, PUT, DELETE. Для обозначения типа и других аспектов запроса используются аннотации. Например, для того, чтобы обозначить, что требуется GET запрос, нам нужно написать перед методом GET, для POST запроса POST, и так далее. В скобках к типу запроса ставится целевой адрес. Для примера возьмем API GitHub’а. Полный URL для получения списка репозиториев определенного пользователя можно представить в виде https://api.github.com/users/octocat/repos, где

  • api.github.com — базовая часть адреса (всегда оканчивается слешем)
  • users/{user}/repos — метод (адрес документа, целевой адрес), где определенного пользователя (octocat) мы заменили на алиас (про использование алиасов чуть позже)

Еще существуют параметры запроса, например в запросе к Umorili мы будем использовать следующий адрес — http://www.umori.li/api/get?name=bash&num=50, где name=bash&num=50 — параметры.

Но одними аннотациями описание не заканчивается, нам же надо где-то их описать. А описываем мы их в интерфейсе (interface). Для нашего приложения интерфейс будет следующим:

package ru.mustakimov.retrofittutorial.api;  import java.util.List;  import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; import ru.mustakimov.retrofittutorial.PostModel;  public interface UmoriliApi {     @GET("/api/get")     Call<List<PostModel>> getData(@Query("name") String resourceName, @Query("num") int count); } 

Разберем этот интерфейс. У нас есть метод getData, возвращающий объект типа Call<List<PostModel>>. Методы должны всегда возвращать объект типа Call<T> и иметь аннотацию типа запроса (GET, POST, PUT, DELETE).
Аннотация @Query("name") String resourceName показывает Retrofit’у, что в качестве параметра запроса нужно поставить пару name=<Значение строки resourceName>.

Если у нас в целевом адресе стоит алиас, то для того, чтобы заместо алиаса поставить значение, нам нужно в параметрах функции написать @Path("<Название аласа>") SomeType variable, где SomeType — любой тип (например, String, int, float).

PostModel — класс, сгенерированный сайтом jsonschema2pojo на основе ответа сервера.

Вот сам класс

package ru.mustakimov.retrofittutorial;  import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName;  public class PostModel {      @SerializedName("site")     @Expose     private String site;     @SerializedName("name")     @Expose     private String name;     @SerializedName("desc")     @Expose     private String desc;     @SerializedName("link")     @Expose     private String link;     @SerializedName("elementPureHtml")     @Expose     private String elementPureHtml;      /**      * @return The site      */     public String getSite() {         return site;     }      /**      * @param site The site      */     public void setSite(String site) {         this.site = site;     }      /**      * @return Site name      */     public String getName() {         return name;     }      /**      * @param name Site name      */     public void setName(String name) {         this.name = name;     }      /**      * @return Site description      */     public String getDesc() {         return desc;     }      /**      * @param desc Site description      */     public void setDesc(String desc) {         this.desc = desc;     }      /**      * @return The link      */     public String getLink() {         return link;     }      /**      * @param link The link      */     public void setLink(String link) {         this.link = link;     }      /**      * @return The elementPureHtml      */     public String getElementPureHtml() {         return elementPureHtml;     }      /**      * @param elementPureHtml The elementPureHtml      */     public void setElementPureHtml(String elementPureHtml) {         this.elementPureHtml = elementPureHtml;     }  } 

Подготовка к запросу

Перед отправкой запроса и получением результата нам нужно произвести инициализацию Retrofit’а и объекта интерфейса. Чтобы приложение не имело сотню объектов, выполняющих одну и ту же функцию, мы произведем всю инициализацию в классе, унаследованном от Application. Код тогда будет следующим:

package ru.mustakimov.retrofittutorial;  import android.app.Application;  import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import ru.mustakimov.retrofittutorial.api.UmoriliApi;  public class App extends Application {      private static UmoriliApi umoriliApi;     private Retrofit retrofit;      @Override     public void onCreate() {         super.onCreate();          retrofit = new Retrofit.Builder()                 .baseUrl("http://www.umori.li/") //Базовая часть адреса                 .addConverterFactory(GsonConverterFactory.create()) //Конвертер, необходимый для преобразования JSON'а в объекты                 .build();         umoriliApi = retrofit.create(UmoriliApi.class); //Создаем объект, при помощи которого будем выполнять запросы     }      public static UmoriliApi getApi() {         return umoriliApi;     } } 

P.S. не забываем в манифесте прописать, что используем свой класс Application

Теперь из любого класса мы имеем доступ к API.

Получение данных

Мы можем выполнять запросы (и, следовательно, получать данные) двумя способами — синхронными а асинхронными запросами. Для синхронного (блокирующего) получения мы используем метод execute() у объекта типа Call. Для нашего примера код был бы следующим:

Response response = App.getApi().getData("bash", 50).execute(); 

В результате выполнения мы получаем объект типа Response (ответ), откуда мы можем уже получить распарсенный ответ методом body().

Для асинхронного получения мы заменяем execute() на enqueue(), где в параметрах передаем функции обратного вызова (колбэки). В нашем примере будет выглядеть примерно так:

App.getApi().getData("bash", 50).enqueue(new Callback<List<PostModel>>() {     @Override     public void onResponse(Call<List<PostModel>> call, Response<List<PostModel>> response) {         //Данные успешно пришли, но надо проверить response.body() на null     }     @Override     public void onFailure(Call<List<PostModel>> call, Throwable t) {         //Произошла ошибка     } }); 

Делаем отображение данных

Данные мы уже получили, а как и теперь отобразить? Кидаем в разметку активности RecyclerView и как-нибудь его обзываем. После этого создаем разметку для элемента

Вот что получилось у меня

activity_main.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"     tools:context="ru.mustakimov.retrofittutorial.MainActivity">      <android.support.v7.widget.RecyclerView         android:layout_width="match_parent"         android:layout_height="match_parent"         android:layout_alignParentTop="true"         android:layout_alignParentLeft="true"         android:id="@+id/posts_recycle_view"         android:layout_alignParentStart="true" /> </RelativeLayout> 

post_item.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:padding="5dp"     android:layout_width="match_parent"     android:layout_height="wrap_content">      <TextView         android:id="@+id/postitem_post"         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:text="Очень интересный пост с баша, который никто никогда не видел, так как его не существует"         android:textColor="?android:attr/textColorPrimary"         android:layout_alignParentTop="true"         android:layout_alignParentLeft="true"         android:layout_alignParentStart="true" />      <TextView         android:id="@+id/postitem_site"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="Bash.im"         android:layout_below="@+id/postitem_post"         android:layout_alignParentRight="true"         android:layout_alignParentEnd="true"         android:gravity="end"         android:textAlignment="textEnd" /> </RelativeLayout> 

После создаем адаптер для RecyclerView

Код адаптера

package ru.mustakimov.retrofittutorial;  import android.os.Build; import android.support.v7.widget.RecyclerView; import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView;  import java.util.List;  public class PostsAdapter extends RecyclerView.Adapter<PostsAdapter.ViewHolder> {      private List<PostModel> posts;      public PostsAdapter(List<PostModel> posts) {         this.posts = posts;     }      @Override     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {         View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_item, parent, false);         return new ViewHolder(v);     }      @Override     public void onBindViewHolder(ViewHolder holder, int position) {         PostModel post = posts.get(position);         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {             holder.post.setText(Html.fromHtml(post.getElementPureHtml(), Html.FROM_HTML_MODE_LEGACY));         } else {             holder.post.setText(Html.fromHtml(post.getElementPureHtml()));         }         holder.site.setText(post.getSite());     }      @Override     public int getItemCount() {         if (posts == null)             return 0;         return posts.size();     }      class ViewHolder extends RecyclerView.ViewHolder {         TextView post;         TextView site;          public ViewHolder(View itemView) {             super(itemView);             post = (TextView) itemView.findViewById(R.id.postitem_post);             site = (TextView) itemView.findViewById(R.id.postitem_site);         }     } } 

И прописываем в MainActivity.java инициализацию RecyclerView, адаптера, а так же получение данных.

А вот и MainActivity

package ru.mustakimov.retrofittutorial;  import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.widget.Toast;  import java.io.IOException; import java.util.ArrayList; import java.util.List;  import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response;  public class MainActivity extends AppCompatActivity {      RecyclerView recyclerView;     List<PostModel> posts;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);          posts = new ArrayList<>();          recyclerView = (RecyclerView) findViewById(R.id.posts_recycle_view);         LinearLayoutManager layoutManager = new LinearLayoutManager(this);         recyclerView.setLayoutManager(layoutManager);          PostsAdapter adapter = new PostsAdapter(posts);         recyclerView.setAdapter(adapter);          try {             Response response = App.getApi().getData("bash", 50).execute();         } catch (IOException e) {             e.printStackTrace();         }          App.getApi().getData("bash", 50).enqueue(new Callback<List<PostModel>>() {             @Override             public void onResponse(Call<List<PostModel>> call, Response<List<PostModel>> response) {                 posts.addAll(response.body());                 recyclerView.getAdapter().notifyDataSetChanged();             }              @Override             public void onFailure(Call<List<PostModel>> call, Throwable t) {                 Toast.makeText(MainActivity.this, "An error occurred during networking", Toast.LENGTH_SHORT).show();             }         });     } } 

На GitHub’е вы можете найти полный код данного приложения — github.com/Mikhail57/RetrofitTutorial
ссылка на оригинал статьи https://habrahabr.ru/post/314028/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *