Урок 9: Внедрение логики изучения слов из телеграм бота

Введение

На прошлом уроке мы практически полностью реализовали UI экрана. Сейчас мы обрабатываем несколько событий кликов, появилась динамика, но пока это даже не близко к тренажеру. Изучать иностранные слова с текущим функционалом сложновато.

И в этой статье мы дорисуем сову – сделаем из текущей заготовки полноценный функционал экрана приложения для изучения английских слов. Здесь будет мало про сам Android. Но прежде чем далее рассказывать вам про новые технологии, нужно подготовить почву. Чтобы не создавать объекты котиков и потом дебажить их, а наглядно показывать как все работает на примере полноценного приложения.

Универсализация кода

Окей. Для начала нужно немного универсализировать наш текущий код. Пройдемся по тому, что имеем прямо сейчас.

На данный момент у нас есть метод onCreate и три дополнительных метода для изменения состояния кнопок. В данный момент мы меняем конкретные захардкоженные кнопки. Наша задача – сделать эти методы более универсальными, чтобы любую из четырех кнопок можно было сделать корректной, некорректной или нейтральной. Поэтому давайте параметризуем наши функции. Вынеся из них прописанные вью в параметры. Чтобы методы принимали определенные вью и внутри применяли для них стилизацию.

Но для начала добавим недостающие идентификаторы в разметке. Я пройдусь по всему документу. Начну с иконки закрытия. Кстати, стандартный шорткат для переименовывания довольно неудобный, а сама процедура переименования довольно частая. Легко проставить удобную для себя комбинацию клавиш в Settings → Keymap → rename в разделе Refactor. Добавлю айдишники к 2 и 4 контейнерам.

Хорошо. Сейчас наши три метода меняют и контейнер с вариантом ответа, и диалог с результатом ответа. Декомпозируем это действие и вынесем отображение результата в отдельный метод. Его оформим чуть позже, а пока просто удаляю лишние преобразования.

Разделение логики текущих методов

Теперь посмотрим внимательно на методы – к каким вью мы обращаемся внутри? Наглядно видно, что мы меняем только 3 элемента. Всегда. А это значит, что эти параметры можно передавать в методы и внутри обезличенно обращаться к ним.

Итого параметры будут такими:

  • layoutAnswer с типом LinearLayout (это тип контейнера в макете);
  • tvVariantNumber и tvVariantValue с типами TextView (для цифры варианта ответа и самого слова);

Теперь внутри метода обращаемся не к элементам разметки через байндинг, а к входным параметрам метода. Заменю эти переменные. Таким образом конкретные значения вариантов ответа перестанут быть захардкоженными и метод станет универсальным. Что в него передаем – то и помечаем как неверный вариант.

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

Восстановление логики

Обратим внимание на слушатели кликов. Функции теперь ожидают какие-то параметры. Воспроизведем старое поведение. То есть при клике на 3 вариант ответа вызываем метод окраски метода в корректный и передаем в него вьюхи того же третьего элемента.

При клике на 1 вариант ответа помечаем его неверным. А при клике на кнопку продолжения вызываем метод нейтральной окраски для первого и третьего варианта ответа.

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

Реализация метода отображения диалога с результатом

Теперь нужен метод, показывающий корректность или некорректность правильного ответа в виде диалога внизу экрана. Добавим четвертый метод showResultMessage(). Он будет принимать один параметр – Boolean значение isCorrect.

Внутри будем менять цвет, текст сообщения и иконку. Объявлю переменные, а инициализировать буду в зависимости от переданного булева значения. Добавлю простое условие. Если передали true, то инициализируем переменные позитивными значениями. Достаем из ресурсов цвет при помощи getColor(). Устанавливаем нужную корректную иконку и присваиваем текст корректного сообщения. Иначе – инициализируем обратными по смыслу значениями.

Ну и внизу через блок with(binding) прописываем видимости кнопок и использование объявленных выше переменных.

  • Скрываю кнопку пропуска
  • Отображаю лейаут сообщения корректности ответа
  • Задаю цвет текста на кнопке продолжения
  • Задаю цвет лейаута диалога
  • Задаю текст корректности ответа
  • Задаю иконку корректности ответа

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

Поэтому в onClickListener() в первом случае я вызываю этот метод с параметром true. Для первого лейаута с неправильным ответом вызываю показ сообщения с параметром false.

Посмотрим что получается. Пока все работает согласно написанной логики.

Обзор класса тренажера

Перейдем к реализации логики работы нашего тренажера – для этого все уже готово. Текущие написанные коллбэки нам пока не пригодятся – удалю их.

Для реализации бизнес-логики мы воспользуемся классом, созданным в рамках курсовой работы по написанию телеграм бота на Kotlin. Я его уже скопировал себе в Android проект. В этом классе реализована основная логика работы приложения, включая хранение слов в словаре и генерацию вопросов. Благодаря тому, что логика разделена на классы – мы можем переиспользовать один из них и внедрить в приложение с минимальными модификациями.

И, конечно, напомню, что всю логику бота целиком вы сможете написать в рамках практики по Kotlin. А фрагменты кода текущего проекта доступны по ссылкам под видео.

Сильно углубляться в детали реализации сейчас не буду, к тому же тут довольно простая логика. Она состоит всего из 3 классов.

Два data class’a:

  • Word: Этот data class представляет собой слово с его оригинальной формой на английском (original), переводом (translate) и флагом (learned), указывающим, было ли слово выучено.
  • Question: Этот класс представляет вопрос, состоящий из списка вариантов ответов (variants) – именно его мы и показываем. И одно поле объекта правильного ответа (correctAnswer).

И класс тренажера:

  • В классе LearnWordsTrainer есть словарь (dictionary), в котором хранится список объектов Word. В отличие от курсовой здесь слова для изучения будут храниться не в файле, а в списке. Но в будущем мы переделаем способ хранения на базу данных или иной альтернативный способ, чтобы получать их через сеть, обращаясь к собственному API. Сейчас акцент не на этом. Этот список представляет собой базовую коллекцию слов для тренировки.
  • Переменная currentQuestion хранит текущий отображаемый вопрос, чтобы мы могли реагировать на действие пользователя и осуществлять проверку.
  • Далее следует метод getNextQuestion: Этот метод генерирует новый вопрос для пользователя. Сначала он фильтрует словарь, чтобы получить список слов, которые еще не были выучены (notLearnedList). Если все слова выучены, возвращается null, что означает отсутствие новых вопросов.
  • Если не все слова выучены, метод формирует вопрос, выбирая случайные слова из списка невыученных слов. Если невыученных слов меньше, чем требуется для вопроса (numberOfAnswers содержит в данном случае значение 4), оставшиеся слова выбираются из списка выученных слов. После этого из сформированного списка вариантов выбирается правильный ответ, и создается объект Question.
  • Метод checkAnswer: Этот метод принимает индекс выбранного пользователем ответа (userAnswerIndex) и проверяет, соответствует ли он индексу правильного ответа в текущем вопросе (currentQuestion). Если ответ пользователя совпадает с правильным ответом, метод возвращает true, и соответствующее слово помечается как выученное (learned = true). Если ответ неверен, возвращается false.

Если вы не писали этот код вместе с нами в рамках практики, сразу может быть непонятно. Это нормально. Перепишите этот код или скачайте по ссылке https://t.me/ievetrov_dev и разберитесь в нем, ставьте точки остановки и запускайте дебаг режим, чтобы проследить за работой программы построчно. На самом деле все довольно доступно, тем более, если вы хорошо освоили базу языка.

Внедрение класса тренажера

Окей, задействуем LearnWordsTrainer. Для начала создам экземпляр этого класса и сохраню в переменную trainer.

И сейчас необходимо продумать механизм отображения загаданного слова и вариантов ответа. Создадим метод showNextQuestion(). В качестве параметра он будет принимать экземпляр тренажера, чтобы можно было обращаться к полям и методам этого класса.

Итак, что будет происходить внутри. Сначала получаем объект Question? из метода getNextQuestion(). Напомню этот объект хранит список вариантов ответа и один объект правильного слова. Если по какой-либо причине он равен null или количество вариантов ответа меньше 4, то мы здесь размещаем какую-то логику завершения обучения.

Предлагаю пока просто все прятать и кнопке пропуска задавать текст “Complete”. Попутно я скорректирую название айдишника лейаута со всеми вариантами ответа. Позже возможно добавим другую логику на случай, когда все слова будут выучены.

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

  • Показываем кнопку пропуска
  • Показываем изучаемое слово. Мы это делаем на всякий случай, если по какой-либо причине до этого мы его могли скрыть. Повторный вызов isVisible никак не ломает процесс и не страшен. Если элемент уже виден, это действие просто пропустится.
  • Далее обращаемся к объекту загаданного слова и достаем из него слово для изучения на оригинальном языке. Присваиваем это слово в TextView с изучаемым словом.
  • И обращаемся ко всем 4 вью с вариантами ответа и присваиваем каждому свое значение. Значение также достаем из объекта полученного Question? и по индексам из списка вариантов ответа достаем и инициализируем все 4 слова для кнопок.

Добавление слушателей кликов

Ну и теперь навешиваем коллбэки. То есть прослушиватели кликов, с которыми мы уже познакомились в предыдущем уроке. Они будут похожи на те, что ранее мы удалили, но с чуть доработанной логикой.

Итак, нам нужно 4 слушателя для каждого варианта ответа. Все они будут абсолютно идентичны, только со своими отличающимися значениями. Поэтому напишу и детально разберу первый, остальные размножу по аналогии.

При клике на вью layoutAnswer1 коллбэк проверяет правильное ли слово под индексом 0. То есть здесь в условии вызывается метод checkAnswer(0) с параметром индекса первого слова. Почему индекс первого слова нулевой? Выше мы под этим индексом его достали из списка вариантов ответа и присвоили первой вью.

Так вот, логику checkAnswer() вы уже видели. Метод по индексам сравнивает нажатое слово и правильное слово, и в зависимости от результата возвращает Boolean. Соответственно если вернул true, то вызываем метод markAnswerCorrect() для окраски ячейки и содержимого в зеленый цвет. Принимаемые параметры нам уже знакомы, их реализовывали в прошлом уроке. Тут же вызываем отображение информационного сообщения внизу экрана showResultMessage() с параметром true.

Ну и в ином случае, если checkAnswer() вернул false – помечаем ячейку неправильным вариантом и показываем сообщение, что ответ неправильный. Мне остается только размножить эти слушатели и поменять индексы. Чтобы каждый слушатель обрабатывал свою ячейку с вариантом ответа.

Реализация показа следующего слова

Великолепно! Сейчас мы запускаем слово, по клику определяем верно-неверно и показываем соответствующий мессадж. Осталось еще обработать кнопки пропуска и продолжения. Иначе не сможем сбрасывать состояние до нейтрального положения и показывать следующий вопрос.

Поэтому добавляем слушатель на btnContinue.

  • По кнопке “Продолжить” скрываем блок с результатами ответа
  • Помечаем все варианты ответа нейтральным цветом
  • Вызываем метод показа следующего слова для отгадывания

И, конечно, еще можно пропустить слово по кнопке Skip. Просто вешаем слушатель кликов и при нажатии вызываем метод показа следующего слова.

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

И вот – приложение для изучения слов полностью функционально! Мы успешно интегрировали логику тренажера и улучшили пользовательский интерфейс. В следующих уроках будем рассматривать более конкретные технологии Android, попутно улучшая функциональность этого демо приложения.

Как обычно внизу вы найдете полезные ссылки, возможно уже размещена информация по практическому спринту на Jetpack Compose. На всякий случай проверьте. Все актуальные новости в будут в телеграм канале https://t.me/ievetrov_dev.

Для тех, кто собрался стать Android-разработчиком

Пошаговаясхема
Пошаговая
схема

Описание процесса обучения от основ Kotlin до Android-разработчика

Бесплатныеуроки
Бесплатные
уроки

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

Обучающийбот
Обучающий
бот

Тренажер и самоучитель по Котлин – бесплатные тесты и практика

Поделиться уроком

Ответить

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