Использование библиотеки Retrofit в проектах на Андроид

Использование библиотеки Retrofit в проектах на Андроид для обмена данными с 1С с помощью HTTP-сервисов, с примерами и готовым рабочим шаблоном.

Решил вот написать статью. Так как писать статьи я не умею, прошу сильно не пинать за возможные ошибки.
Написать статью меня сподвигла вот эта вот публикация
//infostart.ru/public/463387/

В свое время для одного своего проекта на Андроид на базе этой публикации я делал обмен с 1С через WEB-Сервисы. Так как я был начинающим программистом на Андроид, алгоритм получился немного громоздким, т.к. в результате обмена я получал xml файл, который, в итоге,  надо было еще парсить средствами Андроид. Что-то типа вроде этого.

       try {
XmlPullParser xpp = prepareXpp();
while ((xpp.getEventType() != XmlPullParser.END_DOCUMENT)) {
switch (xpp.getEventType()) {
case XmlPullParser.START_DOCUMENT:
//                        Log.d(LOG_TAG, "XmlPullParser.START_DOCUMENT Start document");
break;
case XmlPullParser.START_TAG:
//                      Log.d(LOG_TAG, "XmlPullParser.START_TAG " + xpp.getName() + " " + xpp.getDepth());
switch (xpp.getDepth()) {
case 2:
data = new Bundle();
break;
case 3:
tagName = xpp.getName();
}

//                        if (xpp.getName().equals(XML_NODE_NAME_CONTACT)) {
//                            data = new Bundle();
//                        } else {
//                            tagName = xpp.getName();
//                        }
break;
case XmlPullParser.END_TAG:
//                        Log.d(LOG_TAG, "XmlPullParser.END_TAG " + xpp.getName());
tagName = "";
if (xpp.getDepth() == 2) {
addDataContact(data);
//                          Log.d(LOG_TAG,data.get("FullName").toString());
//                        Log.d(LOG_TAG,"ADD DATA");
//breakProgram = true;
}
break;
case XmlPullParser.TEXT:
//                  Log.d(LOG_TAG, "XmlPullParser.TEXT "  + xpp.getText());
if (xpp.getDepth() == 3) {
String dataText = xpp.getText();
if (dataText == null) {
dataText = "";
};
data.putString(tagName, dataText);
}
//                        if (!tagName.isEmpty()) {
//                          data.putString(tagName, xpp.getText());
//                        }
break;
default:
break;

}
//if (breakProgram) break;
xpp.next();
}
}catch (XmlPullParserException e) {
//e.printStackTrace();
DataXML.append(e.toString());
sendMessage(MainActivity.STATUS_ERR);
return "false";

//            Log.d(LOG_TAG, e.toString());
}catch (Exception e) {
//e.printStackTrace();
DataXML.append(e.toString());
sendMessage(MainActivity.STATUS_ERR);

//            Log.d(LOG_TAG, e.toString());
return "false";
}
return "true";
}

Да и тестирование WEB-сервиса тоже требовало определенных усилий и дополнительного софта. Библиотека ksoap, с помощью которой можно работать с WEB-Сервисами, не внушала у меня особого доверия, может я в ней не разобрался тогда до конца, может еще были причины. В общем, для меня все это было громоздким и устаревшим.

К тому же злые языки в 1С утверждали, что WEB-Сервисы — это вчерашний день и настоятельно рекомендовали переходить на HTTP-сервисы. И я не мог с ними не согласиться. И начал искать альтернативу. В поисках альтернативы наткнулся на библиотеку под названием Retrofit. Это HTTP клиент реализующий REST архитектуру. Выражаясь просто, ее можно использовать для обмена с 1С с помощью HTTP-сервисов. В дополнении к этому, эта библиотека умеет работать с json, но самое важное, что при работе с ней, программист оперирует классами (моделью). Т.е. получая результат от сервера, я работаю не с сырыми данными в виде xml или json, а уже готовыми классами.

Как это работает?

Например, у нас есть некоторые данные, которые надо получить из 1С. Средствами 1С легко получить данные в формате json. Создаем на стороне 1С HTTP-сервис, который принимает запрос и возвращает результат в виде json.

 

 

Функция ПолучитьПередатьДанные(Запрос)
СтруктураРезультата = ОбменДанными.ПолучитьОбработатьДанные(Запрос);
Ответ = Новый HTTPСервисОтвет(СтруктураРезультата.КодСостояния);
Ответ.УстановитьТелоИзСтроки(СтруктураРезультата.РезультатВыполнения);
Возврат Ответ;
КонецФункции

Подключаем к своему проекту Retrofit. 

// retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-gson:2.6.2'

Создаем интерфейс, где будут наши методы для запроса или отправки данных
 

public interface ApiService {
@GET("{secondURL}/{id}/products")
Call<List<RetrofitProduct>> getListProducts(@Path(value =  "secondURL", encoded = true) String secondURL, @Path("id") String id_client);
}

RetrofitProduct — это класс, список объектов которого будут созданы библиотекой автоматически, при успешном получении данных с сервера.

public class RetrofitProduct {
@SerializedName("ID")
@Expose
private String iD;
@SerializedName("name")
@Expose
private String name;
@SerializedName("name")

// далее геттеры и сеттеры
}

Кстати, его можно автоматически сгенерировать на этом вот сайте https://www.jsonschema2pojo.org/
если у вас есть данные в формате  json.

Создаем класс синглтон для настройки клиента и авторизации на стороне 1С  

public class RetrofitClient {
private static Retrofit retrofit = null;
// временно, логин и пароль для подключения будем хранить в настройках?
private static String user = "admin";
private static String pwd = "admin";

public static Retrofit getClient(String baseUrl) {
if (retrofit == null) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(300, TimeUnit.SECONDS)
.addInterceptor(new BasicAuthInterceptor(user, pwd))
.build();


retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();



}
return retrofit;
}

}

Для авторизации на стороне 1С используем класс BasicAuthInterceptor

class BasicAuthInterceptor implements Interceptor{
private String credentials;

public BasicAuthInterceptor(String user, String password) {
credentials = Credentials.basic(user, password);
}

@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request authenticatedRequest = request.newBuilder()
.addHeader("Authorization", credentials)
.build();
return chain.proceed(authenticatedRequest);
}

}

определим класс для инициализации библиотеки
 

public class ApiUtils {
private static String BASE_URL;
private ApiUtils(){}

public static ApiService getAPIService() {
BASE_URL = App.getBaseURL();
try {
return RetrofitClient.getClient(BASE_URL).create(ApiService.class);
}catch (Exception e) {
MyLog.e(e.toString());
return null;
}


}

}

Инициализация клиента в коде

mApiService = ApiUtils.getAPIService();
if (mApiService == null) {
return;
}

И собственно дергаем нужный нам метод

   try {
response = mApiService.getListProducts(
App.getDefaultSecondURL(), App.CONSTANT_ID_PARTNER)
.execute();
if (response.isSuccessful()) {
somethingToDo(response.body()); // тут у нас List<RetrofitProduct>

}else {
String errorMessage = response.raw().toString() + "
" //response.raw() - сырые данные, можно получить ответ сервера
+ response.errorBody().string();

}
}catch (IOException e) {
// обработка исключения;
}

Есть два варианта вызова, синхронный и асинхронный. Синхронный вызов execute() не допускается в UI-потоке. В моем случае я использую WorkManager, который выполняет задачи в своем потоке. 

Асинхронный вызов выглядит вот так
 

       mApiServiceRetrofit.getProduct(App.getDefaultSecondURL(), App.CONSTANT_ID_PARTNER).enqueue(new Callback<RetrofitProduct>() {
@Override
public void onResponse(Call<PostCoordinates> call, Response<PostCoordinates> response) {
if (response.isSuccessful()) {

}else {

}

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



}
});

Можно вызывать из Activity или из Service. Но, как я уже писал выше, я использую WorkManager (https://developer.android.com/reference/androidx/work/WorkManager.html)
Он позволяет запускать фоновые задачи последовательно или параллельно, передавать в них данные, получать из них результат, отслеживать статус выполнения и запускать только при соблюдении заданных условий. И еще, на получения результата работы можно на него подписаться, что очень удобно.
 

       LiveData<WorkInfo> ld = WorkManager.getInstance().getWorkInfoByIdLiveData(workID);
ld.observe(context, new Observer<WorkInfo>() {
@Override
public void onChanged(WorkInfo workInfo) {
if (workInfo.getState().isFinished()) {
// to do
}
}
});

В результате, программист получает отличный и простой инструмент для обмена данными с 1С. При этом можно не только получать данные с помощью HTTP-запроса GET, но также и передавать данные с помощью HTTP-запроса POST.

 

Полное описание библиотеки на сайте разработчика: https://square.github.io/retrofit/
Для ленивых написал простой проект с одним activity, который реализует работу с Retrofit, а также с WorkManager. Проект полностью рабочий, подключаете к Android Studio и можете сразу же пробовать. Без настроек на свой сервер будете получать ответ в виде Toast, об ошибке подключения 🙂

Надеюсь, что статья сэкономит кому то время (и деньги) 🙂
 

2 Comments

  1. dusha0020

    Может стоит «данные в формате Gson» заменить по тексту на json?

    Gson это библиотека Андроида для работы с json, а не формат данных.

    Reply
  2. WKBAPKA

    (1) да, верно! спасибо, не обратил внимание

    Reply

Leave a Comment

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