Разработчикам на Xamarin доступен богатый выбор компонентов для работы с сетью, и в сегодняшней нашей статье мы рассмотрим набор модулей, которые также могут быть использованы в PCL-проектах на Xamarin.Forms.

Все статьи из колонки можно найти и прочитать по ссылке #xamarincolumn, или в конце материала под катом.



Refit для удобного описания клиента REST API


Самым популярным в настоящее время протоколом для общения мобильных приложений с сервером является REST в связке с Json. Поэтому наше сегодняшнее знакомство начнем с библиотеки Refit.

Refit позволяет описать спецификации для работы с REST-сервисом в виде простого Interface с понятным набором входных и выходных параметров, включая возможность манипулировать HTTP-заголовками для отдельных запросов. Для примера возьмем демо-API сервиса httpbin.org:

    [Headers("Accept: application/json")]
    public interface IHttpbinApi
    {
        [Get("/basic-auth/{username}/{password}")]
        Task<AuthResult> BasicAuth(string username, string password, [Header("Authorization")] string authToken, CancellationToken ctx);

        [Get("/cache")]
        Task<HttpResponseMessage> CheckIfModified([Header("If-Modified-Since")] string lastUpdateAtString, CancellationToken ctx);

        [Post("/post")]
        Task<HttpResponseMessage> FormPost([Body(BodySerializationMethod.UrlEncoded)] FormData data, CancellationToken ctx);
    }

После описания данного интерфейса, он подается на вход для Refit:

            var client = new HttpClient(new NativeMessageHandler())
            {
                BaseAddress = new Uri("http://httpbin.org")
            };
            _httpbinApiService = RestService.For<IHttpbinApi>(client);

Сами данные можно при необходимости (конвертации camel case или snake eyes, преобразование из множества в строковые значения) можно расширить аттрибутами из библиотеки Json.net, так как именно она используется в Refit:

      public class AuthResult
    {
        [JsonProperty("authenticated")]
        public bool IsAuthenticated { get; set; }

        [JsonProperty("user")]
        public string Login { get; set; }
    }

В Refit в качестве выходного значения можно получить уже преобразованные объекты DTO или HttpResponseMessage. Последний позволяет получить информацию о запросе и ответе, что может быть полезно при отладке. При желании также может использоваться ModernHttpClient при создании HttpClient. В целом, Refit — достаточно удобный и универсальный инструмент для Xamarin-разработчиков в том числе.

Примечание 1: для установки Refit 3.0 в PCL-проект Xamarin.Forms потребуется перевести проект на .NET Standard.

Примечание 2: в документации Refit нет упоминания на использование CancelationToken для отмены активных операций, но данный механизм работает и описан в тикете.

Polly


При разработке мобильных приложений часто приходится учитывать фактор нестабильности сигнала в сотовых сетях, поэтому при выполнении сетевых запросов часто возникает необходимости делать повторные попытки. Это позволяет не отвлекать лишний раз пользователя просьбой повторить запрос.

Интересный подход по использованию Refit и Polly описал Rob Gibbens в своем блоге (дополнительно там показан пример приоретизацией сетевых запросов с помощью Fusillade).

Вот так мы совершаем запрос:

protected async Task<RequestResult> MakeRequest<T>(Func<CancellationToken, Task<T>> loadingFunction, CancellationToken cancellationToken)
        {
            Exception exception = null;
            var result = default(T);

            try
            {
                result = await Policy.Handle<WebException>().Or<HttpRequestException>()
                    .WaitAndRetryAsync(3, i => TimeSpan.FromMilliseconds(300), (ex, span) => exception = ex)
                    .ExecuteAsync(loadingFunction, cancellationToken);
            }
            catch (Exception e)
            {
// Сюда приходят ошибки вроде отсутствия интернет-соединения или неправильной работы DNS
                exception = e;
            }
//TODO: Обработать исключения или передать их дальше            
            return result;
        }

Вместо loadingFunction необходимо передать ваш код обращения к Refit:

   var authToken = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
   return await MakeRequest(ct => _httpbinApiService.BasicAuth(username, password, authToken, ct), cancellationToken);

Итак, мы интегрировали Refit, Polly и ModernHttpClient.

Кэш


И в завершении статьи можно рассмотреть использование кэша при работе с сетью. Xamarin-разработчику доступны все возможности целевых платформ, поэтому для реализации кэша можно использовать различные СУБД. Одним из самых популярных кэшеров выступает Akavache, работающий поверх SQLite.

    var cache = BlobCache.LocalMachine;
    var cachedObjects = cache.GetAndFetchLatest("objects", GetRemoteObjectAsync,
        offset =>
        {
            TimeSpan elapsed = DateTimeOffset.Now - offset;
            return elapsed > new TimeSpan(hours: 0, minutes: 30, seconds: 0);
        });

Также можно использовать для реализации кэша очень удобную мобильную СУБД Realm. Ниже представлен пример кэшера на базе Realm:

 public static class LocalCache
    {
        private class CachedObject : RealmObject
        {
            [PrimaryKey]
            public string Key { get; set; }
            public string Value { get; set; }
            public DateTimeOffset UpdatedAt { get; set; }
        }


        private static readonly RealmConfiguration Configuration = new RealmConfiguration("cache.realm", true);
        private static Realm Db => Realm.GetInstance(Configuration);


        public static async Task WriteToCache<T>(string key, T data, DateTimeOffset timeStamp)
        {
            if (String.IsNullOrEmpty(key) || data == null || timeStamp == DateTimeOffset.MinValue) return;
            
            var currentValue = Db.All<CachedObject>().Where(o => o.Key == key).ToList().FirstOrDefault();
            if (currentValue == null)
                await Db.WriteAsync(db =>
                {
                    var newValue = db.CreateObject<CachedObject>();
                    newValue.Key = key;
                    newValue.UpdatedAt = timeStamp;
                    newValue.Value = JsonConvert.SerializeObject(data);
                });
            else
                using (var transaction = Db.BeginWrite())
                {
                    currentValue.Value = JsonConvert.SerializeObject(data);
                    currentValue.UpdatedAt = timeStamp;
                    transaction.Commit();
                }
        }

        public static DateTimeOffset CacheLastUpdated(string key)
        {
            if (String.IsNullOrEmpty(key)) return DateTimeOffset.MinValue;
            
            var currentValue = Db.All<CachedObject>().Where(o => o.Key == key).ToList().FirstOrDefault();
            return currentValue?.UpdatedAt ?? DateTimeOffset.MinValue;
        }

        public static void RemoveCache(string key)
        {
            if (String.IsNullOrEmpty(key)) return;
            
            var currentValue = Db.All<CachedObject>().Where(o => o.Key == key).ToList().FirstOrDefault();
            if (currentValue == null) return;


            using (var transaction = Db.BeginWrite())
            {
                Db.Remove(currentValue);
                transaction.Commit();
            }
        }

        public static T GetFromCache<T>(string key)
        {
            if (String.IsNullOrEmpty(key)) return default(T);
            
            var currentValue = Db.All<CachedObject>().Where(o => o.Key == key).ToList().FirstOrDefault();
            return currentValue?.Value == null ? default(T) : JsonConvert.DeserializeObject<T>(currentValue.Value);
        }

        public static void ClearCache()
        {
            Realm.DeleteRealm(Configuration);
        }
    }

Заключение


Итак, сегодня мы рассмотрели использование Refit, Json.net, ModernHttpClient, Polly и Realm при интеграции с REST API. В следующей статье мы рассмотрим вопросы интеграции Xamarin с внешними сервисами и Azure.

Об авторах



Вячеслав Черников — руководитель отдела разработки компании Binwell. В прошлом — один из Nokia Champion и Qt Certified Specialist, в настоящее время — специалист по платформам Xamarin и Azure. В сферу mobile пришел в 2005 году, с 2008 года занимается разработкой мобильных приложений: начинал с Symbian, Maemo, Meego, Windows Mobile, потом перешел на iOS, Android и Windows Phone.

Предыдущие части


1. Быстрое создание MVP (minimum viable product) на базе Microsoft Azure и Xamarin.Forms.
2. Готовим Xamarin.Forms: настройка окружения и первые шаги.
3. Повышаем эффективность работы в Xamarin.Forms.
4. Работаем с состояниями экранов в Xamarin.Forms.

Полезные ссылки


Поделиться с друзьями
-->

Комментарии (7)


  1. Vilyx
    22.09.2016 16:50

    Не совсем по теме, но хотелось бы задать вопрос специалистам Xamarin. Недавно хотел попробовать эту кроссплатформенную среду разработки, но столкнулся с тем, что по факту кроссплатформенностью и не пахнет, для каждой целевой ОС надо делать всё отдельно в том числе интерфейс, действительно кроссплатформенной остаётся только бизнеслогика, которая не всегда является самой сложной частью приложения.
    Подскажите, может я не в ту сторону смотрел?


    1. BOOMik
      22.09.2016 16:59
      +3

      Посмотрите в сторону Xamarin.Forms — благодаря ему можно интерфейс описывать сразу для всех поддерживаемых платформ. Конечно, если приложение достаточно сложное, то многие вещи придется описывать отдельно для каждой платформы, но количество кроссплатформенного кода в первой статье цикла было про это https://habrahabr.ru/company/microsoft/blog/281897/


      Если не использовать Xamarin.Forms, то кроссплатформенной идет бизнес-логика приложения. Благодаря библиотекам типо MVVM-cross можно уменьшить количество платформенного кода описании UI и прочих вещей.


      1. Vilyx
        22.09.2016 17:18

        Спасибо, с интерфейсом прояснили, а что с работой с камерой, GPS, с файловой системой, аудио?


        1. BOOMik
          22.09.2016 18:19
          +1

          Как написал S_A — для многих вещей есть уже готовые плагины, которые позволяют обращаться к ним из кроссплатформенного кода. Хотя некоторые вещи могут быть не покрыты плагинами (сразу и не вспомню таких) или реализованы не совсем так, как требуется, тогда можно реализовать самому. Доступ ко всем системным API имеется.


      1. S_A
        22.09.2016 17:38
        +1

        есть плагины для xamarin.forms. с последнего пререлиза xaml в xamarin.forms также поддерживает и нативные компоненты. так что чем дальше в лес, тем меньше нативного кода. молодцы вообще они.


  1. artem_drozdoff
    22.09.2016 16:51

    Мне кажется в статье не хватает RestSharp.


    1. devious
      23.09.2016 12:17
      +1

      У RestSharp традиционные проблемы с PCL-проектами, поэтому для Xamarin.Forms он не очень хорошо подходит. Да и Refit на наш взгляд более удобное и простое решение. Но это все IMHO, а так RestSharp — да, хороший инструмент.