89
Федеральное государственное автономное образовательное учреждение высшего образования КАЗАНСКИЙ (ПРИВОЛЖСКИЙ) ФЕДЕРАЛЬНЫЙ УНИВЕРСИТЕТ ВЫСШАЯ ШКОЛА ИНФОРМАЦИОННЫХ ТЕХНОЛОГИЙ И ИНФОРМАЦИОННЫХ СИСТЕМ Направление подготовки: 09.03.03 Прикладная информатика ВЫПУСКНАЯ КВАЛИФИКАЦИОННАЯ РАБОТА Разработка мобильного приложения (под ОС Android) для работы с базой археологических исследований ArchGIS в полевых условиях Работа завершена: «___»_____________2017 г. Студент группы 11-304 ___________________ Н.К.Шайхразиев Работа допущена к защите: Научный руководитель Старший преподаватель ИТИС «___»_____________2017 г. ___________________ В. В. Кугуракова

kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

  • Upload
    others

  • View
    7

  • Download
    0

Embed Size (px)

Citation preview

Page 1: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Федеральное государственное автономное образовательное учреждение

высшего образования

КАЗАНСКИЙ (ПРИВОЛЖСКИЙ) ФЕДЕРАЛЬНЫЙ УНИВЕРСИТЕТ

ВЫСШАЯ ШКОЛА ИНФОРМАЦИОННЫХ ТЕХНОЛОГИЙ И

ИНФОРМАЦИОННЫХ СИСТЕМ

Направление подготовки: 09.03.03 Прикладная информатика

ВЫПУСКНАЯ КВАЛИФИКАЦИОННАЯ РАБОТА

Разработка мобильного приложения (под ОС Android) для работы с

базой археологических исследований ArchGIS в полевых условиях

Работа завершена:

«___»_____________2017 г.

Студент группы 11-304 ___________________ Н.К.Шайхразиев

Работа допущена к защите:

Научный руководитель

Старший преподаватель ИТИС

«___»_____________2017 г. ___________________ В. В. Кугуракова

Директор Высшей школы ИТИС

«___»_____________2017 г. ____________________ А. Ф. Хасьянов

Казань – 2017 г.

Page 2: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

ОГЛАВЛЕНИЕ

ОГЛАВЛЕНИЕ........................................................................................................2

ОБЩИЕ ОПРЕДЕЛЕНИЯ......................................................................................3

ВВЕДЕНИЕ..............................................................................................................5

1. Общая информация о работе........................................................................5

2. Цель работы....................................................................................................7

ТЕХНИЧЕСКИЕ ТРЕБОВАНИЯ..........................................................................9

1. Общее положение..........................................................................................9

2. Функционал программного решения.........................................................11

ПРОЕКТИРОВАНИЕ АРХИТЕКТУРЫ.............................................................12

1. Общее положение........................................................................................12

2. Clean architecture..........................................................................................13

3. Паттерн MVP................................................................................................19

4. Хранение данных.........................................................................................23

РЕАЛИЗАЦИЯ.......................................................................................................26

1. Средства разработки....................................................................................26

2. Получение токена........................................................................................29

3. Создание данных..........................................................................................32

4. Поиск данных...............................................................................................36

ЗАКЛЮЧЕНИЕ.....................................................................................................41

СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ............................................43

ПРИЛОЖЕНИЕ.....................................................................................................46

2

Page 3: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

ОБЩИЕ ОПРЕДЕЛЕНИЯ

База данных - набор сведений, хранящихся некоторым упорядоченным

способом.

Графовая база данных — разновидность баз данных с реализацией

сетевой модели в виде графа и его обобщений.

Сущность (entity) – это реальный или представляемый тип объекта,

информация о котором должна сохраняться и быть доступна.

Объекты культурного наследия – предметы материальной культуры,

возникшие в результате исторических событий, представляющие собой

ценность с точки зрения развития человечества.

Артефакт — в археологии — объект, подвергнутый в прошлом

направленному механическому воздействию, обнаруженный в

результате целенаправленных археологических раскопок или какого-

либо единичного, иногда случайного события.

Радиоуглеродный анализ — разновидность радиоизотопной датировки,

применяемая для определения возраста биологических останков,

предметов и материалов биологического происхождения путём

измерения содержания в материале радиоактивного изотопа 14C по

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

Аутентификация — проверка подлинности предъявленного

пользователем идентификатора.

Токен — электронный ключ для доступа к системе.

Авторизация — предоставление прав на выполнение, чтение и записи

ресурсов системы.

3

Page 4: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

UX — User Experience. Это опыт/впечатления, которые получает

пользователь от работы с вашим интерфейсом.

UI — user interface (пользовательский интерфейс).

ORM (Object Relational Mapping) — технология программирования,

которая связывает базы данных с концепциями объектно-

ориентированных языков программирования, создавая «виртуальную

объектную базу данных».

Request (запрос) — данные отправленные клиентом серверу.

Response (ответ) — данные отправленные сервером клиенту,

запросившему их.

4

Page 5: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

ВВЕДЕНИЕ

1. Общая информация о работе

В современном мире сложно представить себе жизнь без мобильного

телефона. Смартфоны и планшеты плотно вошли в нашу жизнь.

Возможности, которые они предоставляют огромные: ввод, обработка,

корректировка информации, прогнозирования и управления, дополнение

зрительными образами, развлечение и досуг. Многие сферы жизни человека

замещаются одним устройством, которое лежит у него кармане.

Но самым важным из всех возможностей является доступ к Интернету,

который связывает все воедино. Это показывает статистика. Так к состоянию

на март 2017 года 46% населения России заходят в Интернет со смартфонов

— прирост составил 15% за год [1], тем самым почти догнав домашние

компьютеры, которые используются для доступа в сеть 54% населения

нашей страны. Рынок мобильного интернета зависит от двух важных

факторов:

Количество пользователей смартфонов и планшетов. Во всем мире

наблюдается рост продаж мобильных устройств. Только в России

насчитывается около 80 млн. пользователей смартфонов в 2017 году

[2], что на 22% больше чем в 2015 году.

Инфраструктура и зона покрытия сети. Тут также наблюдается рост.

Количество базовых станций в сетях мобильной связи за прошлый

год выросло на 16,5 процента [3].

Благодаря росту этих двух ключевых факторов и происходит рост всего

мобильного интернета в России. Но тут есть свои проблемы. Как бы много не

вводилось новых базовых станций в России этого недостаточно. На карте

5

Page 6: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

весьма много белых пятен, то есть имеется большое количество территории,

которые не имеют доступа не только в сеть, но даже поддержки голосовой

связи. Это обусловлено масштабами нашей страны и тем, что 2/3 введенных

новых станций приходится на центральную часть России [3]. Большие

территории за Уралом не имеет доступа к мобильной сети.

В данной дипломной работе будет произведена работа с

информационной системой учета данных археологических исследований

«ArchGIS». Данная система разработана для учета, хранения и анализа всей

совокупности информации, получаемой в результате проведения

археологических исследований и используемой для решения задач

археологии как научной дисциплины.

Проблема доступа к Интернету затронута была не случайна. В силу

специфики археологической деятельности, люди данной профессии зачастую

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

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

вероятностью может отсутствовать связь с сетью. Им требуется записывать

большое количество информации: различные данные, помечать территории с

найденными артефактами, делать фотографии и другое. Сегодня они

обходятся бумажными вариантами протоколов и отчетов, которые потом

приходятся заново перезаполнять в информационной системе «ArchGIS».

Количество действий исследователей избыточна. Можно облегчить их жизнь

и предоставить готовый инструмент для работы с базой данных «ArchGIS»

прямо в полевых выездных условиях.

Было принято решение о разработке мобильного приложения для

археологической информационной системы. По данным статистических

исследований наиболее распространенной мобильной платформой являются

OS Android, iOS и Windows Phone.

6

Page 7: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Рисунок 1 - Рынок мобильных ОС [4].

Причем на долю OS Android приходится 85% рынка [Рисунок 1].

Поэтому для разработок была выбрана мобильная операционная система

Android.

2. Цель работы

Целью данной дипломной работы является разработка мобильного

приложения для работы с базой археологических исследований «ArchGIS» в

полевых условиях на базе OS Android.

Для достижения цели необходимо:

Проанализировать работу информационной системы учета данных

археологических исследований «ArchGIS» и сформулировать

требования к мобильному приложению.

7

Page 8: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Разработать архитектуру и интерфейс мобильного приложения,

удовлетворяющие общим стандартам платформы Android.

Разработать приложение с описанным ниже функционалом.

8

Page 9: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

ТЕХНИЧЕСКИЕ ТРЕБОВАНИЯ

1. Общее положение

Для того чтобы выдвинуть список технических требований следует в

первую очередь изучить функционал предоставляемый сервером. На данный

момент археологическая система «ArchGIS» является графовой базой

данных, обеспеченной интерфейсами ввода/вывода и анализа информации, а

также визуализации картографических данных. Система хранит и

обрабатывает данные о следующих сущностях:

об объектах культурного наследия

об археологических памятниках (культурных слоях)

об археологических комплексах (сооружениях, погребениях и иных

объектах)

об артефактах

об радиоуглеродных датах,

а также данные о полевых и аналитических исследованиях в области

археологии и смежных наук, в том числе:

данные о раскопках, том числе – за пределами территории

археологических объектов

отчеты о полевых исследованиях

опубликованные результаты исследований

архивные документы.

Хранимые данные могут представлять собой тексты, изображения,

трехмерные модели объектов. Все они имеют пространственную привязку (с

9

Page 10: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

указанием степени точности), что позволяет проводить анализ

пространственного расположения, как средствами самой информационной

системы, так и путем обработки выбранного массива данных в иных

геоинформационных системах.

Данные о явлениях и объектах, хранящиеся в системе (атрибуты

сущностей), могут носить как объективный, так и субъективный характер.

Это позволяет различать однозначные, не подверженные изменению

сведения об археологических объектах (постоянные атрибуты) и

информацию, имеющую характер интерпретации наблюдаемых явлений

(переменные атрибуты). Такая организация позволяет работать с данными

различной степени точности, в том числе – учитывать утраченные к

настоящему времени памятники и артефакты, а также учитывать

дискуссионный характер многих археологических интерпретаций.

Важнейшими типами переменных атрибутов являются культурные и

хронологические интерпретации археологических явлений – культурных

слоев памятников, археологических комплексов и артефактов. Наличие

переменных атрибутов позволяет использовать функционал

информационной системы для проведения историко-научных изысканий.

Имеющиеся в системе интерфейсы позволяют осуществлять:

внесение, изменение и отображение хранящихся в ней данных,

осуществление выборки данных из всей совокупности на основе

атрибутов сущностей и связей между ними,

картографическую визуализацию любого выбранного массива

данных, имеющих пространственную привязку;

простейший статистический анализ.

В целом, имеющийся на настоящий момент в системе функционал

позволяет сочетать элементы пространственного анализа с хронологическим 10

Page 11: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

исследованием, построенным на основании различных видов источников

(типологии артефактов, радиоуглеродного датирования, стратиграфических

наблюдений).

Также на сервере присутствует этапы аутентификации, после которой

пользователю предоставляется токен для авторизации в системе.

Но также стоит учитывать специфику выездных работ, для которых в

первую очередь предназначено приложение. Приложение должно работать в

условиях отсутствия сети. Данные, полученные исследователями, должны

сохраняться сначала на устройстве, а потом автоматически

синхронизироваться с сервером.

2. Функционал программного решения

На основании вышеперечисленного, к функционалу разрабатываемого

приложения предъявляются следующие требования:

Получение токена для доступа к данным сервера на странице

авторизации

Сохранение новых исследовательских данных на устройстве

Синхронизация данных с сервером

Быстрый и расширенный поиск данных

Распознавание и отображение картографических данных

Отображение подробных данных на отдельной странице.

11

Page 12: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

ПРОЕКТИРОВАНИЕ АРХИТЕКТУРЫ

1. Общее положение

Прежде чем приступить к разработке мобильного приложения, нужно

заняться построением архитектуры в клиент-серверном Android-приложении.

Важность данной стадии на первый взгляд неочевидна. Пользователю

совсем безразлична архитектура приложения. Слова MVC, MVP или MVVM

ничего не значат для потребителя. Главное приложение должно быть

рабочим. Долгое время, с релиза первых мобильных устройств под

управлением OS Android до 2014 года, разработчики также работали по

данному принципу.

Первая версия Android выходила в спешке, для того чтобы успеть

угнаться за Apple на мобильном рынке. В приоритете были основные

пользовательские компоненты, а заботы об удобстве разработчиков были

вторичны. Google не предоставил разработчикам никаких стандартов ни по

разработке, ни по дизайну и UX. Из-за этого вытекали соответствующие

проблемы.

Во-первых, дизайн. Разные стили приложений смущали пользователей

системы Android и мешали им ориентироваться. Пользователь не знал чего

ему ожидать, и былой опыт взаимодействия не всегда был применим.

Во-вторых, стандарты разработки. Не все разработчики хорошо

владеют паттернами проектирования и умеют разрабатывать правильную

архитектуру приложений. Если не следовать принципам архитектуры, то

скоро можно получить код, который будет:

Невозможно поддерживать. Будет много сложной логики,

расположенной в одном месте без ясности, что за что отвечает. Из

этого следует, что при добавлении нового функционала придется либо 12

Page 13: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

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

правильно, либо сделать задачу кое-как, то есть, образно говоря, через

костыли. А каждый новый функционал, реализованный через

костыли, является причиной дополнительных ошибок.

Невозможно тестировать. Нет возможности писать модульные тесты,

если все приложение есть один большой модуль. Более того, в силу

особенностей написания тестов для Android-приложений на JVM [5],

при большом количестве зависимостей от классов Android в

тестируемых классах, вы не сможете писать тесты.

Такая ситуация продолжалась достаточно долго. Приложения под

Android продолжались писаться в разных стилях с абсолютно разными

подходами в дизайне и в архитектуре. Ситуация изменилось только в 2014

году, когда произошло два события.

Первое, Google анонсировали новую концепцию дизайн-системы -

Material Design [6]. Новый подход позволил создавать консистентный

пользовательский опыт на всех экранах: десктоп, смартфон, планшеты, часы,

телевизоры, машины. Для Android-приложений Material Design представляет

собой эволюцию визуального языка Holo и дизайн-гайдлайнов. Во многих

смыслах это более гибкая система, которая создавалась специально для

других дизайнеры, а не только для работников Google.

Второе, в этом же году вышла статья от Fernando Cejas: «Architecting

Android...The clean way?» [7], в которой рассматривали принципы Clean

Architecture [8]. Это статья дала огромный толчок в развитии архитектуры

мобильных приложений.

2. Clean architecture

13

Page 14: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Существует много подходов для построения систем с хорошей

архитектурой. Несмотря на различия у них много общего. Они все задают

способы разделения приложения на модули и в каждой системе при этом

есть часть, отвечающая за бизнес-логику и часть, отвечающая за

представления данных. И каждый подход позволяет построить систему,

которая удовлетворяет следующим принципам:

Архитектура не должна зависеть от фреймворков [8]. Библиотека

должна встраиваться в вашу архитектуру, а не архитектура должна

подстраиваться под выбранную библиотеку.

Система должна быть тестируема [8]. При этом должна быть

возможность тестировать как модули системы по отдельности, так и

взаимодействие между ними, и интеграцию их в систему.

Приложение не должно быть зависимо от всего: от работы сервера, от

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

окружения [8]. Независимость от окружения очень важна, так как она

позволяет менять различные компоненты системы без изменения ее

архитектуры. Это могут быть изменения в выборе базы данных или же

изменения представления.

Для построения архитектуры системы рассмотрим следующую схему,

где каждая концентрическая окружность является определенным

компонентом системы:

14

Page 15: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Рисунок 2 - Clean Architecture.

Как видно из схемы, система состоит из бизнес-объектов, объектов для

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

высокоуровневых фреймворков.

Такое разделение системы на слои является весьма логичным и

понятным. И пока оно не привносит ничего нового. Главным является

правило зависимостей – ни один внутренний слой не должен знать ничего о

внешнем. Именно это и позволяет строить независимую архитектуру,

принципы которой были описаны выше.

Учитывая особенности тестирование Android-приложений [5],

наиболее важным является обеспечение независимости слоя бизнес-логики, в

которой не должно быть Android-компонентов, так как JVM ничего не знает

об Android. Отсутствие Android-кода позволяет тестировать бизнес-логику

стандартными средствами – Junit [9].

Для обеспечения независимости и тестируемости предлагается разбить

приложение на три ключевых слоя:

15

Page 16: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Слой данных (Data Layer)

Слой бизнес-логики (Domain Layer)

Слой представления (Presentation Layer)

Рисунок 3 - Схема ключевых слоев Android Clean Architecture .

Рассмотрим указанные слои подробнее.

Слой данных

Данный компонент отвечает в первую очередь за получение данных из

разных источников и их кэширование. Например, когда необходимо

получить какого-то пользователя по имени, реализация репозитория

проверяет наличие этого пользователя в локальном хранилище, при

отсутствии сохраненного пользователя она отправляет запрос на сервер и

получает ответ, который сохраняется в локальное хранилище и возвращается.

Или, к примеру, репозиторий может всегда обращаться к серверу, а уже в

случае ошибки возвращать сохраненный результат.

16

Page 17: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Рисунок 4 - Схема слоя данных.

Преимуществом такого подхода является то, что другие слои, которые

запрашивают данные, не знает о происхождении этих данных. Единственное,

что они должны знать, это то, что данные будут получены, и, не важно,

хранятся ли они в памяти, в кэше или на облаке.

Слой бизнес-логики

На слое бизнес-логики содержится, как ни странно, вся бизнес-логика

приложения. Именно к этому слою обращается слои представления для

выполнения запросов и получения данных.

Слой бизнес-логики реализуется в виде Java-модуля, который не

содержит никаких зависимостей от Android-классов. И это хороший подход,

так как для реализации бизнес-логики нам нужны только классы моделей и

стандартные средства языка Java. Более того, такой подход позволит легко

тестировать этот слой с помощью обычных тестов на JUnit, что очень

удобно. В таком случае иногда не будет возможности выполнить какой-либо

метод или использовать некоторые классы из других слоев. Поэтому для

взаимодействия с этим слоем используются интерфейсы.

Слой представления17

Page 18: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

И, разумеется, приложение – это в первую очередь взаимодействие с

пользователем. Поэтому нам нужен специальный слой, который будет

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

пользователем и за другие процессы, связанные с UI. Этот слой не должен

содержать логику приложения, не связанную с UI.

Именно этот слой привязывается к экранам и помогает организовать

взаимодействие со слоем бизнес-логики и работу с данными. Этот слой

может быть реализован с использованием любого предпочитаемого паттерна,

к примеру, MVC, MVP, MVVM и других.

В данном приложение будет использоваться паттерн MVP, который

позволяет разделить экран на UI-часть (View), на логику работы с UI

(Presenter) и объекты для взаимодействия с UI (Model). В общем виде этот

паттерн выглядит следующим образом:

Рисунок 5 - Паттерн MVP.

Так выглядит архитектура клиент-серверного приложения в общем

виде. Но зачастую такая схема избыточная. Слой бизнес-логики почти не

используется и зачастую реализуется на сервере (логика описывается один

раз на сервере, а не для каждой мобильной платформы в отдельности).

Поэтому слой бизнес-логики зачастую можно убрать, а ту небольшую часть,

которая останется в приложении перенести в Presenter. Таким образом, мы

18

Page 19: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

избавляемся от лишнего слоя, лишних взаимодействий и лишних моделей,

упростив в конечном случае реализацию отдельных экранов. Другие слои

остаются не тронутыми. Слой данных скрывает запросы к серверу

кэширование, а слой представления взаимодействует с пользователем, и

поэтому они никуда исчезнуть не могут.

Рисунок 6 - Итоговая схема приложения.

При этом мы не потеряем возможность тестирования, и сохраним

модульность архитектуры приложения. Более того, теперь у нас есть один

основной элемент, который содержит всю логику конкретного экрана – это

делегат (Controller / Presenter / ViewModel). И поэтому для упрощения почти

всегда достаточно тестировать только его.

Поскольку работа делегата полностью зависит от того, какие данные он

получает от сервера, для тестирования делегата достаточно подменять

реализацию репозитория.

Это общее теоретическое изложение, которое нужно для понимания

архитектуры клиент-серверного Android-приложения.

3. Паттерн MVP

Основная идея любого из паттернов MVP, MVC, MVVM заключается в

разделении логики и UI-части приложения так, чтобы их можно было 19

Page 20: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

тестировать по отдельности. При этом сами паттерны достаточно сильно

отличаются между собой, поэтому рассмотрим их, чтобы понять, почему в

Android в первую очередь используется MVP.

Сложно не заметить, что все эти паттерны содержат View и Model, а

отличия заключаются в последнем элементе, который управляет логикой. В

случае MVC это Controller, в случае MVP – Presenter, в MVVM – ViewModel,

который иногда называют делегатом. Разумеется, различия заключаются не

только в названии, но и в том, как именно делегат управляет логикой и

взаимодействует с View и Model.

Начнем рассмотрение паттернов с их общих частей – View и Model:

Model. Весь слой данных в приложении: это и бизнес-объекты,

содержащие логику, и способ их получения (паттерн Repository [10]),

и какие-то менеджеры и другие элементы, относящиеся к данным.

View. View отображает данные, получаемые либо от Model, либо от

делегата, что зависит от конкретного паттерна. View – эта та часть

системы, которая отображает и взаимодействует с пользователем. При

этом View не должна содержать логику, а передавать результаты

взаимодействия делегату, который будет управлять этой View.

Самым известным паттерном, конечно, является MVC, в котором

делегатом является Controller. Схема этого паттерна выглядит следующим

образом:

20

Page 21: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Рисунок 7 - Паттерн MVC.

Когда пользователь взаимодействует со View (к примеру, нажимает на

кнопку), View передает информацию об этом действии в Controller. Controller

обрабатывает это событие в соответствии с логикой системы и изменяет

Model. View отслеживает состояние модели, поэтому при изменении Model

View получает уведомление и отображает новую информацию [11]. Это

активная модель, которая применяется чаще всего. Также существует

пассивная модель, в которой View обновляется через Controller.

И есть еще один важный момент – Controller может управлять

несколькими View, при этом Controller определяет, какая именно View будет

отображаться в текущий момент. Именно из-за этой особенности паттерн

MVC не слишком удобно применять в Android. В Android в роли View чаще

всего выступает Activity, которую просто так сменить, разумеется,

невозможно. Есть определенный вариант использования MVC в Android,

когда разными View являются фрагменты, которые переключает Controller.

Но, зачастую это неэффективно и ведет к созданию перегруженного логикой

и сущностями Activity.

MVP имеет несколько основных отличий от MVC. Во-первых, Presenter

управляет только одной View и взаимодействует с ней через специальный

21

Page 22: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

интерфейс. Во-вторых, View управляется только с помощью Presenter-а, а не

отслеживает изменение Model-и. View в случаи MVP не экран, который мы

видим, а его абстракция, на которую подписан Presenter. Presenter получает

все данные из Model (слоя данных), обрабатывает их в соответствии с

требуемой логикой и управляет View [11]. Схема паттерна MVP выглядит

следующим образом:

Рисунок 8 - Паттерн MVP.

Подведя итоги, можно сделать следующие выводы:

Во-первых, архитектура включает слой данных. Слой данных

представляет собой объект Repository, который выполняет

работу с серверными запросами, кэшированием ответа,

хранением данных и первичной обработкой ошибок.

Во-вторых, основой архитектуры является паттерн MVP.

Presenter – это объект, который управляет отображением данных

через специальный интерфейс View. Presenter также обращается к

репозиторию (model) за получением или передачи

пользовательских данных. View – это интерфейс, содержащий

методы для работы с UI, который реализуется в Activity или

Fragment. 22

Page 23: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

4. Хранение данных

Есть множество способов хранения данных в мобильном приложении:

shared preferences, files (обычные файлы на диске) и базы данных.

Shared Preferences – xml-файл хранилище типа ключ-значение для

примитивных типов данных. Основное назначение – хранение некоего

состояния приложения и пользовательских настроек [12]. Для хранения

множества однотипных структурированных данных не подходит.

В качестве БД выступает SQLite, которая доступна на каждом

устройстве Android по умолчанию. SQLite – реляционная база данных, для

работы с который используются несколько классов-помощников:

SQLiteOpenHelper, ContentValues и т.д. [13]. Но работа с SQLite

сопровождается огромным количеством шаблонного кода в виде:

самостоятельной реализации создания, обновления таблиц, базовых CRUD

операций и т.д. Есть два решения проблемы:

Использовать готовые ORM решения, которые берут на себя большую

часть шаблонного кода, позволяя работать с объектной базой данных,

избавляя от беспокойства о структуре таблиц внутри SQLite.

Отказаться от SQLite и использовать другую базу данных.

И такой альтернативой SQLite является Realm. Realm – специально

созданная NoSQL база данных для мобильных приложений [14], обладающая

рядом преимуществ:

Скорость работы. В зависимости от задачи, преимущество

достигается более двух раз над SQLite [15].

Кросс-платформенность. Файлы базы данных могут совместно

использовать iOS и Android.

23

Page 24: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Простата. Realm работает непосредственно с java-объектами, а не

конвертирует их в sql. Для того чтобы java-объект стал частью Realm

нужно наследоваться от RealmObject или реализовать интерфейс

RealmModel.

Поддержка отношений one-to-one (1..1), one-to-many (1..*), many-to-

many (*..*) реализованная через RealmList — наследника коллекции

List.

Бесплатность использования.

Таким образом, в текущем проекте для хранения локальных данных

будет использоваться база данных Realm.

После анализа системы «ArchGIS» была спроектирована следующая

схема отношений в БД:

24

Page 25: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Рисунок 9 - Схема базы данных.

25

Page 26: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

РЕАЛИЗАЦИЯ

1. Средства разработки

В качестве среды разработки был выбран Android Studio текущей

версии (2.3.1), а Gradle 3.14 будет системой сборки. Дальше нужно выбрать

версию операционной системы для разработки. Android позволяет управлять

совместимостью приложения с одной или несколькими версиями платформ с

помощью целочисленного уровня API [16]. Уровень API, указанный в

приложении, будет сравниваться с уровнем API данной системы Android,

который может различаться для разных устройств. Здесь есть два

обязательных параметра:

targetSdkVersion - указывает число API Level, для которого

приложение разработано [16]. Выбирается последняя доступная на

данный момент версия, которая позволяет использовать весь набор

доступных сейчас инструментов.

minSdkVersion - минимальное значение версии платформы Android, на

которой приложение будет работать [16]. Уровень API Level

ограничивает разработчика и ему приходится обрабатывать ситуации,

которые были недоступны в более ранних версиях Android. Для

совместимости версий используются специальные support library [17].

Если выбор targetSdkVersion не вызывает вопросов, то для выбора

минимальной версии стоит изучить степень распространенности

операционных систем Android.

26

Page 27: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Рисунок 10 - Распространённость версий OS Android [18].

Как видно из графика, достаточно выбрать уровень API 16(Jelly Bean

4.1.x), для того чтобы приложение поддерживалось на 98,2 % устройств во

всем мире [Рисунок 10]. Этого более чем достаточно. Выбор более ранних

версий неоправданно увеличивает трудозатраты на разработку.

Также в проекте будут использоваться следующие библиотеки:

OkHttp 3.8 и Retrofit 2.3 для работы с сетью.

RxJava2 и RxAndroid2 для работы с многопоточностью.

Retrolambda для использования лямбда-выражений.

Picasso 2.5.2 для работы с изображениями.

Butterknife 8.6 для связывания (binding) элементов со View.

Google Map Api и Google Location для картографических работ.

27

Page 28: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Отдельно нужно сказать пару слов о RxJava и RxAndroid. RxJava – это

функциональное реактивное программирование, для работы с асинхронными

потоками данных, которыми можно манипулировать с помощью различных

функций. RxAndroid — расширения RxJava для Android, которые помогут

работать с потоками в Android. Базовыми строительными блоками

реактивного кода являются Observables и Subscribers. Observable является

источником данных, а Subscriber — потребителем. Порождение данных через

Observable всегда происходит в соответствии с одним и тем же порядком

действий: Observable «излучает» некоторое количество данных (в том числе,

Observable может ничего и не излучать), и завершает свою работу — либо

успешно, либо с ошибкой. Для каждого Subscriber, подписанного на

Observable, вызывается метод Subscriber.onNext() для каждого элемента

потока данных, после которого может быть вызван как

Subscriber.onComplete(), так и Subscriber.onError() [19].

Можно выделить следующие основные преимущества реактивного

кода:

Обеспечение многопоточности. RxJava позволяет гибко управлять

асинхронностью выполнения запросов, а также переключать

выполнение операций в различные потоки. Кроме того, что

немаловажно для Android, RxJava также позволяет легко обрабатывать

результат в главном потоке приложения.

Управление потоками данных. Это позволяет преобразовывать

данные в потоке, применять операции к данным в потоке (к примеру,

сохранять их данные из потока в базу), объединять несколько потоков

в один, изменять поток в зависимости от результата другого и многое

другое.

Обработка ошибок. Это еще одно очень важное преимущество RxJava,

которое позволяет обрабатывать различные ошибки, возникающее в

28

Page 29: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

потоке, повторять серверные запросы в случае ошибки и передавать

ошибки подписчикам.

Также при разработки пришлось воспользоваться «Charles Web

Debugging Proxy» [20] для сканирования трафика между сервером и

клиентом (веб-браузер в данном случаи). Charles позволяет записывать и

отображать все данные, которые передаются и получаются при работе с

сетью. По анализу этих данных строятся все запросы к серверу от

приложения.

2. Получение токена

Для получения доступа к системе «ArchGIS» сначала в нем нужно

авторизоваться. Авторизация состоит из:

Заполнения полей пользователем

Валидация на клиенте

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

Получения ответа от сервера с токен-ключом при успешной

авторизации или с сообщением об ошибке в ином случаи.

При нажатии на кнопку «Вход» осуществляется первые два пункта:

получение введенных данных и валидация:

String email = mEtEmail.getText().toString();String password = mEtPassword.getText().toString();int err = 0;if (!validateFields(email)) { err++; mTiEmail.setError(getResources().getString(R.string.validate_email).toString());}if (!validateFields(password)) { err++; mTiPasswor

29

Page 30: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

d.setError(getResources().getString(R.string.validate_pass).toString());}if (err == 0) { loginProcess(email,password); mProgressBar.setVisibility(View.VISIBLE);

} else { showSnackBarMessage(getResources().getString(R.string.validate_error).toString());}

После чего, методом loginProcess(), данные передаются Presenter-у, который в свою очередь передает их в слой данных (репозиторий) и подписывается на его ответ:

RepositoryProvider.provideUserRepository() .login(email, password) .subscribe(mView::handleResponse, mView::handleError);

Репозиторий, получив данные от presenter-а, формирует из них RequestBody и передает слою работы с сетью (retrofit), который уже отправляет запрос на сервер. Репозиторий подписывается на ответ сервера:

@Overridepublic Observable<Response> login(String email, String password){ RequestBody requestEmail = RequestBody.create( MediaType.parse("multipart/form-data"), email); RequestBody requestPassword = RequestBody.create( MediaType.parse("multipart/form-data"), password); return ApiFactory.getUserService() .login(requestEmail, requestPassword) .compose(RxUtils.async());}

Класс RxUtils управляет многопоточностью c помощью средств

обеспечения асинхронности в RxJava:

@NonNullpublic static <T> ObservableTransformer<T, T> async() { return observable -> observable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread());}

После того как данные начнут поступать от сервера, они будут

переданы подписчику, то есть presenter-у, который в зависимости от

успешности запустит следующее окно (handleResponse()) или же отобразит

ошибку (handleError()):

@Overridepublic void handleResponse(Response response) {

30

Page 31: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

mProgressBar.setVisibility(View.VISIBLE); PreferencesManager.saveToken(response.getToken(), getActivity().getApplicationContext()); Intent intent = new Intent(getActivity(), MainActivity.class); startActivity(intent);}

При успешной авторизации, метод handleResponse также сохраняет

полученный токен в SharedPrefence для дальнейшего использования:

public static void saveToken(@NonNull String token, Context context) { SharedPreferences prefs = context.getSharedPreferences(ARCH_GIS_PREFS, Activity.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putString(TOKEN, token); editor.apply();}

В таком ключе взаимодействуют слои в Android Clean Architecture при

помощи реактивного программирования:

Рисунок 11 - Реактивный подход к Android Clean Architecture.

В дальнейшем при каждом сетевом запросе в header будет добавляться

этот токен для авторизации на сервере:

@Overridepublic Response intercept(Chain chain) throws IOException { Request request = chain.request(); String token = PreferencesManager.getToken(context);

31

Page 32: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Headers.Builder header = new Headers.Builder(); if (token != null && !token.isEmpty()) { header.add("Authorization", String.format("Bearer %s", token)); } Request newRequest = request.newBuilder() .headers(header.build()) .build(); return chain.proceed(newRequest);}

Рисунок 12 - Страница авторизации и полученный токен.

3. Создание данных

Внесение данных начинается с выбора того, что будет создаваться, и на

чем это будет базироваться. На текущий момент, на основе отчета или

публикации, можно создать:

Артефакт

Радиоуглеродную датировку

32

Page 33: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Памятник

Исследование

Рисунок 13 - Конфигурация создаваемого объекта.

В зависимости от выбранных параметров будет создано представление

для следующего этапа, в котором пользователю предстоит заполнять данные.

Все поля соответствуют столбцам и отношениям, которые указаны в схеме

базы данных выше. Большинство из них не обязательны , а необходимые же

поля валидируются.

Представление состоит из одного activity (отчет/публикация) и

множества содержащихся в нем fragment-ов, каждый их которых отвечают за

отображение одной сущности. На данный момент реализовано 14 разных

fragment-ов. В момент создания представления также инициализируется

конкретная реализация интерфейса BasePresenter, с ограниченной зоной

ответственности в виде создаваемого объекта.

33

Page 34: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

presenter = new MonumentPresenter(researchFragment, simpleChsFragment, monumentFragment, excavationFragment, photoFragment, planFragment, this);

Конструктор presentora-a состоит из интерфейсов view, которые

реализуются во фрагментах (представление). Таким образом, он получает

данные с каждого представления сущности, нужного для создания новой

сущности и одновременно подписан на событие в activity, которое доступно

для всех fragment-ов.

Рисунок 14 - Структура MVP для создания сущности.

Каждый класс в базе данных содержит маркер isModified обозначающий то, что мы должны данные этого класса загрузить на сервер.

public class Artifact extends RealmObject{

@PrimaryKey private Long id;

/*another fields */ private boolean isModified;}

Когда пользователь нажимает на кнопку «Создать» запускается метод save() из presentora-a:

Artifact artifact = artifactView.validatesData();Monument monument = monumentView.validatesMonument();

34

Page 35: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

/*another sets of fields*/artifact.setMonument(monument);artifact.setModified(true);

RepositoryProvider.provideArtifactRepository() .saveArtifact(artifact) .doOnSubscribe(mView::showLoading) .doOnTerminate(mView::hideLoading) .subscribe(mView::handleResponse , mView::handleError);

Созданный объект передается репозиторию, который сначала его

сохраняет в локальную базу данных:

executeTransaction(realm -> { if (data.getId()!= null){ realm.insertOrUpdate(data); }else{/*Генерация primary key для нового объекта(В Realm нет автогенереации) */ long nextId = nextKey(realm, Artifact.class); data.setId(nextId); realm.insertOrUpdate(data); }});getAllModified() .flatMapIterable(list -> list) .map(this::postArtifact) .subscribe(this::handleResponse , this::errorResponse);return responses;

Все помеченные маркером (не загруженные на сервер) данные

извлекаются из базы и передаются по одному в метод postArtifact(), который

в свою очередь создает RequestBody для каждого поля сущности и передает

их серверу:

RequestBody requestResearch = RequestBody.create( MediaType.parse("multipart/form-data"), "id:" + String.valueOf(data.getResearch().getId()));return ApiFactory.getArtifactService() .post(requestAuthor, …… , requestResearch) .compose(RxUtils.async());

При успешном внесении данных на сервер (положительный response) у

сущности изменяется маркер на false, указывая на то, что его не нужно

синхронизировать. При обновлении данных маркер вновь измениться на

положительный (используется тот же метод, что и для сохранения).

Artifact artifact = realm.where(Artifact.class).equalTo("id", data.getId()).findFirst();if (artifact != null) { artifact.setModified(false);}

35

Page 36: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

В итоге пользователь получает сообщение об успешном выполнении

операции или сообщение с типом ошибки (через написанный адаптер

RetrofitExceptionAdapter), а данные будут синхронизированы в следующий

раз. В дальнейшем планируется создать пункт в меню «Синхронизировать

данные» или полностью автоматизировать данный процесс при подключении

к Интернету.

Рисунок 15 - Экран создания данных.

4. Поиск данных

Есть два вариант поиска данных:

Быстрый поиск по определенным сущностям.

Расширенный поиск с множеством критерий.

На странице быстрого поиска пользователь выбирает одну из 7

сущностей:

36

Page 37: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Автор

Вскрытие

Исследование

ОКН

Отчет

Памятник

Радиоуглеродная датировка

Для которых задает конкретные критерии поиска. Для большинства

сущностей это имя автора и год внесения данных. В зависимости от

выбранной сущности запускается окно с результатами конкретной сущности.

Intent intent = new Intent(this, ReportListActivity.class);String author = mEtAuthorReport.getText().toString();String year = mEtYearReport.getText().toString();intent.putExtra(Constants.SEARCH_AUTHOR_EXTRAS, author);intent.putExtra(Constants.SEARCH_YEAR_EXTRAS, year);startActivity(intent);

Рисунок 16 - Экран ввода критериев быстрого поиска.37

Page 38: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

В новой activity получают критерии поиска и передают их presenter-у

во время инициализации представления. Presenter обращается к репозиторию

за данными:

public void init(String author, String year) { RepositoryProvider.provideReportRepository() .reports(author, year) .doOnSubscribe(mView::showLoading) .doOnTerminate(mView::hideLoading) .subscribe(mView::showItems, mView::showError());}

При старте загрузки данных пользователю выводится прогресс бара и

скрывается после окончания запроса. Repository передает значения service,

который делает запрос на сервер. Полученные данные кэшируются, чтобы в

случаи ошибки пользователь получил результат из истории своих запросов, с

которым он может продолжить работу.

return ApiFactory.getAuthorService() .getAuthors(author, year) .flatMap(new RealmRewriteCache<>(Author.class)) .onErrorResumeNext(new RealmCacheErrorHandler<>(Author.class)) .compose(RxUtils.async());

Рисунок 17 - Экран результатов поиска.

38

Page 39: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

По нажатию на результат поиска появляется следующее окно с

дополнительной информацией о сущности. Также пользователь может

просмотреть результаты на карте. В toolbar-е есть кнопка для открытия

карты.

В качестве карты выступает Google Map Api [21]. Его легко

интегрировать в приложение (нужно лишь получить api-key) и оно доступно

на любом устройстве под управлением OS Android. В зависимости от

вызывающего activity (отвечает за одну сущность) инициализируется

соответствующий presenter, для работы с данным объектом на карте.

Принципы Clean Architecture сохраняются и при работе с картой. Presenter

запрашивает данные из репозитория, который в свою очередь делает запрос к

базе данных. Запрос на сервер в данном случаи излишен, нужные данные

были получены на предыдущем шаге. Текущее местоположение и

координаты объектов отображаются на карте.

Рисунок 18 - Результаты поиска на карте.

При долгом нажатии на маркере открывается новое окно с

дополнительной информацией об объекте.

39

Page 40: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

Во время расширенного поиска пользователь выбирает одну из трех

доступных сущностей:

Автор

Памятник

Исследование

К которым добавляет неограниченное количество критерий. Критерии

представляют собой определенное поле и его значения для поиска. Результат

отображается аналогично быстрому поиску.

Рисунок 19 - Экран расширенного поиска.

40

Page 41: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

ЗАКЛЮЧЕНИЕ

В ходе проделанной работы, по сформулированным требованиям был

разработан прототип мобильного приложения для работы с информационной

системой учета данных археологических исследований «ArchGIS».

«ArchGIS» - уникальная база данных с историей нашей страны, которая

пополняется с каждым днем благодаря усилиям археологов. Приложение

разрабатывалось специально для исследователей, которые проводят, в том

числе и выездные полевые работы, поэтому приложение минимально

зависимо от доступа к сети. Для автономной работы в приложение есть своя

локальная база данных, в которую всегда сохраняются данные в

независимости от того есть ли подключение к Интернету или нет. Это

обеспечивает сохранность и надежность данных. Синхронизация с сервером

происходят по маркерам, хранящимся в каждой сущности. Сейчас можно

создать 4 базовых объекта на основании отчета или публикации. В будущем

предстоит еще реализовать сценарий создания объекта культурного

наследия.

Также в приложение был реализован функционал быстрого и

расширенного поиска данных. Поиск осуществляется по серверу, но при

отсутствии сети, данные берутся из базы данных, в которой хранится история

из последних 5 запросов по каждой сущности. Результаты поиска

отображаются в виде списка или маркерами на карте. Сейчас метки на карте

содержат лишь основные поля, а для получения дополнительной

информации необходимо открывать новое окно. Поэтому в дальнейшем

предстоит кастомизировать маркеры, чтобы все данные были доступны сразу

на карте.

Так же в будущем, для большей автономности приложения, предстоит

реализовать функцию загрузки заранее выбранных данных, которые

понадобятся на выездных исследованиях. Архитектура приложения 41

Page 42: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

разработана по стандартам Android Clean Archetechture, что позволяет не

прикладывать дополнительние усилия для добавления нового функционала.

Клиент легко может развиваться вместе с основной системой.

42

Page 43: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ

[1]Аудитория пользователей Интернета в России [Электронный ресурс].

Режим доступа: http :// mediascope . net / press / news /744498/ (дата обращения:

27.03.2017).

[2]Количество пользователей мобильных устройств в России [Электронный

ресурс]. Режим доступа: https://www.statista.com/statistics/467166/forecast-

of-smartphone-users-in-russia/ (дата обращения: 27.03.2017).

[3]Количество базовых станций в сетях мобильной связи [Электронный

ресурс]. Режим доступа: https :// rkn . gov . ru / news / rsoc / news 43427. htm (дата

обращения: 19.04.2017).

[4]Статистика рынка мобильных операционных систем [Электронный

ресурс]. Режим доступа: https://www.statista.com/statistics/272307/market-

share-forecast-for-smartphone-operating-systems/ (дата обращения:

28.04.2017).

[5]Особенности тестирования приложений на мобильных устройствах

[Электронный ресурс]. Режим доступа:

http :// www . ringames . ru / testirovanie /154- osobennosti - testirovaniya -

prilozhenij - na - mobilnykh - ustrojstvakh . html (дата обращения: 14.04.2017).

[6]Обзор Material Design. [Электронный ресурс]. Режим доступа:

https :// habrahabr . ru / company / redmadrobot / blog /252773/ (дата обращения:

25.03.2017).

[7]Статья об Android Clean Architecture. [Электронный ресурс]. Режим

доступа:https://fernandocejas.com/2014/09/03/architecting-android-the-clean-

way/ (дата обращения: 11.04.2017).

[8]Clean Code: A Handbook of Agile Software Craftsmanship 1st Editionby

/Robert C. Martin, 2009. - P. 124, 156-160, 166-169.43

Page 44: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

[9]Тестирование библиотекой Junit [Электронный ресурс]. Режим доступа:

https :// developer . android . com / training / testing / start / index . html (дата

обращения: 14.04.2017).

[10] Repository Pattern [Электронный ресурс]. Режим доступа:

https://martinfowler.com/eaaCatalog/repository.html (дата обращения:

20.04.2017).

[11] Обзор паттернов MVC, MVP[Электронный ресурс]. Режим доступа:

http://rsdn.org/article/patterns/ModelViewPresenter.xml (дата обращения:

29.04.2017).

[12] Xранения данных в Shared Preferences [Электронный ресурс]. Режим

доступа: https :// developer . android . com / guide / topics / data / data -

storage . html # pref (дата обращения: 06.04.2017).

[13] Xранения данных в базе данных SQLite [Электронный ресурс]. Режим

доступа: https://developer.android.com/guide/topics/data/data-storage.html#db

(дата обращения: 06.04.2017).

[14] Realm Java [Электронный ресурс]. – Режим доступа:

https://realm.io/docs/java/latest/ (дата обращения: 06.04.2017).

[15] Скорость работы базы данных Realm [Электронный ресурс]. Режим

доступа: https://news.realm.io/news/introducing-realm/ (дата обращения:

06.04.2017).

[16] Совместимость приложения на разных OS Android [Электронный

ресурс]. Режим доступа:

https://developer.android.com/guide/topics/manifest/uses-sdk-element.html

(дата обращения: 5.05.2017).

[17] Обзор Suppot Libary [Электронный ресурс]. Режим доступа:

https://developer.android.com/topic/libraries/support-library/features.html /

(дата обращения: 5.05.2017).44

Page 45: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

[18] Распространённость версий OS Android [Электронный ресурс]. Режим

доступа: https://www.statista.com/statistics/271774/share-of-android-

platforms-on-mobile-devices-with-android-os/ (дата обращения: 16.05.2017).

[19] Reactive Programming with RxJava / Tomasz Nurkiewicz, Ben

Christensen , 2016.- P.1-25, 34, 277-291.

[20] Charles Web Debugging Proxy [Электронный ресурс]. Режим доступа:

https://www.charlesproxy.com/documentation/ (дата обращения: 30.04.2017).

[21] Google Map Api [Электронный ресурс]. Режим доступа:

https :// developers . google . com / maps / documentation / android - api /? hl = en (дата

обращения: 11.05.2017).

45

Page 46: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

ПРИЛОЖЕНИЕ

1. Инициализация фабрики для работа с сетью:

public final class ApiFactory {

private static OkHttpClient sClient;

private static volatile UserService userService; /**other services and fields*/ private static volatile Gson mgson;

private ApiFactory() { }

@NonNull public static UserService getUserService() { UserService service = userService; if (service == null) { synchronized (ApiFactory.class) { service = userService; if (service == null) { service = userService = buildRetrofit().create(UserService.class); } } } return service; }

/**other builders for services */

@NonNull private static Retrofit buildRetrofit() { return new Retrofit.Builder() .baseUrl(Constants.BASE_URL) .client(getClient()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); }

@NonNull private static OkHttpClient getClient() { OkHttpClient client = sClient; if (client == null) { synchronized (ApiFactory.class) { client = sClient; if (client == null) { client = sClient = buildClient(); } } } return client; }

@NonNull private static OkHttpClient buildClient() {

46

Page 47: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

return new OkHttpClient.Builder() .addInterceptor(TokenInterceptor.create(App.getContext())) .build(); }

public interface UserService {

@Multipart @POST("login") Observable<Response> login(@Part("username") RequestBody name, @Part("password") RequestBody pass);

}

public final class TokenInterceptor implements Interceptor {

private Context context;

private TokenInterceptor(Context context){ this.context = context; } @NonNull public static Interceptor create(Context context) { return new TokenInterceptor(context); }

@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); String token = PreferencesManager.getToken(context); Headers.Builder header = new Headers.Builder(); if (token != null && !token.isEmpty()) { header.add("Authorization", String.format("Bearer %s", token)); } Request newRequest = request.newBuilder() .headers(header.build()) .build(); return chain.proceed(newRequest); }}

2. Пример модели.

public class Author extends RealmObject implements ListItem,Serializable {

@PrimaryKey @SerializedName("id") private Long id; @SerializedName("name") private String name; @SerializedName("birth") private String year;

@Override public Long getId() { return id; }

public void setId(Long id) { this.id = id; }

47

Page 48: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

@Override public String getName() { return name; }

@Override public String getType() { return null; }

@Override public String getEpoch() { return null; }

public void setName(String name) { this.name = name; }

@Override public String getYear() { return year; }

@Override public String getAuthor() { return null; }

@Override public List<String> getX() { return null; }

@Override public List<String> getY() { return null; }

public void setYear(String year) { this.year = year; }}

3. Фабрика repository и пример конкретного repository

public final class RepositoryProvider {

private static UserRepository sUserRepository;

private RepositoryProvider() { }

@NonNull public static UserRepository provideUserRepository() { if (sUserRepository == null) { sUserRepository = new DefaultUserRepository();

48

Page 49: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

} return sUserRepository; }

public class DefaultArtifactRepository extends BaseRepository implements ArtifactRepository {

Observable<Response> responses; private Artifact temp;

@Override public void saveArtifact(@NonNull final Artifact data) { executeTransaction(realm -> { if (data.getId()!= null){ realm.insertOrUpdate(data); }else{ long nextId = nextKey(realm, Artifact.class); data.setId(nextId); realm.insertOrUpdate(data); } }); postArtifact(data); }

@Override public Artifact getArtifactById(long id) { Realm realm = Realm.getDefaultInstance(); Artifact inMemory = null; try { Artifact artifact = realm.where(Artifact.class).equalTo("id", id).findFirst(); inMemory = realm.copyFromRealm(artifact); } catch (Exception e) { e.printStackTrace(); } realm.close(); return inMemory; }

@Override public Observable<List<Artifact>> getAllArtifacts() { Realm realm = Realm.getDefaultInstance(); RealmResults<Artifact> results = realm.where(Artifact.class).findAll();

return Observable.just(realm.copyFromRealm(results)); }

private void postArtifact(@NonNull final Artifact data){ Realm realm = Realm.getDefaultInstance(); RealmResults<Artifact> results = realm.where(Artifact.class).equalTo("isModified", true).findAll(); List<Artifact> post = realm.copyFromRealm(results); post.add(data); for (Artifact a : post){ postArtifacts(a) .subscribe(this::handleResponse, this::errorResponse); } }

49

Page 50: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

public Observable< Artifact> postArtifact(@NonNull final Artifact data){ temp = data; RequestBody requestAuthor = RequestBody.create( MediaType.parse("multipart/form-data"), "id:" +String.valueOf(data.getResearch().getAuthor().getId())); RequestBody requestReport = RequestBody.create( MediaType.parse("multipart/form-data"), "id:" +String.valueOf(data.getResearch().getReport().getId())); RequestBody requestResearch = RequestBody.create( MediaType.parse("multipart/form-data"), "id:" +String.valueOf(data.getResearch().getId()));

return ApiFactory.getArtifactService() .post(requestAuthor, requestReport, requestResearch) .flatMap(response -> Observable.just(data));

}

private void handleResponse(Artifact response){ Realm realm = Realm.getDefaultInstance(); Artifact artifact = realm.where(Artifact.class).equalTo("id", response.getId()).findFirst(); if (artifact != null) { artifact.setModified(false); }

}

4. Создания сущности:

public class ReportActivity extends BaseActivity implements BaseCreateView{

@BindView(R.id.vp_report) ViewPager mViewPager; @BindView(R.id.button_create_report) Button mBtCreate;

private ReportViewPagerAdapter adapter; private BasePresenter presenter;

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FrameLayout contentFrameLayout = (FrameLayout) findViewById(R.id.activity_content); getLayoutInflater().inflate(R.layout.activity_report, contentFrameLayout); mTabLayout.setVisibility(View.VISIBLE);

ButterKnife.bind(this); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); mToolbar.setNavigationOnClickListener(v -> onBackPressed()); }

String createData = getIntent().getStringExtra(Constants.CREATE_DATA_EXTRAS); adapter = new ReportViewPagerAdapter(getSupportFragmentManager()); adapter.setViewPager(mViewPager, createData, this); mTabLayout.setupWithViewPager(mViewPager);

50

Page 51: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

}

@Override @OnClick(R.id.button_create_report) public void onCreateData() { presenter.save(); }

@Override public void handleResponse(Response response) { Toast.makeText(this, response.getMessage(), Toast.LENGTH_LONG).show(); }

@Override public void handleError(Throwable throwable) { RetrofitException error = (RetrofitException) throwable; try { Response errorResponse = error.getErrorBodyAs(Response.class); Toast.makeText(this, errorResponse.getMessage(), Toast.LENGTH_LONG).show(); } catch (IOException e) { e.printStackTrace(); } }

@Override public void hideLoading() {

mLoadingView.hideLoading();

}

@Override public void showLoading(Disposable disposable) {

mLoadingView.showLoading(disposable);

}

public class ReportViewPagerAdapter extends BaseViewPagerAdapter{

public ReportViewPagerAdapter(FragmentManager manager) { super(manager); }

private void setViewPager(ViewPager viewPager, String createData, BaseCreateView view) { boolean stand = true;

if (createData.contentEquals(getResources().getString(R.string.artifact))) { ResearchFragment researchFragment = ResearchFragment.newInstance(stand); MonumentFragment monumentFragment = MonumentFragment.newInstance(); ExcavationFragment excavationFragment = ExcavationFragment.newInstance(); CollectionFragment collectionFragment = CollectionFragment.newInstance(); ArtifactFragment artifactFragment =

51

Page 52: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

ArtifactFragment.newInstance(); presenter = new ArtifactPresenter(artifactFragment, collectionFragment, monumentFragment, excavationFragment, researchFragment, view); adapter.addFrag(researchFragment, getResources().getString(R.string.research)); adapter.addFrag(monumentFragment, getResources().getString(R.string.monument)); adapter.addFrag(excavationFragment, getResources().getString(R.string.excavation)); adapter.addFrag(collectionFragment, getResources().getString(R.string.collection)); adapter.addFrag(artifactFragment, getResources().getString(R.string.artifact));

} if (createData.contentEquals(getResources().getString(R.string.monument))) { ResearchFragment researchFragment = ResearchFragment.newInstance(stand); MonumentFragment monumentFragment = MonumentFragment.newInstance(); SimpleChsFragment simpleChsFragment = SimpleChsFragment.newInstance(); ExcavationContainerFragment excavationFragment = ExcavationContainerFragment.newInstance(); PhotoFragment photoFragment = PhotoFragment.newInstance(); PlanFragment planFragment = PlanFragment.newInstance(); presenter = new MonumentPresenter(researchFragment, simpleChsFragment, monumentFragment, excavationFragment, photoFragment, planFragment); adapter.addFrag(researchFragment, getResources().getString(R.string.research)); adapter.addFrag(simpleChsFragment, getResources().getString(R.string.okn)); adapter.addFrag(monumentFragment, getResources().getString(R.string.monument)); adapter.addFrag(excavationFragment, getResources().getString(R.string.excavation)); adapter.addFrag(photoFragment, getResources().getString(R.string.photo)); adapter.addFrag(planFragment, getResources().getString(R.string.topoplan)); } if (createData.contentEquals(getResources().getString(R.string.research))) { //flag ResearchFragment researchFragment = ResearchFragment.newInstance(!stand); MonumentContainerFragment monumentFragment = MonumentContainerFragment.newInstance(); ExcavationContainerFragment excavationFragment = ExcavationContainerFragment.newInstance(); presenter = new ResearchPresenter(researchFragment, monumentFragment,excavationFragment); adapter.addFrag(researchFragment, getResources().getString(R.string.research)); adapter.addFrag(monumentFragment, getResources().getString(R.string.monument)); adapter.addFrag(excavationFragment, getResources().getString(R.string.excavation)); } viewPager.setAdapter(adapter);

52

Page 53: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

viewPager.setOffscreenPageLimit(adapter.getCount()); }

}}

public class ArtifactPresenter implements BasePresenter {

private final ArtifactView artifactView; private final CollectionView collectionView; private final MonumentView monumentView; private final ExcavationView excavationView; private final ResearchView researchView; private final BaseCreateView mView;

public ArtifactPresenter(ArtifactView artifactView, CollectionView collectionView, MonumentView monumentView, ExcavationView excavationView, ResearchView researchView, BaseCreateView mView) { this.artifactView = artifactView; this.collectionView = collectionView; this.monumentView = monumentView; this.excavationView = excavationView; this.researchView = researchView; this.mView = mView; }

public void save() {

Artifact artifact = artifactView.validatesData(); Monument monument = monumentView.validatesMonument(); /*other validate methods */ Excavation excavation = excavationView.validatesExcavation(); Collection collection = collectionView.validatesCollection();

Author author = researchView.validatesAuthor(); Report report = researchView.validatesReport(); Publication publication = researchView.validatesPublication(); RealmList<Author> col = researchView.validatesCollaboratorList();

Research research = new Research(); research.setAuthor(author); research.setAuthors(col); research.setReport(report); research.setPublication(publication);

artifact.setResearch(research); artifact.setMonument(monument); artifact.setExcavation(excavation); artifact.setCollection(collection);

artifact.setModified(true);

monument.setResearch(research); RepositoryProvider.provideAuthorRepository().saveAuthor(author); RepositoryProvider.provideResearchRepository().saveResearch(research); RepositoryProvider.provideMoumentRepository().saveMonument(monument); RepositoryProvider.provideExcavationRepository().saveExcavation(excavation); RepositoryProvider.provideArtifactRepository() .saveArtifact(artifact) .doOnSubscribe(mView::showLoading)

53

Page 54: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

.doOnTerminate(mView::hideLoading) .subscribe(mView::handleResponse , mView::handleError); }

}

public class ArtifactFragment extends BaseFragment implements SelectImage, ArtifactView {

public static final int LAYOUT = R.layout.item_add_one_photo; public static final int TITLE = R.id.tv_title_item_one_photo; public static final int IMAGE = R.id.iv_pick_one_photo_item; public static final int PICK = R.id.button_pick_one_photo; public static final int MORE = R.id.button_more_action_item_one_photo; public static final int SLIDE_LAYOUT = R.id.animate_container_one_photo_item; public static final int TEXT = R.string.one_photo; public static final int MAPPING = R.array.array_mapping; public static final int DATE = R.array.array_date;

@BindView(R.id.container_one_photo) LinearLayout mContainerOnePhoto; @BindView(R.id.button_pick_photo_artifact) Button mBtAddPhoto; @BindView(R.id.et_name_artifact) EditText mEtName; @BindView(R.id.ti_name_artifact) TextInputLayout mTiName; @BindView(R.id.et_year_artifact) EditText mEtYear; @BindView(R.id.et_category_artifact) EditText mEtCategory; @BindView(R.id.et_material_artifact) EditText mEtMaterial; @BindView(R.id.sp_date_artifact) Spinner mSpDate; @BindView(R.id.et_from_artifact) EditText mEtFrom; @BindView(R.id.et_to_artifact) EditText mEtTo; @BindView(R.id.et_attribution_artifact) EditText mEtAttribution; @BindView(R.id.sp_mapping_artifact) Spinner mSpMapping; @BindView(R.id.et_n_artifact) EditText mEtN; @BindView(R.id.et_e_artifact) EditText mEtE; @BindView(R.id.et_desc_artifact) EditText mEtDesc;

private HashMap<ImageView, String> imageHashMap; private List<ImageView> imageViewList; private int imageViewIndex;

public ArtifactFragment() { }

public static ArtifactFragment newInstance() { ArtifactFragment fragment = new ArtifactFragment(); return fragment; }

@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); }

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container,savedInstanceState); View view = inflater.inflate(R.layout.constructor_fragment_artifact, container, false); ButterKnife.bind(this, view); selectImage = this;

54

Page 55: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

imageHashMap = new HashMap<>(); imageViewList = new ArrayList<>();// presenter = new ArtifactPresenter(this);

mBtAddPhoto.setOnClickListener(v -> addLayout(mContainerOnePhoto, LAYOUT, TITLE, MORE, SLIDE_LAYOUT, TEXT, PICK, IMAGE)); setSpinners(mSpMapping, MAPPING); setSpinners(mSpDate, DATE); return view; }

@Override public void galleryIntent(ImageView imageView) { if (!imageViewList.contains(imageView)){ imageViewList.add(imageView); imageViewIndex = imageViewList.indexOf(imageView); } else { imageViewIndex = imageViewList.indexOf(imageView); } Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, "Select File"), SELECT_FILE); }

@Override public void cameraIntent(ImageView imageView) { if (!imageViewList.contains(imageView)){ imageViewList.add(imageView); imageViewIndex = imageViewList.indexOf(imageView); } else { imageViewIndex = imageViewList.indexOf(imageView); } Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(intent, REQUEST_CAMERA); }

@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { if (requestCode == SELECT_FILE) { onSelectFromGalleryResult(data); } else if (requestCode == REQUEST_CAMERA){ onCaptureImageResult(data); onSelectFromGalleryResult(data); }

}

}

private void onSelectFromGalleryResult(Intent data){ Uri returnUri = data.getData(); String path = returnUri.getPath();

55

Page 56: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

ImageView temp = imageViewList.get(imageViewIndex); temp.setVisibility(View.VISIBLE); Picasso.with(getActivity()).load(returnUri).noPlaceholder().centerCrop().fit() .into(temp); if (!imageHashMap.containsKey(imageViewList.get(imageViewIndex))){ imageHashMap.put(imageViewList.get(imageViewIndex) , path); } else { imageHashMap.replace(imageViewList.get(imageViewIndex), path); }

}

private void onCaptureImageResult(Intent data) { Bitmap thumbnail = (Bitmap) data.getExtras().get("data"); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); thumbnail.compress(Bitmap.CompressFormat.JPEG, 90, bytes);

File destination = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis() + ".jpg");

FileOutputStream fo; try { destination.createNewFile(); fo = new FileOutputStream(destination); fo.write(bytes.toByteArray()); fo.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }

@Override public Artifact validatesData() { setError(mTiName); String name = mEtName.getText().toString(); int err = 0; if (!validateFields(name)) { err++; mTiName.setError("Name should not be empty !"); } if (err == 0) { Artifact details = new Artifact(); details.setName(name); details.setYear(mEtYear.getText().toString()); details.setCategory(mEtCategory.getText().toString()); details.setMaterial(mEtMaterial.getText().toString()); details.setDate(mSpDate.getSelectedItem().toString()); details.setFrom(mEtFrom.getText().toString()); details.setTo(mEtTo.getText().toString()); details.setAttribution(mEtAttribution.getText().toString()); details.setMapping(mSpMapping.getSelectedItem().toString()); details.setN(mEtN.getText().toString()); details.setE(mEtE.getText().toString()); details.setDesc(mEtDesc.getText().toString());

56

Page 57: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

RealmList<Image> imageRealmList = new RealmList<>(); for (ImageView view : imageViewList) { Image image = new Image(); String path = imageHashMap.get(view); image.setPath(path); imageRealmList.add(image); } details.setImages(imageRealmList);

return details;

} else { showSnackBarMessage("Enter Valid Details !"); return null; } }}

5. Представление поиска данных.

public class QuickSearchActivity extends BaseActivity{

@BindView(R.id.button_quick_show_result) Button mBtResult; @BindView(R.id.sp_quick_search_object) Spinner mSpEntity; @BindView(R.id.sp_epoch_monument_quick) Spinner mSpEpoch; @BindView(R.id.tv_epoch_monument_quick) TextView mTvEpoch; @BindView(R.id.sp_type_monument_quick) Spinner mSpType; @BindView(R.id.tv_type_monument_quick) TextView mTvType; @BindView(R.id.et_name_monument_quick) EditText mEtNameMonument; @BindView(R.id.ti_name_monument_quick) TextInputLayout mTiNameMonument; @BindView(R.id.et_name_author_research_quick) EditText mEtAuthorResearch; @BindView(R.id.ti_name_author_research_quick) TextInputLayout mTiAuthorResearch; @BindView(R.id.et_year_research_quick) EditText mEtYearResearch; @BindView(R.id.ti_year_research_quick) TextInputLayout mTiYearResearch; @BindView(R.id.et_name_author_quick) EditText mEtAuthor; @BindView(R.id.ti_name_author_quick) TextInputLayout mTiAuthor; @BindView(R.id.et_name_author_report_quick) EditText mEtAuthorReport; @BindView(R.id.ti_name_author_report_quick) TextInputLayout mTiAuthorReport; @BindView(R.id.et_year_report_quick) EditText mEtYearReport; @BindView(R.id.ti_year_report_quick) TextInputLayout mTiYearReport; @BindView(R.id.et_name_chs_quick) EditText mEtChs; @BindView(R.id.ti_name_chs_quick) TextInputLayout mTiChs; @BindView(R.id.et_name_author_excavation_quick) EditText mEtAuthorExcavation; @BindView(R.id.ti_name_author_excavation_quick) TextInputLayout mTiAuthorExcavation; @BindView(R.id.et_year_research_excavation_quick) EditText mEtYearExcavation; @BindView(R.id.ti_year_research_excavation_quick) TextInputLayout mTiYearExcavation; @BindView(R.id.et_index_radiocarbon_quick) EditText mEtIndex; @BindView(R.id.ti_index_radiocarbon_quick) TextInputLayout mTiIndex;

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FrameLayout contentFrameLayout = (FrameLayout) findViewById(R.id.activity_content); getLayoutInflater().inflate(R.layout.activity_quick_search, contentFrameLayout); ButterKnife.bind(this);

57

Page 58: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

// presenter = new QuickSearchPresenter(this);

setEntity(); }

private void setEntity(){ ArrayAdapter<CharSequence> adapterEntity = ArrayAdapter.createFromResource( this, R.array.search_spinner_array_quick, android.R.layout.simple_spinner_item); adapterEntity.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

ArrayAdapter<CharSequence> adapterEpoch = ArrayAdapter.createFromResource( this, R.array.epoch, android.R.layout.simple_spinner_item); adapterEpoch.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); ArrayAdapter<CharSequence> adapterType = ArrayAdapter.createFromResource( this, R.array.monument_type, android.R.layout.simple_spinner_item); adapterType.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mSpEntity.setAdapter(adapterEntity); mSpEpoch.setAdapter(adapterEpoch); mSpType.setAdapter(adapterType); mSpEntity.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { public void onItemSelected(AdapterView<?> parent, View itemSelected, int selectedItemPosition, long selectedId) { if(mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.monument))){ setGone(); mTiNameMonument.setVisibility(View.VISIBLE); mEtNameMonument.setVisibility(View.VISIBLE); mTvEpoch.setVisibility(View.VISIBLE); mTvType.setVisibility(View.VISIBLE); mSpEpoch.setVisibility(View.VISIBLE); mSpType.setVisibility(View.VISIBLE); } if(mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.research))){ setGone(); mTiAuthorResearch.setVisibility(View.VISIBLE); mEtAuthorResearch.setVisibility(View.VISIBLE); mTiYearResearch.setVisibility(View.VISIBLE); mEtYearResearch.setVisibility(View.VISIBLE); } if (mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.author))){ setGone(); mTiAuthor.setVisibility(View.VISIBLE); mEtAuthor.setVisibility(View.VISIBLE);

58

Page 59: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

} if (mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.report))){ setGone(); mTiAuthorReport.setVisibility(View.VISIBLE); mEtAuthorReport.setVisibility(View.VISIBLE); mTiYearReport.setVisibility(View.VISIBLE); mEtYearReport.setVisibility(View.VISIBLE); } if (mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.okn))){ setGone(); mTiChs.setVisibility(View.VISIBLE); mEtChs.setVisibility(View.VISIBLE); } if (mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.excavation))){ setGone(); mTiAuthorExcavation.setVisibility(View.VISIBLE); mEtAuthorExcavation.setVisibility(View.VISIBLE); mTiYearExcavation.setVisibility(View.VISIBLE); mEtYearExcavation.setVisibility(View.VISIBLE); } if (mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.radiocarbon_dating))){ setGone(); mTiIndex.setVisibility(View.VISIBLE); mEtIndex.setVisibility(View.VISIBLE); } } public void onNothingSelected(AdapterView<?> parent) { } }); }

private void setGone(){ mTiNameMonument.setVisibility(View.GONE); mEtNameMonument.setVisibility(View.GONE); mSpEpoch.setVisibility(View.GONE); mSpType.setVisibility(View.GONE); mTvEpoch.setVisibility(View.GONE); mTvType.setVisibility(View.GONE); mTiAuthorResearch.setVisibility(View.GONE); mEtAuthorResearch.setVisibility(View.GONE); mTiYearResearch.setVisibility(View.GONE); mEtYearResearch.setVisibility(View.GONE); mTiAuthor.setVisibility(View.GONE); mEtAuthor.setVisibility(View.GONE); mTiAuthorReport.setVisibility(View.GONE); mEtAuthorReport.setVisibility(View.GONE); mTiYearReport.setVisibility(View.GONE); mEtYearReport.setVisibility(View.GONE); mTiChs.setVisibility(View.GONE); mEtChs.setVisibility(View.GONE); mTiAuthorExcavation.setVisibility(View.GONE); mEtAuthorExcavation.setVisibility(View.GONE);

59

Page 60: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

mTiYearExcavation.setVisibility(View.GONE); mEtYearExcavation.setVisibility(View.GONE); mTiIndex.setVisibility(View.GONE); mEtIndex.setVisibility(View.GONE); }

@OnClick(R.id.button_quick_show_result) public void quickSearch(){ if(mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.author))){ Intent intent = new Intent(this, AuthorListActivity.class); String author = mEtAuthor.getText().toString(); String year=""; intent.putExtra(Constants.SEARCH_AUTHOR_EXTRAS, author); intent.putExtra(Constants.SEARCH_YEAR_EXTRAS, year); startActivity(intent); } if(mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.excavation))){ Intent intent = new Intent(this, ExcavationListActivity.class); String author = mEtAuthorExcavation.getText().toString(); String year = ""; if (mEtYearExcavation.getText() != null){ year = mEtYearExcavation.getText().toString(); } intent.putExtra(Constants.SEARCH_AUTHOR_EXTRAS, author); intent.putExtra(Constants.SEARCH_YEAR_EXTRAS, year); startActivity(intent); } if(mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.okn))){ Intent intent = new Intent(this, ChsListActivity.class); String index = mEtChs.getText().toString(); intent.putExtra(Constants.SEARCH_NAME_EXTRAS, index); startActivity(intent); } if(mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.monument))){ Intent intent = new Intent(this, MonumentListActivity.class); String monument = mEtNameMonument.getText().toString(); String epoch = ""; if (mSpType.getSelectedItemPosition()!=0){ epoch = String.valueOf(mSpEpoch.getSelectedItemPosition()); } String type = ""; if (mSpType.getSelectedItemPosition()!=0){ type= String.valueOf(mSpType.getSelectedItemPosition()); } intent.putExtra(Constants.SEARCH_MONUMENT_EXTRAS, monument); intent.putExtra(Constants.SEARCH_TYPE_EXTRAS, type); intent.putExtra(Constants.SEARCH_EPOCH_EXTRAS, epoch); startActivity(intent); } i

60

Page 61: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

f(mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.radiocarbon_dating))){ Intent intent = new Intent(this, RadiocarbonListActivity.class); String index = mEtIndex.getText().toString(); intent.putExtra(Constants.SEARCH_NAME_EXTRAS, index); startActivity(intent); } if(mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.report))){ Intent intent = new Intent(this, ReportListActivity.class); String author = mEtAuthorReport.getText().toString(); String year = ""; if (mEtYearReport.getText() != null){ year = mEtYearReport.getText().toString(); } intent.putExtra(Constants.SEARCH_AUTHOR_EXTRAS, author); intent.putExtra(Constants.SEARCH_YEAR_EXTRAS, year); startActivity(intent); } if(mSpEntity.getSelectedItem().toString().equals(getResources().getString(R.string.research))){ Intent intent = new Intent(this, ResearchListActivity.class); String author = mEtAuthorResearch.getText().toString(); String year = ""; if (mEtYearResearch.getText() != null){ year = mEtYearResearch.getText().toString(); } intent.putExtra(Constants.SEARCH_AUTHOR_EXTRAS, author); intent.putExtra(Constants.SEARCH_YEAR_EXTRAS, year); startActivity(intent); } }

}

public class AuthorListActivity extends BaseActivity implements CommonListView<Author>, BaseAdapter.OnItemClickListener<Author> {

@BindView(R.id.recyclerView) EmptyRecyclerView mRecyclerView; @BindView(R.id.empty) View mEmptyView;

private LoadingView mLoadingView; private CommonAdapter<Author> mAdapter; private AuthorListPresenter presenter; private String author; private String year;

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FrameLayout contentFrameLayout = (FrameLayout) findViewById(R.id.activity_content); getLayoutInflater().inflate(R.layout.quick_activity_author_list, contentFrameLayout); ButterKnife.bind(this); if (getSupportActionBar() != null) {

61

Page 62: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); mToolbar.setNavigationOnClickListener(v -> onBackPressed()); } author = getIntent().getStringExtra(Constants.SEARCH_AUTHOR_EXTRAS); year = getIntent().getStringExtra(Constants.SEARCH_YEAR_EXTRAS);

mLoadingView = LoadingDialog.view(getSupportFragmentManager()); presenter = new AuthorListPresenter(this); initRecycler(); presenter.init(author, year);

}

private void initRecycler() { LinearLayoutManager layoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(layoutManager); mRecyclerView.addItemDecoration(new DividerItemDecoration(this)); mRecyclerView.setEmptyView(mEmptyView); mAdapter = getAdapter(); mAdapter.attachToRecyclerView(mRecyclerView); mAdapter.setOnItemClickListener(this); }

private CommonAdapter<Author> getAdapter() { return new CommonAdapter<>(new ArrayList<>()); }

@Override public void hideLoading() { mLoadingView.hideLoading(); }

@Override public void showLoading(Disposable disposable) { mLoadingView.showLoading(disposable); }

@Override public void showItems(@NonNull List<Author> items) { mAdapter.changeDataSet(items); }

@Override public void showError() { Toast.makeText(this, R.string.some_error, Toast.LENGTH_LONG).show(); mAdapter.clear(); }

@Override public void showError(Throwable throwable) {

}

@Override public void showDetails(Author item) { AuthorShowActivity.start(this, item); }

@Override

62

Page 63: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

public void onItemClick(@NonNull Author item) { presenter.onItemClick(item); }

}

public class AuthorListPresenter {

private final CommonListView<Author> mView;

public AuthorListPresenter(@NonNull CommonListView<Author> view) { mView = view; }

public void init(String author, String year) { RepositoryProvider.provideAuthorRepository() .authors(author, year) .doOnSubscribe(mView::showLoading) .doOnTerminate(mView::hideLoading) .subscribe(mView::showItems, throwable -> mView.showError()); }

public void onItemClick(Author author) { mView.showDetails(author); } }

6. Инициализация карты

public class SearchMapActivity extends AppCompatActivity implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, GoogleMap.OnMarkerClickListener, LocationListener, SearchMapView {

private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;

private GoogleMap mMap; private GoogleApiClient mGoogleApiClient; private Location mLastLocation; private LocationRequest mLocationRequest; private Marker mCurrLocationMarker;

private SearchMapPresenter presenter; private LoadingView mLoadingView;

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_activity_map); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mLoadingView = LoadingDialog.view(getSupportFragmentManager()); initPresenter(); mapFragment.getMapAsync(this); buildGoogleApiClient();

}

63

Page 64: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

@Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { //Location Permission already granted mMap.setMyLocationEnabled(true); configMap(); } else { //Request Location Permission checkLocationPermission(); } } else { mMap.setMyLocationEnabled(true); configMap(); } }

protected synchronized void buildGoogleApiClient() { mGoogleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); }

@Override public void onConnected(@Nullable Bundle bundle) { mLocationRequest = new LocationRequest(); mLocationRequest.setInterval(100000); mLocationRequest.setFastestInterval(100000); mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY); if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this); } }

@Override public void onConnectionSuspended(int i) {

}

@Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {

}

@Override public boolean onMarkerClick(Marker marker) { return false; }

64

Page 65: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

@Override public void onLocationChanged(Location location) { mLastLocation = location; if (mCurrLocationMarker != null) { mCurrLocationMarker.remove(); } LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); MarkerOptions markerOptions = new MarkerOptions(); markerOptions.position(latLng); markerOptions.title("Current Position"); markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)); mCurrLocationMarker = mMap.addMarker(markerOptions);

//move map camera mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng,5)); }

@Override protected void onStart() { super.onStart(); mGoogleApiClient.connect(); }

@Override protected void onStop() { super.onStop(); if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { mGoogleApiClient.disconnect(); } }

public static final int MY_PERMISSIONS_REQUEST_LOCATION = 99; private void checkLocationPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) { new AlertDialog.Builder(this) .setTitle("Location Permission Needed") .setMessage("This app needs the Location permission, please accept to use location functionality") .setPositiveButton("OK", (dialogInterface, i) -> { //Prompt the user once explanation has been shown ActivityCompat.requestPermissions(SearchMapActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, MY_PERMISSIONS_REQUEST_LOCATION ); }) .create() .show(); } else { // No explanation needed, we can request the permission. ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, MY_PERMISSIONS_REQUEST_LOCATION ); } }

65

Page 66: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

}

@Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case MY_PERMISSIONS_REQUEST_LOCATION: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! Do the // location-related task you need to do. if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {

if (mGoogleApiClient == null) { buildGoogleApiClient(); } mMap.setMyLocationEnabled(true); }

} else { Toast.makeText(this, "permission denied", Toast.LENGTH_LONG).show(); } return; }

} }

private void initPresenter() { String activity = getIntent().getExtras().getString(Constants.CALLING_ACTIVITY); if (activity != null || !activity.equals("") || !activity.isEmpty()) { if (activity.equals(ResearchListActivity.class.getSimpleName())) { presenter = new SearchMapResearchPresenter(this); } if (activity.equals(ChsListActivity.class.getSimpleName())) { presenter = new SearchMapChsPresenter(this); } if (activity.equals(ExcavationListActivity.class.getSimpleName())) { presenter = new SearchMapExcavationPresenter(this); } if (activity.equals(MonumentListActivity.class.getSimpleName())) { presenter = new SearchMapMonumentPresenter(this); } if (activity.equals(RadiocarbonListActivity.class.getSimpleName())) { presenter = new SearchMapRadiocarbonPresenter(this); } } }

private void configMap() {

66

Page 67: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[] {android.Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); return; } mMap.getUiSettings().setZoomControlsEnabled(true); mMap.getUiSettings().setMyLocationButtonEnabled(true); mMap.getUiSettings().setAllGesturesEnabled(true); mMap.setOnMarkerClickListener(this); presenter.init(); }

private int setMarker(ListItem marker) { int count = 0; for (int j = 0; j < marker.getX().size(); j++) { if (marker.getX().get(j) != null || !marker.getX().get(j).equals("")) { double lat = Double.parseDouble(marker.getX().get(j)); if (marker.getY().get(j) != null || !marker.getX().get(j).equals("")) { double lan = Double.parseDouble(marker.getY().get(j)); placeMarkerOnMap(lat, lan, marker.getName()); count++; } } } return count; }

private void placeMarkerOnMap(double latitude, double longitude, String name) { mMap.addMarker(new MarkerOptions() .position(new LatLng(latitude, longitude)) .anchor(0.5f, 0.5f) .title(name) ); }

@Override public void hideLoading() { mLoadingView.hideLoading(); }

@Override public void showLoading(Disposable disposable) { mLoadingView.showLoading(disposable); }

@Override public void showError() { Toast.makeText(this, R.string.some_error, Toast.LENGTH_LONG).show(); }

@Override public void loadChsResponse(List<ChsResponse> responses) { int count = 0; for (int i = 0; i < responses.size(); i++) { count += setMarker(responses.get(i)); }

67

Page 68: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

if (count == 0){ Toast.makeText(this, R.string.marker_error, Toast.LENGTH_LONG).show(); } }

@Override public void loadMonumentResponse(List<MonumentResponse> responses) { int count = 0; for (int i = 0; i < responses.size(); i++) { count += setMarker(responses.get(i)); } if (count == 0){ Toast.makeText(this, R.string.marker_error, Toast.LENGTH_LONG).show(); }

}

@Override public void loadExcavationResponse(List<ExcavationResponse> responses) { int count = 0; for (int i = 0; i < responses.size(); i++) { count += setMarker(responses.get(i)); } if (count == 0){ Toast.makeText(this, R.string.marker_error, Toast.LENGTH_LONG).show(); } }

@Override public void loadRadiocarbonResponse(List<RadiocarbonDate> responses) { int count = 0; for (int i = 0; i < responses.size(); i++) { RadiocarbonResponse response = responses.get(i).getRadiocarbonResponse(); count += setMarker(response); } if (count == 0){ Toast.makeText(this, R.string.marker_error, Toast.LENGTH_LONG).show(); } }

@Override public void loadResearchResponse(@NonNull List<ResearchResponse> responses) { int count = 0; for (int i = 0; i < responses.size(); i++) { count += setMarker(responses.get(i)); } if (count == 0){ Toast.makeText(this, R.string.marker_error, Toast.LENGTH_LONG).show(); } }}

public class SearchMapRadiocarbonPresenter implements SearchMapPresenter {

private final SearchMapView mView;

public SearchMapRadiocarbonPresenter(@NonNull SearchMapView view) {

68

Page 69: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

mView = view; }

public void init() { RepositoryProvider.provideRadiocarbonRepository() .getAllRadiocarbonResponse() .doOnSubscribe(mView::showLoading) .doOnTerminate(mView::hideLoading) .subscribe(mView::loadRadiocarbonResponse, throwable -> mView.showError()); }}

7. Представление сущности

public class AuthorShowActivity extends BaseActivity {

@BindView(R.id.vp_author) ViewPager mViewPager;

private AuthorViewPagerAdapter adapter;

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FrameLayout contentFrameLayout = (FrameLayout) findViewById(R.id.activity_content); getLayoutInflater().inflate(R.layout.show_activity_author, contentFrameLayout); mTabLayout.setVisibility(View.VISIBLE); ButterKnife.bind(this); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); mToolbar.setNavigationOnClickListener(v -> onBackPressed()); } Long id = getIntent().getLongExtra(Constants.ID_KEY, 1L); adapter = new AuthorViewPagerAdapter(getSupportFragmentManager()); adapter.setViewPager(mViewPager, id); mTabLayout.setupWithViewPager(mViewPager);

}

public static void start(@NonNull Activity activity, @NonNull Author author) { Intent intent = new Intent(activity, AuthorShowActivity.class); intent.putExtra(Constants.ID_KEY, author.getId()); activity.startActivity(intent); }

public class AuthorViewPagerAdapter extends BaseViewPagerAdapter {

public AuthorViewPagerAdapter(FragmentManager manager) { super(manager); }

private void setViewPager(ViewPager viewPager, Long id) {

69

Page 70: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

adapter.addFrag(GeneralFragment.newInstance(id), getResources().getString(R.string.general)); adapter.addFrag(ResearchFragment.newInstance(id), getResources().getString(R.string.research)); adapter.addFrag(MonumentFragment.newInstance(id), getResources().getString(R.string.monument)); adapter.addFrag(ReportFragment.newInstance(id), getResources().getString(R.string.report)); adapter.addFrag(PublicationFragment.newInstance(id), getResources().getString(R.string.publication)); viewPager.setAdapter(adapter); viewPager.setOffscreenPageLimit(adapter.getCount()); } }

}

public class GeneralFragment extends BaseFragment implements GeneralView<Author> { @BindView(R.id.tv_name) TextView mTvName; @BindView(R.id.tv_birth) TextView mTvBirth; @BindView(R.id.tv_bio) TextView mTvBio; @BindView(R.id.tv_org) TextView mTvOrg;

private AuthorShowPresenter presenter;

public GeneralFragment() { }

public static GeneralFragment newInstance(Long id) { GeneralFragment fragment = new GeneralFragment(); Bundle args = new Bundle(); args.putLong(Constants.ID_KEY, id); fragment.setArguments(args); return fragment; }

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View view = inflater.inflate(R.layout.show_fragment_author, container, false); ButterKnife.bind(this, view); presenter = new AuthorShowPresenter(this); Long id = getArguments().getLong(Constants.ID_KEY); try { presenter.init(id); } catch (Exception e) { e.printStackTrace(); } return view; }

@Override public void showError(Throwable throwable) {

Throwable tttrt= throwable; System.out.println(tttrt); Toast.makeText(getActivity(), R.string.some_error, Toast.LENGTH_LONG).show();

70

Page 71: kpfu.ru · Web viewкоторые не имеют доступа не только в сеть, но даже поддержки голосовой связи. Это обусловлено

} @Override public void showInfo(Author item) { if (item.getName() != null) mTvName.setText(item.getName()); if (item.getYear() != null) mTvBirth.setText(item.getYear()); /**wait in server**/ if (item.getName() != null) mTvBio.setText(item.getName()); if (item.getName() != null) mTvOrg.setText(item.getName()); }}

public class AuthorShowPresenter {

private final GeneralView<Author> mView;

public AuthorShowPresenter(@NonNull GeneralView<Author> view) { mView = view; }

public void init(Long id) throws IOException, JSONException { RepositoryProvider.provideAuthorRepository() .getGeneral(id) .subscribe(mView::showInfo, throwable -> mView.showError(throwable)); } }

71