Доброго времени суток всем читателям Хабр.  Поиск работы в it сфере сильно отличается от обычных профессий и порог вхождения на любую позицию в it намного выше большинства профессий. Традиционно при отклике на вакансию просят сделать тестовое задание, это является своеобразным фильтром, каждый раз возникает дилемма, делать его или не делать, ведь никому не хочется впустую потратить время и не получить в ответ ни приглашения на собеседование ни тем более приглашения на работу. В данной статье я хотел бы разобрать тестовое задание на позицию Android developer и продемонстрировать свой способ решения. После прочтения ТЗ предлагаю проанализировать, из чего будет состоять приложение и что мы будем делать чтобы успешно его реализовать. Начнем с того что в данном ТЗ кроме требований к языку (код должен быть написан на Kotlin), есть требования к архитектуре приложения (MVVM или MVP), так же сразу становится понятно, что нам понадобится список (не простой список конечно, нужна будет сортировка по дате, об этом я напишу далее) и в этом списке мы будем отображать GET запрос, который получим по API. Что же, на первый взгляд ТЗ не выглядит сложным, из интересного это сортировка по дате. Теперь можно приступать к решению!  Для начала предлагаю подумать об архитектуре Структура проекта будет следующая ui – в этой папке будет храниться все что связано с ui интерфейсом: MainFragment – фрагмент где будет отображаться список, MainViewModelFactory – фабрика для создания экземпляра ViewModelTraining. ViewModelTraining – вью модель для работы с данными и отображения в MainFragment. adapter – в этой паке будет класс TrainingAdapter, который отвечает за связь данных с ui api – в данной паке будет BASE_URL,  интерфейс ApiService с GET запросом, а так же ApiResult с обработкой результатов работы с API. data -  в этой паке будут модели, которые мы будем отображать с помощью GET запроса. di -  в этой папке будет внедрение зависимостей Теперь приступаем к самому интересному, к реализации. Подключаем библиотеки. Нам понадобятся: viewBinding - для облегчения доступа к элементам пользовательского интерфейса.   Lifecycle -  чтобы обеспечить более удобное и надежное управление жизненным циклом компонентов. Coroutines -  для работы с асинхронным кодом. Retrofit - для работы с API. Navigation Components - в данном случае не обязательно, но обычно я использую именно Navigation Components для работы с fragments. Верстаем разметку. В данном приложении будет single activity, поэтому activity_main будет содержать FragmentContainerView. С фрагментами, на мой взгляд, лучше использовать navigation, поскольку это более лаконично и позволит масштабировать приложение, если это понадобится  Кроме этого, в примере присутствует BottomNavigationView без возможности перехода на другие Fragments, так же реализуем ее в нашем activity_main. Далее fragment_main где будет список, для списка используем recyclerview.   Для него будет основной listitem, в нем мы будем отображать то что будет в списке. На макете это будет выглядеть примерно так: Кроме этого нужен header_item для отображения даты  MainActivity код будет выглядеть так: Данный код создает экземпляр активности MainActivity, привязывает разметку активности, устанавливает ее в качестве контента активности, настраивает навигацию и связывает нижнее меню навигации.   С помощью расширения Kotlin data class File from JSON создаем дата классы для модели. enum class TrainingType понадобится в будущем для работы в адаптере.  4. Внедряем зависимости, поскольку приложение небольшое, я решил не использовать Hilt. Данный код создает экземпляры retrofit и viewModelFactory в методе onCreate(), и предоставляет экземпляры ViewModel через функцию provideViewModel().    В классе App мы используем ApiService и MainViewModelFactor, создадим их немного позже Не забываем указать App в манифесте.    5. Пишем ретрофит. Тут все стандартно, интерфейс с запросом, обработка ответов и BASE_URL, напомню, билдер находится в App. Так же нужно указать работу с сетью в манифесте.   В классе RetrofitClient указываем BASE_URL В интерфейсе ApiService пишем GET запрос который будет возвращать Response<Training> Класс ApiResult предоставляет общую структуру для представления результатов операций API. Подклассы Success и Error предоставляют специфические реализации для успешных результатов и ошибок соответственно.  Обязательно указываем работу с сетью.  Пишем адаптер Адаптер это часто используемый паттерн в андроид разработке он есть в любом приложении где есть список. В данном случае задача усложнена тем, что нам нужно отобразить не один listitem, а еще и header_item (обычно мы отображаем какой-то один список). В TrainingViewHolder в конструкторе которого будет не (val binding: ListitemBinding), а будет (view: View), таким образом мы сможем обращаться и к списку и к дате. Метод onCreateViewHolder переопределен для создания экземпляра TrainingViewHolder и в зависимости от типа элемента, определенного в viewType. Возвращается экземпляр TrainingViewHolder, связанный с inflate.   Метод getItemViewType переопределен для получения типа элемента по его позиции в списке. Возвращается значение ordinal типа элемента TrainingType.   Метод onBindViewHolder переопределен для связывания данных с представлениями элементов списка. Если тип элемента TrainingType.TRAIN, то значения связываются с соответствующими представлениями (TextView) внутри TrainingViewHolder.itemView. Если тип элемента отличается от TrainingType.TRAIN, то значение связывается с TextView внутри TrainingViewHolder.itemView, которые отображают заголовок элемента.   Метод getItemCount возвращает количество элементов в списке training.   Метод setData используется для установки новых данных в адаптер и вызова notifyDataSetChanged(), чтобы обновить список.   Отображаем все в MainFragment и сортируем список  . Для этого создадим класс MainViewModelFactory. MainViewModelFactory используется для создания экземпляров ViewModelTraining с использованием ApiService в качестве зависимости.   Создаем ViewModelTraining. Внутри класса ViewModelTraining определена переменная liveData, которая является экземпляром MutableLiveData с параметризованным типом ApiResult<Training>. MutableLiveData предоставляет возможность наблюдать за изменениями данных и обновлять пользовательский интерфейс при их изменении.    В блоке init определен инициализационный блок, который вызывает метод loadListTraining(). Этот метод используется для загрузки списка тренировок и обновления значения liveData.   Метод loadListTraining() использует viewModelScope.launch для запуска корутины в контексте Dispatchers.IO. Внутри корутины выполняется запрос к методу getTrainingList() объекта retrofit. Если запрос успешен, то результат помещается в liveData с помощью liveData.postValue(ApiResult.Success(result.body())), иначе помещается сообщение об ошибке с помощью liveData.postValue(ApiResult.Error("Not Loading")).   Метод getTrainerById принимает аргумент id типа String и используется для получения имени тренера по его идентификатору. Внутри метода используется функция find для поиска тренера в списке данных liveData. Если тренер найден, то возвращается его имя, иначе возвращается пустая строка.   Создаем MainFragment Создается приватное свойство _binding типа FragmentMainBinding?, которое используется для привязки разметки фрагмента.  Создается свойство mBinding, которое возвращает _binding!!, чтобы обеспечить доступ к привязке фрагмента.    Создается свойство viewModel типа ViewModelTraining, которое будет использоваться для обработки бизнес-логики и управления данными, связанными с тренировками.     В методе onCreateView создается привязка разметки фрагмента с использованием _binding, который получает доступ к разметке фрагмента с помощью FragmentMainBinding.inflate(layoutInflater, container, false). Затем возвращается корневой вид  разметки.      В методе onViewCreated происходит инициализация viewModel, используя provideViewModel из класса App, который предоставляет экземпляр ViewModelTraining с использованием activity?.application и передает текущий фрагмент this. Затем создается экземпляр TrainingAdapter, передавая лямбда-выражение viewModel.getTrainerById(it) в конструктор. Далее устанавливается адаптер для mBinding.trainingAdapter.adapter. Наблюдатель liveData в viewModel слушает изменения и обновляет UI в соответствии с результатами. Если результат является ApiResult.Success и данные не равны null, вызывается setData для trainingAdapter с отсортированными данными. В противном случае показывается Toast с сообщением об ошибке.   В классе определен метод sortLessons, который принимает объект Training и возвращает список LessonEntity. В этом методе происходит сортировка списка lessons по убыванию даты. Затем создается пустой изменяемый словарь map, куда добавляются элементы из списка lessons с группировкой по дате. Затем создается новый список newList, в который добавляются заголовки и тренировки из словаря map. Наконец, возвращается отсортированный список newList.   В методе onDestroy _binding устанавливается в null, чтобы избежать утечки памяти.  Все, задние готово! На эмуляторе выглядит приложение так: В заключение хочу написать, что такого рода тестовые, где вас просят отобразить что-нибудь с помощью запросов, встречаются часто. Думаю, мой разбор данного тестового может быть полезен новичкам, которые планируют стать Андроид Разработчиками и только начинают искать работу и пробуют откликаться на вакансии. Делать тестовые или нет, ваш выбор, возможно вам повезет и после тестового вас позовут на собеседование. Более подробно с кодом можете ознакомиться тут ТГ для обратной связи: @AndrewNB