Статьи

Оптимизируйте Android Java-код с Kotlin

Выпущенная в качестве альтернативы iOS, первая публичная версия Android была выпущена в 2008 году. Она была основана на Java и поддерживала Java 6, которая в то время была последней версией. Прошло два года, и пришла Java 8, которая принесла новые интересные функции, включая Stream API , интерфейсы и возможность использовать лямбды вместо SAM (Single Abstract Method).

Java 9 уже на пороге, и разработчики Android застряли, используя более старые версии Java, а это значит, что многие новые функции будут для них недоступны.

Для разработчиков Android теперь существует альтернатива, Kotlin , язык JVM, разработанный JetBrains (компания, стоящая за IntelliJ и Android Studio). Как и другие языки JVM, включая Scala или Groovy , Kotlin официально не поддерживается Android, но мы можем использовать его как плагин. Создание приложения с другим языком JVM позволит вам использовать новые функции, отличные от доступных в Java 6.

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

Ваш первый файл Kotlin

Для начала установите последнюю версию Android Studio, плагины Kotlin и Kotlin Extensions For Android .

Примечание . Убедитесь, что у вас установлена ​​последняя версия Android Studio. Хотя это не важно для Kotlin, у него есть некоторые функции, которые мы собираемся использовать в этом приложении, такие как использование ресурсов Vector в качестве элементов для рисования.

Вы можете найти окончательный код этого руководства на GitHub .

Создайте новый проект под названием ToDo :

Новое приложение

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

Теперь мы добавим Kotlin в наш проект. Чтобы иметь возможность компилировать файлы Kotlin в вашем приложении, нам нужно добавить в начало файл app/build.gradle (он у меня установлен для последней сборки на момент написания, 0.14.449):

 buildscript { repositories { mavenCentral() } dependencies { classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.14.449' } } apply plugin: 'kotlin-android' 

Добавьте эту зависимость в тот же файл:

 compile 'org.jetbrains.kotlin:kotlin-stdlib:0.14.449' 

Теперь мы можем использовать файлы Kotlin вместо файлов Java в нашем приложении.

Начнем с преобразования файла MainActivity.java файл Kotlin. Откройте файл MainActivity и выберите пункт меню Код -> Преобразовать файл Java в файл Kotlin . Или вы можете дважды нажать Shift и найти «Java to Kotlin» для того же результата. MainActivity.java будет преобразован в MainActivity.kt и полученный синтаксис Kotlin, похожий, но более простой, чем Java:

 package com.aziflaj.todo import android.os.Bundle import android.support.design.widget.FloatingActionButton import android.support.design.widget.Snackbar import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar import android.view.Menu import android.view.MenuItem import android.view.View class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar = findViewById(R.id.toolbar) as Toolbar setSupportActionBar(toolbar) val fab = findViewById(R.id.fab) as FloatingActionButton fab.setOnClickListener(object : View.OnClickListener { override fun onClick(view: View) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null) .show() } }) } override fun onCreateOptionsMenu(menu: Menu): Boolean { // Inflate the menu; this adds items to the action bar if it is present. menuInflater.inflate(R.menu.menu_main, menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { val id = item.itemId if (id == R.id.action_settings) { return true } return super.onOptionsItemSelected(item) } } 

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

Разработка пользовательского интерфейса

Это приложение будет иметь три вида. Первый покажет список всех задач, добавленных пользователем в ListView . Второй показывает, когда пользователь нажимает FAB, и будет отображать, где создается новая задача. Третьим будет подробный вид задачи, где он может быть удален.

Вы можете увидеть три готовых вида ниже:

Окончательный дизайн

Давайте добавим некоторые цвета по умолчанию в файл colors.xml :

 <color name="colorPrimary">#E5AE00</color> <color name="colorPrimaryDark">#DBA600</color> <color name="colorAccent">#FFC100</color> 

Для FAB я хочу показать белый знак плюс вместо значка почты по умолчанию. Это сделает функцию FAB более понятной для пользователя. Начиная с Android Studio 1.4, вы можете использовать векторные активы в качестве элементов для рисования.

Чтобы создать знак плюс, откройте пункт меню Файл -> Создать -> Векторный актив . Выберите знак плюс из готовых значков материала. Сохраните его как ic, добавьте 24dp.xml, а затем откройте файл из папки res / drawable . Установите для свойства android:fillColor path #FFFF .

В действии main.xml_ установите для свойства android:src FAB значение @drawable/ic_add_24dp и кнопка теперь имеет знак плюс вместо почтового знака по умолчанию.

Векторные активы

Создание провайдера контента

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

Добавьте пакет ( File -> New -> Package ) с именем com.aziflaj.todo.data (соответственно изменив идентификатор) и создайте файл с именем TaskContract.kt . Поместите этот код в этот файл:

 package com.aziflaj.todo.data import android.content.ContentResolver import android.content.ContentUris import android.net.Uri import android.provider.BaseColumns object TaskContract { val CONTENT_AUTHORITY = "com.aziflaj.todo" val BASE_CONTENT_URI: Uri = Uri.parse("content://${CONTENT_AUTHORITY}") val TASK_PATH = TaskEntry.TABLE_NAME object TaskEntry { val CONTENT_URI: Uri = BASE_CONTENT_URI.buildUpon().appendPath(TASK_PATH).build() val CONTENT_TYPE = "${ContentResolver.CURSOR_DIR_BASE_TYPE}/${CONTENT_AUTHORITY}/${TASK_PATH}" val CONTENT_ITEM_TYPE = "${ContentResolver.CURSOR_ITEM_BASE_TYPE}/${CONTENT_AUTHORITY}/${TASK_PATH}" val TABLE_NAME = "tasks" val _ID = BaseColumns._ID val _COUNT = BaseColumns._COUNT val COL_TITLE = "title" val COL_DESCRIPTION = "description" fun buildWithId(id: Long): Uri { return ContentUris.withAppendedId(CONTENT_URI, id) } fun getIdFromUri(uri: Uri): Long { return ContentUris.parseId(uri) } } } 
  1. package и операторы import работают так же, как код Java
  2. Использование object является Kotlin способ создания Singleton. Обычно вам не нужны объекты TaskContract , поэтому мы TaskContract его в Singleton и создаем один его экземпляр.
  3. val CONTENT_AUTHORITY = "com.aziflaj.todo" является константой, как и final переменная. В Kotlin константы создаются с использованием ключевого слова val а переменные — с помощью ключевого слова var . Вы не видите тип константы, потому что Котлин достаточно умен , чтобы понять это.
  4. val CONTENT_URI: Uri = ... является final Uri объектом final Uri . В Kotlin вы указываете тип данных после имени переменной.
  5. "content://${CONTENT_AUTHORITY}" : это способ Kotlin для конкатенации строк. Все после знака $ и между скобками является выражением, и вместо него используется его значение.

Метод Котлина для создания методов:

 fun getIdFromUri(uri: Uri): Long { return ContentUris.parseId(uri) } 

Он начинается с ключевого слова fun , за которым следует имя метода, переменные в скобках и тип возвращаемого значения, в данном случае Long .

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

В том же пакете создайте класс с именем TaskDbHelper :

 package com.aziflaj.todo.data import android.content.Context import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper class TaskDbHelper(context: Context?) : SQLiteOpenHelper(context, TaskDbHelper.DATABASE_NAME, null, TaskDbHelper.DATABASE_VERSION) { companion object { val DATABASE_NAME = "task.db" val DATABASE_VERSION = 1 } override fun onCreate(db: SQLiteDatabase?) { val createTaskTable = "CREATE TABLE ${TaskContract.TaskEntry.TABLE_NAME} (" + "${TaskContract.TaskEntry._ID} INTEGER PRIMARY KEY, " + "${TaskContract.TaskEntry.COL_TITLE} TEXT NOT NULL, " + "${TaskContract.TaskEntry.COL_DESCRIPTION} TEXT NOT NULL, " + " UNIQUE (${TaskContract.TaskEntry.COL_TITLE}) ON CONFLICT REPLACE" + ");" db?.execSQL(createTaskTable) } override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { db?.execSQL("DROP TABLE IF EXISTS ${TaskContract.TaskEntry.TABLE_NAME}") onCreate(db) } } 

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

 class TaskDbHelper(context: Context?) : SQLiteOpenHelper(context, TaskDbHelper.DATABASE_NAME, null, TaskDbHelper.DATABASE_VERSION) 

Мы создаем конструктор по умолчанию для класса TaskDbHelper который получает параметр context и вызывает его родительский конструктор, который в Java будет:

 super(context, TaskDbHelper.DATABASE_NAME, null, TaskDbHelper.DATABASE_VERSION); 

companion object похож на ключевое слово object введенное в TaskContract . Он создает то, что в Java известно как «статические поля», которые принадлежат самому классу, а не созданным объектам. Вы можете получить доступ к этим полям так же, как в Java со статическими полями TaskDbHelper.DATABASE_NAME .

Вместо аннотации @Override в Kotlin вы можете использовать override в качестве ключевого слова, как в случае с override fun onCreate(db: SQLiteDatabase?) . Это очевидно, но что с вопросительным знаком? Этот знак вопроса, добавленный к типу объекта, означает, что объект, добавленный в метод, может быть null . Это делается для предотвращения защитного программирования и проверки вручную, является ли этот объект нулевым. Когда вы db?.execSQL(createTaskTable) к этому объекту как db?.execSQL(createTaskTable) (обратите внимание на знак вопроса), и этот объект является null , вместо того, чтобы db?.execSQL(createTaskTable) NullPointerException , Kotlin обходит вызов и не выполняет его вообще.

Наконец, сам контент-провайдер. Создайте класс с именем TaskProvider . Я начну реализовывать провайдера с создания companion object :

 package com.aziflaj.todo.data class TaskProvider : ContentProvider() { companion object { val TASK = 100 val TASK_WITH_ID = 101 fun createUriMatcher(): UriMatcher { var matcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH) val authority = TaskContract.CONTENT_AUTHORITY matcher.addURI(authority, TaskContract.TASK_PATH, TASK) matcher.addURI(authority, "${TaskContract.TASK_PATH}/#", TASK_WITH_ID) return matcher } val sUriMatcher: UriMatcher = createUriMatcher() var mOpenHelper: SQLiteOpenHelper? = null } } 

Это создает UriMatcher чтобы сказать, UriMatcher ли Uri указанный пользователем, на запись в таблице или на саму таблицу.

Метод onCreate() прост, поэтому мы можем приступить к его реализации:

 override fun onCreate(): Boolean { mOpenHelper = TaskDbHelper(context) return true } 

Он создает SQLiteOpenHelper используя TaskDbHelper мы создали ранее. Вы можете заметить здесь еще одну особенность Kotlin. Вместо использования getContext() мы обращаемся к контексту как к context .

Другой метод, который мы реализуем, это метод query() , вероятно, самый сложный метод поставщика контента:

 override fun query(uri: Uri?, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? { val db: SQLiteDatabase = mOpenHelper?.readableDatabase as SQLiteDatabase val match: Int = sUriMatcher.match(uri) var cursor: Cursor? when (match) { TASK -> { cursor = db.query(TaskContract.TaskEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder) } TASK_WITH_ID -> { val id: Long = TaskContract.TaskEntry.getIdFromUri(uri as Uri) cursor = db.query(TaskContract.TaskEntry.TABLE_NAME, projection, "${TaskContract.TaskEntry._ID} = ?", arrayOf(id.toString()), null, null, sortOrder) } else -> throw UnsupportedOperationException("Unknown uri: $uri") } cursor?.setNotificationUri(context.contentResolver, uri) return cursor } 

4-я строка кода может показаться странной, особенно as SQLiteDatabase . Это котлинский способ кастинга. Приведение может быть знакомо вам при использовании метода findViewById . Проверьте MainActivity.kt и вы увидите тот же метод приведения FAB по идентификатору.

Блок when является эквивалентом блока switch .

arrayOf(id.toString()) содержит три интересных вещи о Kotlin. Метод arrayOf — это встроенный метод Kotlin, который генерирует массив из списка аргументов, разделенных запятыми. Метод toString вызывается непосредственно в переменную Long . Это означает, что toString является методом расширения. Вы можете создавать новые методы (как методы расширения) для класса, и в этом случае вызывать их как 10.toString() . Kotlin по мере необходимости заботится об автобоксе, поэтому целочисленная переменная может быть int или Integer , в зависимости от ситуации.

Мы будем реализовывать другие методы контент-провайдера таким же образом. Для метода insert() :

 override fun insert(uri: Uri?, values: ContentValues?): Uri? { val db = mOpenHelper?.writableDatabase val match: Int = sUriMatcher.match(uri) var insertionUri: Uri? var insertedId: Long when (match) { TASK -> { insertedId = db!!.insert(TaskContract.TaskEntry.TABLE_NAME, null, values) insertionUri = if (insertedId > 0) { TaskContract.TaskEntry.buildWithId(insertedId) } else { throw SQLException("Failed to insert row into $uri") } } else -> throw UnsupportedOperationException("Unknown uri: $uri") } context.contentResolver.notifyChange(uri, null) return insertionUri } 

Новым здесь является вызов метода db!!.insert(...) . !! означает, что вы уверены, что экземпляр не будет null , поэтому в любом случае вызовите метод insert . Также новым здесь является то, что мы присвоили блок if переменной insertionUri . В Kotlin большинство блоков возвращают значение, которое означает, что вы можете назначить блок переменной. Выше, TaskContract.TaskEntry.buildWithId(insertedId) будет TaskContract.TaskEntry.buildWithId(insertedId) если insertedId TaskContract.TaskEntry.buildWithId(insertedId) больше 0 или TaskContract.TaskEntry.buildWithId(insertedId) SQLException .

Метод delete() :

 override fun delete(uri: Uri?, selection: String?, selectionArgs: Array<out String>?): Int { val db = mOpenHelper?.writableDatabase val match = sUriMatcher.match(uri) var deleted: Int var customSelection = selection ?: "1" when (match) { TASK -> deleted = db!!.delete(TaskContract.TaskEntry.TABLE_NAME, customSelection, selectionArgs) else -> throw UnsupportedOperationException("Unknown uri: $uri") } if (deleted > 0) { context.contentResolver.notifyChange(uri, null) } return deleted } 

Новым здесь является оператор ?: (Оператор AKA Elvis), который говорит, что если selection не равен null , customSelection получит свое значение, в противном случае он станет "1" .

Я не собираюсь показывать другие методы поставщика контента, потому что они не вводят новый синтаксис Kotlin. Вы можете найти другие методы контент-провайдера на GitHub

Добавьте поставщика содержимого в файл AndroidManifest.xml непосредственно перед закрывающим тегом приложения.

 <provider android:authorities="com.aziflaj.todo" android:name=".data.TaskProvider" /> 

Теперь контент-провайдер готов к использованию.

Завершение пользовательского интерфейса

Мы создадим ListView для MainActivity . Внутри содержимого файла main.xml_ и этого кода:

 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".MainActivity" tools:showIn="@layout/activity_main"> <ListView android:id="@+id/task_listview" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> 

Создайте пустое действие под названием CreateTaskActivity . Это создаст файл CreateTaskActivity.java (который вы должны конвертировать в Kotlin) и файл create task.xml, который мы сейчас отредактируем. Это файл макета, который показывает, когда пользователь нажимает FAB. Мы создадим форму для новой задачи:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.aziflaj.todo.CreateTaskActivity"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/task.create.title" android:textColor="@color/gray_dark" android:paddingBottom="@dimen/activity_horizontal_margin" android:textSize="@dimen/text_large" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <android.support.design.widget.TextInputLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:paddingRight="@dimen/activity_horizontal_margin"> <EditText android:id="@+id/task_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:capitalize="words" android:enabled="true" android:hint="@string/task.title.placeholder" /> </android.support.design.widget.TextInputLayout> <Button android:id="@+id/save_task_btn" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@color/colorAccent" android:text="@string/task.button.save" android:textColor="@color/white" /> </LinearLayout> <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/task_description" android:layout_width="match_parent" android:layout_height="wrap_content" android:capitalize="sentences" android:hint="@string/task.description.placeholder" android:inputType="textMultiLine" /> </android.support.design.widget.TextInputLayout> </LinearLayout> 

Добавьте эти значения в string.xml :

 <string name="task.create.title">Create a new task</string> <string name="task.title.placeholder">Task title</string> <string name="task.description.placeholder">Task description</string> <string name="task.button.save">Save</string> 

И до colors.xml`

 <!-- grayscale --> <color name="white">#FFF</color> <color name="gray_light">#DBDBDB</color> <color name="gray">#939393</color> <color name="gray_dark">#5F5F5F</color> <color name="black">#323232</color> 

И для измеренияs.xml :

 <dimen name="text_large">30sp</dimen> 

Создайте новое действие с именем TaskDetailsActivity и преобразуйте его в Kotlin. Добавьте этот код в задачу задачи details.xml :

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.aziflaj.todo.TaskDetailsActivity"> <TextView android:id="@+id/detail_task_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="@dimen/activity_vertical_margin" android:textSize="@dimen/text_large" /> <TextView android:id="@+id/detail_task_description" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="@dimen/text_medium" /> </LinearLayout> 

Добавьте это в dimensions.xml :

 <dimen name="text_medium">18sp</dimen> 

Далее мы создадим меню для этого представления. Удалить меню main.xml_ file. Удалите onCreateOptionsMenu и onOptionsItemSelected из MainActivity.kt . Создайте новый файл ресурсов меню:

Подробное меню

 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_delete" android:icon="@android:drawable/ic_menu_delete" android:title="@string/task.details.menu.delete" app:showAsAction="always" /> </menu> 

Добавьте это в strings.xml :

 <string name="task.details.menu.delete">Delete</string> 

В TaskDetailsActivity.kt переопределите необходимые методы:

 override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.task_details_menu, menu) return true } override fun onOptionsItemSelected(item: MenuItem?): Boolean { val id = item?.itemId when (id) { R.id.action_delete -> { //delete task return true } } return super.onOptionsItemSelected(item) } 

Здесь мы расширяем меню и готовим его для обработки пользовательских событий, нажав кнопку «Удалить» в меню.

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

Хранение новых задач

Мы используем класс CreateTaskActivity для вставки новых задач, но пользователю нужен способ переключения на это представление. Откройте файл MainActivity.kt и установите для этого слушателя значение FAB:

 val newTaskFab = findViewById(R.id.fab) as FloatingActionButton newTaskFab.setOnClickListener({ view -> val newTaskIntent = Intent(applicationContext, CreateTaskActivity::class.java) startActivity(newTaskIntent) }) 

Теперь мы готовы хранить новые задачи во встроенной базе данных SQLite. У нас есть провайдер контента и пользовательский интерфейс, теперь мы читаем из полей EditText и сохраняем данные в базе данных. onCreate методе onCreate файла CreateTaskActivity.kt после настройки макета представления ( setContentView(R.layout.activity_create_task) ) добавьте этот код:

 val saveBtn = findViewById(R.id.save_task_btn) as Button saveBtn.setOnClickListener({ view -> val taskTitleEditText = findViewById(R.id.task_title) as EditText val taskTitle = taskTitleEditText.text.toString() val taskDescriptionEditText = findViewById(R.id.task_description) as EditText val taskDescription: String = taskDescriptionEditText.text.toString() if (taskTitle.isEmpty() or taskDescription.isEmpty()) { val inputEmpty = getString(R.string.error_input_empty) Toast.makeText(applicationContext, inputEmpty, Toast.LENGTH_LONG).show() } else { val values = ContentValues() values.put(TaskEntry.COL_TITLE, taskTitle) values.put(TaskEntry.COL_DESCRIPTION, taskDescription) var inserted = contentResolver.insert(TaskEntry.CONTENT_URI, values) startActivity(Intent(this, MainActivity::class.java)) Log.d("New Task", "inserted: $inserted") } }) 

Здесь мы используем лямбду вместо интерфейса SAM. Это Kotlin способ написания лямбда-методов, где view — это аргумент, а после -> lambda body. С помощью этого кода мы читаем из полей и храним данные в базе данных, используя метод insert() провайдера контента. Если заголовок или описание задачи пусто, мы показываем ошибку как тост. Добавьте сообщение об ошибке в файл strings.xml :

 <string name="error.input.empty">Title or description is empty</string> 

Когда вы добавляете новую запись в базу данных, вы можете увидеть в LogCat, добавлена ​​она или нет.

Перечислите ВСЕ задачи

Для перечисления задач мы будем использовать CursorLoader который запрашивает базу данных и создает объект Cursor . Этот объект Cursor используется объектом CursorAdapter для заполнения ListView в классе MainActivity .

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

Создайте файл макета с именем taskview item.xml :

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/list_item_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/gray" android:textSize="@dimen/text_large" /> </LinearLayout> 

Мы расширим это в CursorAdapter . Поэтому создайте класс Kotlin с именем TaskAdapter и добавьте этот код:

 package com.aziflaj.todo import android.content.Context import android.database.Cursor import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.CursorAdapter import android.widget.TextView import com.aziflaj.todo.data.TaskContract class TaskAdapter(context: Context, cursor: Cursor?, flags: Int) : CursorAdapter(context, cursor, flags) { override fun newView(context: Context?, cursor: Cursor?, parent: ViewGroup?): View? { return LayoutInflater.from(context).inflate(R.layout.task_listview_item, parent, false) } override fun bindView(view: View?, context: Context?, cursor: Cursor?) { var titleTextView = view?.findViewById(R.id.list_item_title) as TextView val TITLE_COL_INDEX = cursor?.getColumnIndex(TaskContract.TaskEntry.COL_TITLE) as Int val taskTitle = cursor?.getString(TITLE_COL_INDEX) titleTextView.text = taskTitle } } 

Откройте MainActivity.kt и заставьте его реализовать android.app.LoaderManager.LoaderCallbacks<Cursor> :

 class MainActivity : AppCompatActivity(), android.app.LoaderManager.LoaderCallbacks<Cursor> { override fun onCreateLoader(id: Int, args: Bundle?): android.content.Loader<Cursor>? { return CursorLoader(applicationContext, TaskContract.TaskEntry.CONTENT_URI, null, null, null, null) } override fun onLoadFinished(loader: android.content.Loader<Cursor>?, data: Cursor?) { taskAdapter?.swapCursor(data) } override fun onLoaderReset(loader: android.content.Loader<Cursor>?) { taskAdapter?.swapCursor(null) } companion object { val TASK_LOADER = 0 //the loader id var taskAdapter: TaskAdapter? = null var listView: ListView? = null } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar = findViewById(R.id.toolbar) as Toolbar setSupportActionBar(toolbar) loaderManager.initLoader(TASK_LOADER, null, this) listView = findViewById(R.id.task_listview) as ListView taskAdapter = TaskAdapter(applicationContext, null, 0) listView?.adapter = taskAdapter listView?.setOnItemClickListener({ parent, view, position, id -> val currentTask: Cursor? = parent.getItemAtPosition(position) as Cursor var detailsIntent = Intent(this, TaskDetailsActivity::class.java) val TASK_ID_COL = currentTask?.getColumnIndex(TaskContract.TaskEntry._ID) as Int val _id = currentTask?.getLong(TASK_ID_COL) as Long val taskUri = TaskContract.TaskEntry.buildWithId(_id) detailsIntent.setData(taskUri) startActivity(detailsIntent) }) val newTaskFab = findViewById(R.id.fab) as FloatingActionButton newTaskFab.setOnClickListener({ view -> val newTaskIntent = Intent(applicationContext, CreateTaskActivity::class.java) startActivity(newTaskIntent) }) } } 

Свяжите адаптер с ListView , добавив этот код в метод onCreate :

 listView = findViewById(R.id.task_listview) as ListView taskAdapter = TaskAdapter(applicationContext, null, 0) listView?.adapter = taskAdapter 

Детальный просмотр задач

Если щелкнуть задачу, ничего не происходит. Мы хотим перенаправить пользователя на подробный вид задачи. Добавьте этот фрагмент после настройки адаптера представления списка:

 listView?.setOnItemClickListener({ parent, view, position, id -> val currentTask: Cursor? = parent.getItemAtPosition(position) as Cursor var detailsIntent = Intent(this, TaskDetailsActivity::class.java) val TASK_ID_COL = currentTask?.getColumnIndex(TaskContract.TaskEntry._ID) as Int val _id = currentTask?.getLong(TASK_ID_COL) as Long val taskUri = TaskContract.TaskEntry.buildWithId(_id) detailsIntent.setData(taskUri) startActivity(detailsIntent) }) 

Откройте TaskDetailsActivity.kt и замените код следующим:

 package com.aziflaj.todo import android.content.Intent import android.net.Uri import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.widget.TextView import com.aziflaj.todo.data.TaskContract class TaskDetailsActivity : AppCompatActivity() { companion object { var taskId = 0L } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_task_details) val taskUri = intent?.data as Uri taskId = TaskContract.TaskEntry.getIdFromUri(taskUri) val cursor = contentResolver.query(taskUri, null, null, null,null) cursor.moveToFirst() val taskTitle = cursor.getString(cursor.getColumnIndex(TaskContract.TaskEntry.COL_TITLE)) val taskDescr = cursor.getString(cursor.getColumnIndex(TaskContract.TaskEntry.COL_DESCRIPTION)) var titleTextView = findViewById(R.id.detail_task_title) as TextView var descrTextView = findViewById(R.id.detail_task_description) as TextView titleTextView.text = taskTitle descrTextView.text = taskDescr } override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.task_details_menu, menu) return true } override fun onOptionsItemSelected(item: MenuItem?): Boolean { val id = item?.itemId when (id) { R.id.action_delete -> { val deleted = contentResolver.delete( TaskContract.TaskEntry.CONTENT_URI, "${TaskContract.TaskEntry._ID} = ?", arrayOf(taskId.toString())) if (deleted == 1) { startActivity(Intent(this, MainActivity::class.java)) } return true } } return super.onOptionsItemSelected(item) } } 

Мы тут:

  • Получить Uri с намерением
  • Запросите базу данных на основе Uri, и получите название и описание задачи
  • Обновите интерфейс с запрошенными значениями.
  • Добавьте функциональность к кнопке удаления в меню

Завершение

Котлин: Swift для Android

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

Kotin не называется Swift для Android без причины, он ускоряет и упрощает процесс разработки, действительно делая его « быстрым ». В заключение, каковы преимущества и недостатки?

Преимущества Котлина

  • Быстрее развивать
  • Проще поддерживать
  • Нет NullPointerException и защитного программирования

Недостатки Котлина

  • Большой размер APK (5,2 МБ для приложения, которое мы разработали)
  • Более медленный процесс компиляции

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

Что вы думаете о Kotlin? Вы бы использовали это?