В мире Android разработки существует множество интересных библиотек, и сегодня мы рассмотрим детище компании Square — Retrofit. Что же это за зверь такой? 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 и как-нибудь его обзываем. После этого создаем разметку для элемента
<?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, адаптера, а так же получение данных.
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/
Добавить комментарий