Знакомство с Kotlin. Работа с шаблонизатором #

Васильев Андрей Михайлович, 2024

Версии презентации

Язык Kotlin #

  • Разработан компанией JetBrains
  • Представлен в 2011 году
  • Версия 1.0 выпущена в 2016 году
  • С 2019 является основным языком разработки под Android
  • Версия 1.6 вышла в ноябре 2021 года
  • Версия 1.7 вышла в июне 2022 года
  • Версия 1.8 вышла в декабре 2022 года
  • Версия 1.9 вышла в июле 2023 года

Примерно каждые полгода выходит новая версия. Тематика последних выпусков — расширение списка поддерживаемых платформ и переход на унифицированный компилятор

Особенности платформы #

  • Можно разрабатывать приложения под несколько платформ:
    • Мобильные приложения Android
    • Настольные приложения без JVM, платформенный бинарный код
    • Приложения под iOS
    • Серверные веб-приложения
    • Клиентские веб-приложения (платформа JavaScript)
    • Клиентские веб-приложения (платформа WebAssembly)
  • Можно разрабатывать библиотеки для различных платформ
  • Можно использовать общую кодовую базу при разработке приложений под разные платформы

Мы будем рассматривать классический вариант — разработку приложений под JVM

Особенности языка Kotlin #

  • Язык общего назначения, т.е. можно разрабатывать любые приложения
  • Поддерживает современные парадигмы программирования:
    • Императивная, процедурная
    • Объектно-ориентированная
    • Функциональная
  • Обладает современным компактным синтаксисом
  • Изначально нацелен на решение практических ограничений языка Java 7/8 (разработчикам JetBrains надоело писать слишком много кода)
  • Динамично развивается, разработчики оставляют право за собой изменять детали работы языка

Изучение языка Kotlin #

Ментальная модель при изучении языка #

flowchart LR kotlin["Исходный код на Kotlin"] java["Исходный код на Java"] kotlin-jvm("Компилятор Kotlin\nдля JVM") javac("Компилятор Javac") kotlin-class[".class-файлы"] java-class[".class-файлы"] jvm("Виртуальная машина\nJVM") kotlin --> kotlin-jvm --> kotlin-class --> jvm java --> javac --> java-class --> jvm

  • В качестве целевой платформы будем использовать только JVM
  • Можно легко перенести идиоматические для Java решения:
    • Деление кода на пакеты
    • Написание классов
    • Создание объектов из классов
    • Использование коллекций (только стоит использовать классы Kotlin: List, Map)
    • Использование перечислений
  • Другие инструменты хорошо дополняют известные элементы и решают проблемы многословности Java-кода
  • Разбираться с поддержкой других платформ в рамках курса не надо

Как запускать приложения #

Минимальное приложение #

За исключением конфигурации системы сборки минимальное приложение состоит только лишь из функции:

fun main() {
    println("Я работаю!")
}
  • Синтаксис языка Си-подобный, т.е. уже знакомый и привычный
  • Для описания функций и методов используется ключевое слово fun
  • Аргументы описываются в скобках после имени функции
  • Для вызова функции используется знакомый синтаксис: название функции, за которым в круглых скобках указывается набор аргументов

Базовый синтаксис #

Рассмотрим основы синтаксиса языка Kotlin

  • В целом всё знакомо
    • Ключевые компоненты синтаксиса взяты из Си
    • Описание типов переменных взято из Оберона (Паскаля, Модулы)
  • Есть возможность писать функции в пакете, без привязки к классам
  • Фукнции поддерживают значения аргументов по умолчанию

Разработка простого приложения #

Реализуем небольшое приложение, которое позволит:

  • Считать последовательность чисел со стандартного потока ввода
  • Найти минимальный элемент
  • Найти максимальный элемент
  • Подсчитать среднее значение последовательности

Функции в Kotlin #

Рассмотрим основы работы с функциями в Kotlin

  • Функции являются объектами первого рода, т.е. ссылки на них могут быть помещены в переменные
  • Подход к реализации функционального программирования повторяет решение из Java: функция является объектом класса, реализующего функцию интерфейса
  • Удобно создавать лямбда-функции
  • Удобно связывать лямбда-функции с вызывающими их методами

Null-безопасность #

Рассмотрим вопрос null-безопасности в Kotlin

  • Отсутствие данных, null-ссылка, является краевым состоянием ссылки
  • Данное состояние необходимо специально обрабатывать в любых языках
  • Kotlin предоставляет удобные средства для обработки данной ситуации

Формирование HTML-документов #

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

Принципиальная схема работы шаблонизатора

flowchart LR renderer(Шаблонизатор) template[Шаблон] base_template[Базовый шаблон] partial[Частичный шаблон] data[Данные\nдля отображения] text[Текст] base_template --> template partial --> template template --> renderer data --> renderer renderer --> text

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

Шаблонизатор Pebble #

Pebble — шаблонизатор для JVM, синтаксически схожий с Twig и Jinja

  • Поддерживает большое количество встроенных фильтров и тегов
  • Поддерживает наследование и подключение шаблонов друг в друга
  • Поддерживает расширения: собственные теги, фильтры и функции

Использование Pebble #

Для использования шаблонизатора необходимо:

  1. Подключить соответствующую библиотеку к проекту в конфигурацию Gradle
  2. Создать объект шаблонизатора
  3. Для каждой отдельной страницы
    1. Создать класс, описывающий данные для отображения
    2. Создать файл шаблона, преобразующий данные в текст

Подключение к проекту #

Для подключения библиотеки Pebbletemplates необходимо в файле build.gradle в разделе dependencies добавить следующую строку:

implementation 'io.pebbletemplates:pebble:3.2.2'

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

Установка расширения для IDEA #

Изначально IDEA не поддерживает редактирование Pebble, однако существует расширение

  • Поддерживающее подсветку синтаксиса языка Pebble
  • Может быть настроено на подсветку целевого языка шаблона, например HTML

Создание шаблонов #

  • Шаблоны обычно представляют собой файлы с расширением .peb, которые находятся в каталоге с ресурсами приложения
  • В Gradle ресурсы располагаются в каталоге src/main/resources

Создадим шаблон-раскладку, которая будет являться базой для всех других страниц и назовём её layout.peb:

<html>
<head>
    <title>{% block title %}Сайт{% endblock %}</title>
</head>
<body>
    <div id="content">
        {% block content %}{% endblock %}
    </div>
    <div id="footer">
        {% block footer %}
            Copyright 2000-3088
        {% endblock %}
    </div>
</body>
</html>

Создадим шаблон для стартовой страницы и назовём его home.peb:

{% extends "layout.peb" %}

{% block title %} Стартовая страница {% endblock %}

{% block content %}
    <h1>Стартовая страница</h1>
    <p>С очень важным содержимым</p>
    <p>Время создания {{ time | date("yyyy-MM-dd HH:mm") }}</p>
{% endblock %}
  • {% %} определяет места вызова управляющих конструкций Pebble
  • {{ }} определяет места для вывода результатов выражения в строку
  • block определяет места в раскладке для расширения
  • extends позволяет наследовать один шаблон от другого
  • Фильтр date позволяет преобразовывать даты в формат, удобный для
  • time — данные для отображения на странице

Отображение шаблона #

  • Необходимо создать объект шаблонизатора, который будет преобразовывать шаблоны в строки. Достаточно создать его 1 раз для всего приложения
  • Необходимо скомпилировать целевой шаблон
  • Сформировать набор данных для отображения
  • Преобразовать данные в строку с помощью скомпилированного шаблона
val engine = PebbleEngine.Builder().build() // Создаём шаблонизатор
val template = engine.getTemplate("home.peb") // Компилируем шаблон home.peb
val data = mapOf("time" to LocalDateTime.now()) // Формируем данные

val writer = StringWriter()
template.evaluate(writer, data) // Записать сформированную шаблоном
println(writer)

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

Наполнение Pebble-шаблона #

<html>
  <head>
    <title>Пример шаблона</title>
  </head>
  <body>
    <p>Имя: {{ model.name }}</p>
    <p>День рождения: {{ model.birthDate | date("yyyy-MM-dd") }}
  </body>
</html>
  • Возможен вызов любых методов данных, которые были переданы
  • Шаблонизатор предоставляет множество фильтров, которые можно применить к данным {{ SOURCE | FILTER-1 | FILTER-2 }}

Доступ к переменным #

При обращении к полю переменной model.data внутри шаблона Pebble попытается у переданного объекта вызвать следующие методы:

  • Если model является словарём (наследником Map), то будет вызван метод model.get("data")
  • model.getData()
  • model.isData()
  • model.hasData()
  • model.data()
  • model.data

Если в переменной list содержится список, то к его полям можно обращаться с помощью list[0] вместо list.get(0)

Если возвращённое значение на каком-то этапе будет null, то шаблонизатор вернёт пустую строку

Передача списков данных в шаблоны #

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

class Event(val start: LocalDateTime, val description: String)
class EventsList(val events: List<Event>)

Шаблон для отображения списка событий:

{% for event in model.events %} // model содержит ссылку на объект EventsList
  <h3>Событие {{ event.description }}</h3>
  <p>Начало события: {{ event.start | date("yyyy-MM-dd HH:mm",
    timeZone="UTC") }}</p>
{% else %}
  <p>События не описаны</p>
{% endfor %}

Управляющие конструкции, вывод которых не должен попасть в текст документа, помещаются внутри {% %}

Управляющие конструкции #

{% if category == "news" %}
  {{ news }}
{% elseif category == "sports" %}
  {{ sports }}
{% else %}
  <p>Пожалуйста укажите категорию</p>
{% endif %}
  • С помощью условных конструкций необходимо адаптировать данные для отображения
  • Запрещено помещать логику по обработке, группировке, сортировке и т.п. внутри шаблона. Все эти действия необходимо произвести внутри основной логики приложения, а в шаблон необходимо передать уже подготовленные данные
  • Условные выражения поддерживают логическое объединение выражений:
    • and — логическое И
    • or — логическое ИЛИ
    • not — логическое отрицание
    • () — группировка

Проверки #

В рамках условных выражений Pebble позволяет вызывать операцию is и связывать её с проверками:

{% if 3 is odd %}
  3 является нечётным числом
{% endif %}

При использовании оператора можно проверить отрицание проверки:

{% if name is not null %}
  Имя не указано!
{% endif %}

Наследование шаблонов #

Шаблонизатор Pebble поддерживает концепцию иерархических шаблонов

  • Базовый шаблон включает в себя основное содержимое и точки расширения
  • Шаблон-расширение указывает содержимое для точек расширения

Базовый шаблон, например layout.peb:

<html>
  <body>
    <div id="content">
      {% block content %} {% endblock %}
    </div>
    <div id="footer">
      {% block footer %} Default content {% endblock %}
    </div>
  </body>
</html>

Шаблон-расширение:

{% extends "./layout.peb" %}
{% block content %} Содержимое {% endblock %}

Включение шаблонов #

Помимо иерархического разделения Pebble поддерживает включение одних шаблонов в другие

  • Шаблоны с описанием макросов
  • Шаблоны для описания типовых компонентов
  • Подменяемые статические конструкции страниц
<div class="sidebar">
  {% include "advertisement.html" %}
</div>