Поговорим про delegated properties и все, что с ними связано. Узнаем, зачем они нужны, какие delegated properties предоставляет стандартная библиотека, напишем несколько своих и рассмотрим более сложные примеры, которые могут быть полезны в мире android-разработки. Мы также посмотрим, во что delegated properties превращаются во время компиляции, и какие сюрпризы нас могут ожидать.
5. Делегирование
● Очень часто используемый дизайн паттерн
● Kotlin поддерживает делегирование на уровне языка
● Два вида делегирования в Kotlin
6. Делегирование
● Очень часто используемый дизайн паттерн
● Kotlin поддерживает делегирование на уровне языка
● Два вида делегирования в Kotlin
● Interface delegation
7. Делегирование
● Очень часто используемый дизайн паттерн
● Kotlin поддерживает делегирование на уровне языка
● Два вида делегирования в Kotlin
● Interface delegation
● Property delegation
11. Properties
● Понятие field отсутсвует на уровне языка
● Есть только property
● Пара setter + getter для var и getter для val
12. Properties
● Понятие field отсутсвует на уровне языка
● Есть только property
● Пара setter + getter для var и getter для val
● Все обращения происходят через getter и setter
13. Properties
● Понятие field отсутсвует на уровне языка
● Есть только property
● Пара setter + getter для var и getter для val
● Все обращения происходят через getter и setter
● При необходимости у property есть backing field
21. Delegates.notNull()
class ChatFragment : Fragment() {
override fun onAttach(activity: Activity) {
super.onAttach(activity)
adapter = createMessagesAdapter(context)
}
}
22. Delegates.notNull()
class ChatFragment : Fragment() {
private var adapter: MessagesAdapter? = null
override fun onAttach(activity: Activity) {
super.onAttach(activity)
adapter = createMessagesAdapter(context)
}
}
23. Delegates.notNull()
class ChatFragment : Fragment() {
private var adapter: MessagesAdapter? = null
override fun onAttach(activity: Activity) {
super.onAttach(activity)
adapter = createMessagesAdapter(context)
}
private fun onMessagesChanged(messages: List<Message>) {
adapter!!.notifyMessagedChanged(messages)
}
}
24. Delegates.notNull()
class ChatFragment : Fragment() {
private var adapter: MessagesAdapter by Delegates.notNull()
override fun onAttach(activity: Activity) {
super.onAttach(activity)
adapter = createMessagesAdapter(context)
}
private fun onMessagesChanged(messages: List<Message>) {
adapter!!.notifyMessagedChanged(messages)
}
}
25. Delegates.notNull()
class ChatFragment : Fragment() {
private var adapter: MessagesAdapter by Delegates.notNull()
override fun onAttach(activity: Activity) {
super.onAttach(activity)
adapter = createMessagesAdapter(context)
}
private fun onMessagesChanged(messages: List<Message>) {
adapter.notifyMessagedChanged(messages)
}
}
26. lazy
class ChatFragment : Fragment() {
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
createMessagesAdapter(context)
}
private fun onMessagesChanged(messages: List<Message>) {
adapter.notifyMessagedChanged(messages)
}
}
27. Delegates.observable
class ChatFragment : Fragment() {
private var title by Delegates.observable("Chat") { desc, old, new ->
notifyTitleChanged(new)
}
private fun notifyTitleChanged(title: String) {
titleView.text = title
}
}
28. Наш первый делегат
public class OvalView extends View {
private float radiusX = 100.0f;
private float radiusY = 100.0f;
}
29. Наш первый делегат
public class OvalView extends View {
private float radiusX = 100.0f;
private float radiusY = 100.0f;
public void setRadiusX(float radiusX) {
this.radiusX = radiusX;
invalidate();
}
}
30. Наш первый делегат
public class OvalView extends View {
private float radiusX = 100.0f;
private float radiusY = 100.0f;
public void setRadiusX(float radiusX) {
this.radiusX = radiusX;
invalidate();
}
public void setRadiusY(float radiusY) {
this.radiusY = radiusY;
invalidate();
}
}
31. Наш первый делегат
class OvalView : View() {
val radiusX by Delegates.observable(100.0f) { desc, old, new ->
invalidate()
}
val radiusY by Delegates.observable(100.0f) { desc, old, new ->
invalidate()
}
}
32. Наш первый делегат
fun <T> View.bindViewProperty(initial: T): ReadWriteProperty<Any, T> {
return Delegates.observable(initial) { desc, old, new ->
invalidate()
}
}
33. Наш первый делегат
fun <T> View.bindViewProperty(initial: T): ReadWriteProperty<Any, T> {
return Delegates.observable(initial) { desc, old, new ->
invalidate()
}
}
class OvalView : View() {
val radiusX by bindViewProperty(100.0f)
val radiusY by bindViewProperty(100.0f)
}
34. Наш первый делегат
fun <T> View.bindViewProperty(initial: T): ReadWriteProperty<Any, T> {
return Delegates.observable(initial) { desc, old, new ->
invalidate()
}
}
class OvalView : View() {
val radiusX by bindViewProperty(100.0f)
val radiusY by bindViewProperty(100.0f)
val centerX by bindViewProperty(50.0f)
val centerY by bindViewProperty(50.0f)
}
35. Еще один простой делегат
fun <V : View> Activity.bindView(id: Int): Lazy<V> {
throw UnsupportedOperationException("Implement me!")
}
36. Еще один простой делегат
fun <V : View> Activity.bindView(id: Int): Lazy<V> {
return lazy(LazyThreadSafetyMode.NONE) {
findViewById(id) as V
}
}
37. Еще один простой делегат
fun <V : View> Activity.bindView(id: Int): Lazy<V> {
return lazy(LazyThreadSafetyMode.NONE) {
findViewById(id) as V
}
}
class UserActivity : Activity() {
private val image by bindView<ImageView>(R.id.user_image)
private val firstName by bindView<TextView>(R.id.user_first_name)
private val lastName by bindView<TextView>(R.id.user_last_name)
}
39. Typesafe Bundle Builders
val fragment = UserFragment()
val extras = Bundle()
extras.putString("firstName", "Ivan")
extras.putString("lastName", "Ivanov")
extras.putInt("age", 20)
fragment.arguments = extras
40. Typesafe Bundle Builders
val fragment = UserFragment()
val extras = Bundle()
extras.putString("firstName", "Ivan")
extras.putString("lastName", "Ivanov")
extras.putInt("age", 20)
fragment.arguments = extras
inline fun <reified T : Any> bindArgument(bundle: Bundle, default: T? = null): BundleDelegate {
throw UnsupportedOperationException()
}
41. Typesafe Bundle Builders
class UserArguments(val extras: Bundle) {
var firstName by bindArgument<String>(extras)
var lastName by bindArgument<String>(extras)
var age by bindArgument(extras, 33)
}
42. Typesafe Bundle Builders
class UserArguments(val extras: Bundle) {
var firstName by bindArgument<String>(extras)
var lastName by bindArgument<String>(extras)
var age by bindArgument(extras, 33)
}
val fragment = UserFragment()
val arguments = UserArguments(Bundle())
arguments.firstName = "Ivan"
arguments.lastName = "Ivanov"
fragment.arguments = arguments.extras
43. Typesafe Bundle Builders
class UserFragment : Fragment() {
private val args by lazy(LazyThreadSafetyMode.NONE) {
UserArguments(arguments)
}
}
44. LifecycleAware delegates
class ChatFragment : BaseFragment() {
private var chat by Delegates.notNull<ChatModel>()
private var messages by Delegates.notNull<CollectionRange<Message>>()
}
45. LifecycleAware delegates
class ChatFragment : BaseFragment() {
private var chat by Delegates.notNull<ChatModel>()
private var messages by Delegates.notNull<CollectionRange<Message>>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
chat = acquireChatModel()
messages = chat.acquireMessagesRange()
}
}
46. LifecycleAware delegates
class ChatFragment : BaseFragment() {
private var chat by Delegates.notNull<ChatModel>()
private var messages by Delegates.notNull<CollectionRange<Message>>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
chat = acquireChatModel()
messages = chat.acquireMessagesRange()
}
override fun onDestroy() {
messages.close()
chat.close()
super.onDestroy()
}
}
47. LifecycleAware delegates
class ChatFragment : BaseFragment() {
private val chat by bindToLifecycleEagerly(LifecycleInterval.CREATED) {
acquireChatModel()
}
private val participants by bindToLifecycleEagerly(LifecycleInterval.CREATED) {
chat.acquireParticipantsRange()
}
}
48. Preferences delegates
class DebugSettings(private val preferences: SharedPreferences) {
var logEnabled: Boolean
set(value) = preferences.edit().putBoolean("logEnabled", value).apply()
get() = preferences.getBoolean("logEnabled", true)
var logTag: String
set(value) = preferences.edit().putString("logTag", value).apply()
get() = preferences.getString("logTag", "Debug")
}
51. ReadWriteProperty и ReadOnlyProperty
public interface ReadWriteProperty<in R, T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
public interface ReadOnlyProperty<in R, out T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
}
55. KProperty<T>
● Хранит в себе метаинформацию о property
● KProperty<T>.name: String
● KProperty<T>.returnType.javaType: Class
56. KProperty<T>
● Хранит в себе метаинформацию о property
● KProperty<T>.name: String
● KProperty<T>.returnType.javaType: Class
● KProperty<T>.returnType.isMarkedNullable: Boolean
57. KProperty<T>
● Хранит в себе метаинформацию о property
● KProperty<T>.name: String
● KProperty<T>.returnType.javaType: Class
● KProperty<T>.returnType.isMarkedNullable: Boolean
● KProperty<T>.annotations: List<Annotation>
60. KProperty<T> и Android
● По дефолту работает только KProperty.name
● Оставшимся методам необходима зависимость kotlin-reflect
61. KProperty<T> и Android
● По дефолту работает только KProperty.name
● Оставшимся методам необходима зависимость kotlin-reflect
● Количество методов - 12112
62. KProperty<T> и Android
● По дефолту работает только KProperty.name
● Оставшимся методам необходима зависимость kotlin-reflect
● Количество методов - 12112
● JAR Size - 2268KB
63. KProperty<T> и Android
● По дефолту работает только KProperty.name
● Оставшимся методам необходима зависимость kotlin-reflect
● Количество методов - 12112
● JAR Size - 2268KB
● DEX size - 1726KB
65. Как жить без reflection
● KProperty.name работает и так
66. Как жить без reflection
● KProperty.name работает и так
● KProperty.returnType.javaType - inline reified generic функция
67. Как жить без reflection
● KProperty.name работает и так
● KProperty.returnType.javaType - inline reified generic функция
● KProperty.returnType.isMarkedNullable - два разных делегата
68. Как жить без reflection
● KProperty.name работает и так
● KProperty.returnType.javaType - inline reified generic функция
● KProperty.returnType.isMarkedNullable - два разных делегата
inline fun <reified T : Any> bindValue(): ReadWriteProperty<Any, T> {
return createRequiredDelegate(T::class.java)
}
69. Как жить без reflection
● KProperty.name работает и так
● KProperty.returnType.javaType - inline reified generic функция
● KProperty.returnType.isMarkedNullable - два разных делегата
inline fun <reified T : Any> bindValue(): ReadWriteProperty<Any, T> {
return createRequiredDelegate(T::class.java)
}
inline fun <reified T : Any> bindOptionalValue(): ReadWriteProperty<Any, T?> {
return createOptionalDelegate(T::class.java)
}
78. Preferences delegates
override fun getValue(thisRef: Any, property: KProperty<*>): T {
val key = name ?: property.name
if (!preferences.contains(key)) {
return default
}
}
79. Preferences delegates
override fun getValue(thisRef: Any, property: KProperty<*>): T {
val key = name ?: property.name
if (!preferences.contains(key)) {
return default
}
return when (clazz) {
String::class.java -> preferences.getString(key, null)
else -> throw UnsupportedOperationException()
} as T
}
80. Preferences delegates
override fun getValue(thisRef: Any, property: KProperty<*>): T {
val key = name ?: property.name
if (!preferences.contains(key)) {
return default
}
return when (clazz) {
String::class.java -> preferences.getString(key, null)
Int::class.java -> preferences.getInt(key, 0)
else -> throw UnsupportedOperationException()
} as T
}
81. Preferences delegates
override fun getValue(thisRef: Any, property: KProperty<*>): T {
val key = name ?: property.name
if (!preferences.contains(key)) {
return default
}
return when (clazz) {
String::class.java -> preferences.getString(key, null)
Int::class.java -> preferences.getInt(key, 0)
Long::class.java -> preferences.getLong(key, 0L)
else -> throw UnsupportedOperationException()
} as T
}
82. Preferences delegates
override fun getValue(thisRef: Any, property: KProperty<*>): T {
val key = name ?: property.name
if (!preferences.contains(key)) {
return default
}
return when (clazz) {
String::class.java -> preferences.getString(key, null)
Int::class.java -> preferences.getInt(key, 0)
Long::class.java -> preferences.getLong(key, 0L)
Boolean::class.java -> preferences.getBoolean(key, false)
else -> throw UnsupportedOperationException()
} as T
}
83. Preferences delegates
override fun getValue(thisRef: Any, property: KProperty<*>): T {
val key = name ?: property.name
if (!preferences.contains(key)) {
return default
}
return when (clazz) {
String::class.java -> preferences.getString(key, null)
Int::class.java -> preferences.getInt(key, 0)
Long::class.java -> preferences.getLong(key, 0L)
Boolean::class.java -> preferences.getBoolean(key, false)
Float::class.java -> preferences.getFloat(key, 0.0f)
else -> throw UnsupportedOperationException()
} as T
}