Выпущенная в качестве альтернативы 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) } } }
-
package
и операторыimport
работают так же, как код Java - Использование
object
является Kotlin способ создания Singleton. Обычно вам не нужны объектыTaskContract
, поэтому мыTaskContract
его в Singleton и создаем один его экземпляр. -
val CONTENT_AUTHORITY = "com.aziflaj.todo"
является константой, как иfinal
переменная. В Kotlin константы создаются с использованием ключевого словаval
а переменные — с помощью ключевого словаvar
. Вы не видите тип константы, потому что Котлин достаточноумен
, чтобы понять это. -
val CONTENT_URI: Uri = ...
являетсяfinal Uri
объектомfinal Uri
. В Kotlin вы указываете тип данных после имени переменной. -
"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, и получите название и описание задачи
- Обновите интерфейс с запрошенными значениями.
- Добавьте функциональность к кнопке удаления в меню
Завершение
И вот как вы создаете приложение для Android в Kotlin. Хотя Kotlin еще не стабилен, у него есть функции, которые, я думаю, понравятся разработчикам Android.
Kotin не называется Swift для Android
без причины, он ускоряет и упрощает процесс разработки, действительно делая его « быстрым ». В заключение, каковы преимущества и недостатки?
Преимущества Котлина
- Быстрее развивать
- Проще поддерживать
- Нет
NullPointerException
и защитного программирования
Недостатки Котлина
- Большой размер APK (5,2 МБ для приложения, которое мы разработали)
- Более медленный процесс компиляции
Я чувствую, что эти недостатки могут не существовать долго после выпуска стабильной версии. Для получения дополнительной информации о Kotlin прочитайте справочник по языку .
Что вы думаете о Kotlin? Вы бы использовали это?