Первое приложение на Kotlin для Android

Первое приложение на Kotlin для Android

Знакомимся с понятиями, вникаем в операторы и ветвления, осваиваем платформу
11 минут8058

В этой статье мы пошагово рассмотрим, как написать своё первое игровое приложение. В процессе разработки вы познакомитесь с базой языка Kotlin: переменными, операторами, ветвлениями, — а также с основами программирования на платформе Android: созданием макетов, слушателями событий, запуском приложения.

Для начала поговорим в целом о Kotlin. 

Востребованность языка

Kotlin получил признание ещё до релиза версии 1.0 в феврале 2016 года. Специалисты ценят его за краткость, выразительность и безопасность. После того как Google заявил о полной поддержке этого языка для разработки Android-приложений, популярность Kotlin выросла экспоненциально. Сейчас он используется при создании практически всех новых приложений для этой ОС. Благодаря полной взаимозаменяемости с Java, его подключают и к существующим проектам, если планируется их долгосрочная поддержка. Так что Kotlin сегодня — отраслевой стандарт в Android-разработке. Он востребован, и это его главное преимущество.

Лаконичность — важное преимущество

Kotlin позволяет писать те же вещи, что и на Java, но обходиться значительно меньшим количеством кода. Например:

public class Person {
   private String firstName;
   private String lastName;
   private int age;
 
   public Person(String firstName, String lastName, int age) {
       this.firstName = firstName;
       this.lastName = lastName;
       this.age = age;
   }
 
   public String getFirstName() {
       return firstName;
   }
 
   public String getLastName() {
       return lastName;
   }
 
   public int getAge() {
       return age;
   }
}

Это простой класс для описания человека (имя, фамилия, возраст), который используется в Java для представления простого объекта. Теперь то же, только на Kotlin:

data class Person(val firstName: String, val lastName: String, val age: Int)

Впечатляет, не правда ли? 

Безопасность языка

Как и Java, Kotlin — язык со статической типизацией. Так что синтаксические ошибки и баги, связанные с неправильным обращением к объектам, отлавливаются во время сборки проекта. Благодаря этому оперативно выявляется неработоспособный код, определяются и исправляются ошибки. 

Полная совместимость с Java

Kotlin спроектирован так, чтобы его можно было применять везде, где используется Java: они полностью совместимы. Одна часть кода приложения может быть на Java, а другая — на Kotlin, и всё будет прекрасно работать. Вы сможете обращаться к классам, написанным на Kotlin, из Java-кода и наоборот без ограничений.

Кстати, при разработке Android-приложений применяется Java не выше 7-й версии, где недоступны stream api, методы интерфейсов по умолчанию и другие возможности. Android Studio 3.0 позволяет использовать только некоторые фичи Java 8. В Kotlin же есть все эти инструменты — и даже больше.

История создания Kotlin

Разработку Kotlin в 2010 году начали специалисты компании JetBrains. Их перестали устраивать возможности Java, и они решили создать свой язык программирования, объединяющий лучшие черты существующих ЯП. Название Kotlin — в честь острова в Финском заливе, на котором расположен город Кронштадт. В 2011 году новый язык представили публике, а в 2012-м открыли его исходный код. Всё это время велась интенсивная разработка и тестирование. В 2016-м выпустили релизную версию 1.0. Уже тогда многие Android-разработчики использовали Kotlin в своих проектах. После объявления на Google I/O 2017 о полной поддержке языка для создания мобильных приложений, Kotlin стал чрезвычайно популярен в профессиональном сообществе.

Практика

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

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

Теперь, когда вы запустили среду разработки и создали проект, у вас автоматически открыто две вкладки: для создания макета экрана приложения (activity_main.xml) и для программирования (MainActivity.kt). Начнём с макета. Нам нужно нарисовать вот такой экран:

Для этого откройте вкладку activity_main.xml. Здесь всё написано на языке разметки XML. Все визуальные элементы, которые пользователь видит на экране любого приложения, описаны с помощью XML. По сути это набор тегов (<Кнопка>) и атрибутов внутри тегов (цвет_кнопки, размер_кнопки, текст_кнопки). Замените то, что есть во вкладке сейчас, вот этим кодом:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
 
  <androidx.appcompat.widget.AppCompatButton
     android:id="@+id/buttonStartGame"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_marginTop="100dp"
     android:text="Старт!"
     app:layout_constraintLeft_toLeftOf="parent"
     app:layout_constraintRight_toRightOf="parent"
     app:layout_constraintTop_toTopOf="parent" />
 
  <TextView
     android:id="@+id/textViewResult"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:textColor="#FF0000"
     android:textSize="30sp"
     app:layout_constraintLeft_toLeftOf="parent"
     app:layout_constraintRight_toRightOf="parent"
     app:layout_constraintTop_toBottomOf="@id/buttonStartGame" />
 
  <androidx.appcompat.widget.AppCompatEditText
     android:id="@+id/editTextUserNumber"
     android:layout_width="150dp"
     android:layout_height="wrap_content"
     android:gravity="center_horizontal"
     android:hint="Введите число"
     android:inputType="number"
     android:maxLength="1"
     app:layout_constraintLeft_toLeftOf="parent"
     app:layout_constraintRight_toRightOf="parent"
     app:layout_constraintTop_toBottomOf="@id/textViewResult" />
 
  <androidx.appcompat.widget.AppCompatButton
     android:id="@+id/buttonGuess"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="Угадать"
     app:layout_constraintLeft_toLeftOf="parent"
     app:layout_constraintRight_toRightOf="parent"
     app:layout_constraintTop_toBottomOf="@id/editTextUserNumber" />
</androidx.constraintlayout.widget.ConstraintLayout>

Теперь давайте разберёмся, что всё это значит. В качестве контейнера для всех наших элементов (кнопок, полей для ввода и т. д.) мы используем ConstraintLayout. Внутри него можно размещать элементы относительно друг друга, и они будут сохранять это расположение независимо от типа и размера экрана. Это очень важно, учитывая, сколько разных телефонов работает на операционной системе Android. 

Первым элементом в нашем контейнере будет кнопка «Старт» (AppCompatButton). У неё есть следующие атрибуты:

  • android:id — уникальный идентификатор, по которому мы найдём этот элемент в MainActivity.kt;
  • android:layout_width — ширина кнопки, которая равна длине текста;
  • android:layout_height — высота по умолчанию;
  • android:layout_marginTop — отступ от верхнего края экрана;
  • android:text: — надпись на кнопке;
  • app:layout_constraintLeft_toLeftOf — левый край кнопки притягивается к левому краю контейнера; app:layout_constraintRight_toRightOf — правый край кнопки притягивается к правому краю контейнера. В итоге кнопка располагается по центру экрана в горизонтальной плоскости;
  • app:layout_constraintTop_toTopOf — верхний край кнопки притягивается к верхнему краю контейнера. То есть в нашем случае кнопка всегда будет находиться на расстоянии 100 пикселей от верхнего края экрана.

Далее идёт текстовое поле, где мы будем отображать результат (начало игры, угадал пользователь число или нет). Для этого используем TextView:

  • android:id — идентификатор;
  • android:layout_width — ширина равна ширине отображаемого текста (пока там никакого текста нет);
  • android:layout_height — высота по умолчанию;
  • android:textColor — цвет текста. Мы указали красный с помощью шестнадцатеричного кода (#FF0000);
  • android:textSize — размер текста;
  • app:layout_constraintLeft_toLeftOf и app:layout_constraintRight_toRightOf — указывают на то, что текст всегда будет по центру;
  • app:layout_constraintTop_toBottomOf — верхний край текста будет всегда под нижним краем кнопки «Старт».

Теперь займёмся полем для ввода числа, которое пытается угадать пользователь. Это AppcompatEditText:

  • android:id — идентификатор;
  • android:layout_width — ширина;
  • android:layout_height — высота (у нас выставлено значение по умолчанию);
  • android:gravity — число внутри поля ввода будет вводиться по центру, а не с левого края;
  • android:hint — подсказка;
  • android:inputType — будет сразу выдвигаться числовая клавиатура;
  • android:maxLength — можно ввести число, состоящее только из одной цифры;
  • app:layout_constraintLeft_toLeftOf и app:layout_constraintRight_toRightOf — указывают на то, что поле всегда будет по центру;
  • app:layout_constraintTop_toBottomOf: — верхний край поля ввода будет всегда под нижним краем текста с результатом.

И последний элемент — ещё одна кнопка, нажимая на которую пользователь будет узнавать, угадал он число или нет:

  • android:id — идентификатор;
  • android:layout_width — ширина равна ширине отображаемого текста (пока там никакого текста нет);
  • android:layout_height — высота по умолчанию;
  • android:text — надпись на кнопке;
  • app:layout_constraintLeft_toLeftOf и app:layout_constraintRight_toRightOf — указывают на то, что кнопка всегда будет по центру;
  • app:layout_constraintTop_toBottomOf — верхний край кнопки будет всегда под нижним краем поля для ввода.

Запустите эмулятор и убедитесь, что всё отображается как надо. Теперь перейдём непосредственно к коду. Activity — это и есть экран вашего приложения. Туда передаётся макет, который мы с вами только что нарисовали, там обрабатываются все нажатия на кнопки и решается, какие надписи выводить в текстовых полях. Откройте вкладку MainActivity.kt и замените весь код на этот (только не трогайте верхнюю строку вида package example.app.myapp):

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlin.random.Random
 
class MainActivity : AppCompatActivity() {
 
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
 
       var count = 0
       var userGuess = -1
       var randomNumber = -1
       buttonStartGame.setOnClickListener {
           randomNumber = Random.nextInt(0, 10)
           textViewResult.text = "Угадайте число от 0 до 9"//Мы же программисты!
       }
       buttonGuess.setOnClickListener {
           if (randomNumber < 0) {
               randomNumber = Random.nextInt(0, 10)
           }
 
           val userInput = editTextUserNumber.text.toString()
           userGuess = userInput.toInt()
 
           if (count < 2) {
               if (userGuess == randomNumber) {
                   textViewResult.text = "Угадали! Еще раз?"
                   count = 0
                   userGuess = -1
                   randomNumber = -1
               } else {
                   textViewResult.text = if (userGuess > randomNumber) "меньше!" else "больше!"
                   count++                  
               }
           } else {
               textViewResult.text = if (userGuess == randomNumber) "Угадали! Еще раз?" else "Не угадали :( Еще раз?"
               count = 0
               userGuess = -1
               randomNumber = -1
           }
       }
   }
} 

Ещё один массив кода, который нужно разобрать :) Начнём по порядку: сверху вниз и слева направо.

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

Далее идёт class MainActivity : AppcompatActivity() и открывающая фигурная скобка. Ключевое слово class используется для обозначения классов в языке. Это конструкции, в которых происходит всё основное действие: объявляются переменные, другие классы, пишутся функции. Все программы состоят из классов, иногда их могут быть тысячи.

После ключевого слова пишется название класса (MainActivity). Имена классов принято писать в CamelCase (СловаСлитноКаждоеСБольшойБуквы). Классам, как и другим сущностям (функциям, переменным), рекомендуется давать мнемонические, то есть «говорящие», имена. После двоеточия идёт ещё один класс. Это значит, что мы наследуемся от этого класса и получаем все его возможности. После имени в фигурных скобках записывается тело класса. Оно может содержать поля, функции и другие классы. Переменные хранят данные — в этом мы убедимся, разрабатывая приложение. Функции содержат набор команд или код. Имена функций (переменных) принято писать в стиле camelCase (какКлассыНоСоСтрочнойПервойБуквы).

В нашем классе только одна функция — onCreate(). Она есть в каждой Activity (экране). Именно с неё начинается запуск и отображение экрана (без этой функции ваш экран просто не покажется).

Помимо аргументов функции (Bundle ...), которые мы не будем использовать, и обращения к родительскому методу (super...), который тоже, как вы заметили, вставляется по умолчанию, есть метод setContentView. В него в качестве аргумента передаётся наш макет экрана. Без него экран запустится, но будет пустым.

Далее мы видим объявление сразу трёх переменных:

  • var count = 0
  • var userGuess = -1
  • var randomNumber = -1

Давайте разбираться. Переменные хранят какие-то данные (числа, строки, другие классы, булевы значения, функции). Это ячейки в памяти телефона, в которых и хранятся значения. Значения можно записать в ячейку и прочитать. В Kotlin перед использованием переменной её нужно объявить и указать тип, так как мы имеем дело с языком со строгой типизацией. Но если тип очевиден, то его можно опустить: компилятор Kotlin сам догадается, какой тип у переменной.

Переменные могут быть примитивными (числа, символы, логические значения) или ссылочными (массивы, строки, любые другие объекты). В Kotlin восемь примитивных типов (byte, int, short, long, float, double, boolean, char) и бесконечное количество ссылочных. Имя переменной может содержать буквы и цифры, знак подчёркивания. Оно не должно начинаться с цифры. В профессиональной разработке в именах переменных используют только буквы. Имя должно явно указывать на назначение переменной — чтобы сразу было понятно, для чего она нужна. У нас три переменные:

  • var count = 0. Это количество попыток, которые использовал игрок. После каждой попытки count будет увеличиваться на 1. И когда дойдёт до 2 (0, 1, 2 — три попытки), игра закончится. В программировании счёт всегда ведётся с 0, а не с 1, как мы привыкли в повседневной жизни;
  • var userGuess = -1. Число, которое ввёл пользователь. Изначально оно равно -1: так мы уверены, что пользователь ещё ничего не ввёл;
  • var randomNumber = -1. Число, загаданное приложением. Изначально оно равно -1: так мы уверены, что игра ещё не началась.

Вы, конечно, заметили, что перед каждой переменной стоит ключевое слово var. Объявление переменных в Kotlin немного отличается от Java. В начале объявления указывается ключевое слово var или val, затем имя переменной и далее её тип или сразу значение. Если переменная инициализируется сразу (как в нашем случае) в месте объявления, тип можно не указывать. Компилятор выведет его из типа присвоенного значения. В Kotlin существует два типа переменных: 

  • var — изменяемая, то есть значение ей может присваиваться сколько угодно раз;
  • val — неизменяемая (final в терминах Java), присвоить ей значение можно только единожды, но считывать её по-прежнему можно многократно.

Создатели Kotlin рекомендуют использовать val там, где это возможно. Такой подход позволяет избежать ошибок и писать более надёжный код. Так как у нас все переменные должны обновлять свои значения с каждой новой игрой (или даже в процессе игры), то все наши переменные — var.

Далее идёт следующий код:

buttonStartGame.setOnClickListener {
           randomNumber = Random.nextInt(0, 10)
           textViewResult.text = "Угадайте число от 0 до 9"
       }

 

Тут мы находим кнопку в макете XML (buttonStartGame) и вешаем на неё «слушатель нажатий» (setOnClickListener). Это значит, что, когда пользователь нажмёт на эту кнопку, выполнится код в фигурных скобках. 

В них мы обращаемся к одной из наших переменных и присваиваем ей случайное значение с помощью класса Random и его функции nextInt. Она позволяет передать переменной границы, в рамках которых мы хотим получить случайное число.

После этого сразу находим наше текстовое поле и выводим там текст "Угадайте число от 0 до 9". Можете удалить весь код, что находится ниже, и запустить приложение:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlin.random.Random
 
class MainActivity : AppCompatActivity() {
 
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
 
       var count = 0
       var userGuess = -1
       var randomNumber = -1
       buttonStartGame.setOnClickListener {
           randomNumber = Random.nextInt(0, 10)
           textViewResult.text = "Угадайте число от 0 до 9"//Мы же программисты!
       }
   }
}

Приложение уже работает и реагирует на ваши нажатия! Возвращаемся к основному массиву кода и дальше видим следующее:

buttonGuess.setOnClickListener {
           if (randomNumber < 0) {
               randomNumber = Random.nextInt(0, 10)
           }...

Как вы уже поняли, мы повесили слушатель нажатий на кнопку «Угадать». А дальше идёт интересная конструкция, начинающаяся с ключевого слова if («если»). Это так называемая проверка условия, или ветвления: когда мы сравниваем какие-то логические выражения (те, на которые можно ответить только «да» или «нет») и, ориентируясь на ответ, выполняем то или иное действие. 

Любой язык программирования высокого уровня позволяет изменять порядок выполнения программы в зависимости от заданного условия. В Kotlin для этого используется конструкция if. После этого ключевого слова в скобках записывается логическое выражение, затем в фигурных скобках — группа команд. Если результат логического выражения — true (истина), то эта группа команд выполняется, если false (ложь) — нет. Есть ещё ключевое слово else, которое может идти после условия и обозначать «в противном случае делаем это».

В коде мы проверяем условие (randomNumber < 0). Если randomNumber меньше нуля, то генерируем случайное число. Если же больше или равен нулю, это значит, что случайное число уже сгенерировано (игра в самом разгаре!) и ничего делать не надо: можно выполнять программу дальше. 

А затем мы получаем значение, которое вписал пользователь в поле для ввода. Обращаемся к editTextUserNumber, берём у него значение и переводим в строку. Чтобы сохранить это значение, мы объявили переменную userInput (причём с ключевым словом val, потому что это значение нам не нужно изменять). И затем сразу присваиваем значение переменной userGuess. Но так как userGuess у нас типа Int, а userInput — String, надо строку перевести в число с помощью функции toInt().

Дальше мы проверяем следующее условие: если число попыток меньше двух (то есть пользователь ещё не использовал все), то продолжаем игру. В ином случае (ключевое слово else) заканчиваем игру, выводим результат и предлагаем сыграть снова. Если у пользователя остались попытки — проверяем следующее условие: число, которое он ввёл, равно случайному числу? Обратите внимание, как происходит сравнение через оператор ==. В программировании это сравнение на одинаковость, а = — это оператор присвоения значений. Не путайте их.

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

if (userGuess == randomNumber) {
            textViewResult.text = "Угадали! Еще раз?"
             count = 0
             userGuess = -1
             randomNumber = -1
 } else {
             textViewResult.text = if (userGuess > randomNumber) "меньше!" else "больше!"
             count++            
 }

Если пользователь не угадал (else...), мы выводим в текстовое поле результат и увеличиваем количество использованных попыток на одну. Обратите внимание, как всё это реализуется. Дело в том, что в Kotlin (в отличие от Java) выражения сравнения можно использовать в качестве результата. Именно поэтому мы можем так лаконично отобразить текст в текстовом поле: text = if (userGuess > randomNumber) "меньше!" else "больше!". Если число пользователя больше случайного — мы пишем в текстовом поле слово «меньше», а в противном случае (else) «больше». 

Второй интересный момент — выражение count++. Это просто увеличение текущего значения (count) на единицу. То же самое, что написать count = count + 1.

В этом блоке кода мы проверили число пользователя на равенство (userGuess == randomNumber) на «больше или меньше» (if (userGuess > randomNumber) "меньше!" else "больше!") и произвели соответствующие действия. Вот вам классическое ветвление и управление программой в зависимости от результата сравнения. 

Переходим к последнему блоку кода в приложении. Если у пользователя не остаётся попыток (else...), мы заканчиваем игру.

               textViewResult.text =
                   if (userGuess == randomNumber) "Угадали! Еще раз?" else "Не угадали :( Еще раз?"
               count = 0
               userGuess = -1
               randomNumber = -1

Выводим результат в текстовое поле по аналогии с предыдущим и обнуляем значения count и userGuess. Вот и всё! Ваше первое приложение готово.

Бонус: предотвращаем ошибки

Попробуйте оставить поле пустым и нажать на кнопку «Угадать». Приложение упадёт. Это происходит потому, что мы пытаемся конвертировать в число пустую строку:

val userInput = editTextUserNumber.text.toString()
userGuess = userInput.toInt()

Чтобы этого избежать, давайте добавим ещё одну проверку:

if (userInput.isBlank()) {
   textViewResult.text = "Введите число!"
} else {...

Тут мы проверяем, не пустует ли поле ввода. Функция isBlank возвращает нам true, если в поле ничего нет, и false, если число вписано. При пустом поле мы показываем текст «Введите число», в противном случае — продолжаем программу. 

Полный код будет выглядеть так:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlin.random.Random
 
class MainActivity : AppCompatActivity() {
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        var count = 0
        var userGuess = -1
        var randomNumber = -1
        buttonStartGame.setOnClickListener {
            randomNumber = Random.nextInt(0, 10)
            textViewResult.text = "Угадайте число от 0 до 9"//Мы же программисты!
        }
        buttonGuess.setOnClickListener {
            if (randomNumber < 0) {
                randomNumber = Random.nextInt(0, 10)
            }
            val userInput = editTextUserNumber.text.toString()
            if (userInput.isBlank()) {
                textViewResult.text = "Введите число!"
            } else {
                userGuess = userInput.toInt()
                if (count < 2) {
                    if (userGuess == randomNumber) {
                        textViewResult.text = "Угадали! Еще раз?"
                        count = 0
                        userGuess = -1
                        randomNumber = -1
                    } else {
                        textViewResult.text = if (userGuess > randomNumber) "меньше!" else "больше!"
                        count++
                    }
                } else {
                    textViewResult.text = if (userGuess == randomNumber) "Угадали! Еще раз?" else "Не угадали :( Еще раз?"
                    count = 0
                    userGuess = -1
                    randomNumber = -1
                }
            }
        }
    }
}

Конечно, код этого приложения можно улучшить и оптимизировать. В качестве самостоятельной работы подумайте, как бы вы это сделали? Буду рад услышать ваши ответы в комментариях :)

программированиеandroid
Нашли ошибку в тексте? Напишите нам.
Спасибо,
что читаете наш блог!
Posts popup