6. Anko
• Android + Kotlin = Anko
• Set of methods/extensions/helpers to make Android development
easier
7. Anko
• Android + Kotlin = Anko
• Set of methods/extensions/helpers to make Android development
easier
• Library consists of multiple parts
8. Anko
• Android + Kotlin = Anko
• Set of methods/extensions/helpers to make Android development
easier
• Library consists of multiple parts
• Commons - general helpers for intents, dialogs, etc.
9. Anko
• Android + Kotlin = Anko
• Set of methods/extensions/helpers to make Android development
easier
• Library consists of multiple parts
• Commons - general helpers for intents, dialogs, etc.
• Sqlite - DSL for creating Sqlite Queries and helpers for parsing
results
10. Anko
• Android + Kotlin = Anko
• Set of methods/extensions/helpers to make Android development
easier
• Library consists of multiple parts
• Commons - general helpers for intents, dialogs, etc.
• Sqlite - DSL for creating Sqlite Queries and helpers for parsing
results
• Coroutines - utilities based on Kotlin Coroutines
11. Anko
• Android + Kotlin = Anko
• Set of methods/extensions/helpers to make Android development
easier
• Library consists of multiple parts
• Commons - general helpers for intents, dialogs, etc.
• Sqlite - DSL for creating Sqlite Queries and helpers for parsing
results
• Coroutines - utilities based on Kotlin Coroutines
• Layouts - typesafe way to create layouts
12. Anko - Layouts
• Anko aims to replace XML for building UI of an app
13. Anko - Layouts
• Anko aims to replace XML for building UI of an app
• XML has several disadvantages
14. Anko - Layouts
• Anko aims to replace XML for building UI of an app
• XML has several disadvantages
• Its not typesafe and nullsafe
15. Anko - Layouts
• Anko aims to replace XML for building UI of an app
• XML has several disadvantages
• Its not typesafe and nullsafe
• Forces you to write almost the same code for every layout
you make and it allows almost no code reuse
16. Anko - Layouts
• Anko aims to replace XML for building UI of an app
• XML has several disadvantages
• Its not typesafe and nullsafe
• Forces you to write almost the same code for every layout
you make and it allows almost no code reuse
• Waste of computational resources because of parsing of
XML file in runtime
18. Performance
• There is couple of articles about performance comparison
between Anko and XML, all in favor of Anko
19. Performance
• There is couple of articles about performance comparison
between Anko and XML, all in favor of Anko
• One of them describes 400% speed improvement when
inflating layout using Anko instead of with LayoutInflater
20. Performance
• There is couple of articles about performance comparison
between Anko and XML, all in favor of Anko
• One of them describes 400% speed improvement when
inflating layout using Anko instead of with LayoutInflater
• I was curious if the results are really that good or if author is
just a big fan of Anko
22. Performance
• I’ve created a simple RecyclerView.Adapter with different
viewItemType for each row and I measured execution time of
onCreateViewHolder method
23. Performance
• I’ve created a simple RecyclerView.Adapter with different
viewItemType for each row and I measured execution time of
onCreateViewHolder method
• I’ve measured 1000 creations and marked down min, max and average
values for couple of different devices
24. Performance
• I’ve created a simple RecyclerView.Adapter with different
viewItemType for each row and I measured execution time of
onCreateViewHolder method
• I’ve measured 1000 creations and marked down min, max and average
values for couple of different devices
Measurement results
Emulator N5X
An 7.1,
SGS8
An 7.0
Nexus 5X
An 8.0
GSmrad
(GSmart Roma R2)
An 4.3
Huawei
Y330
An 4.2.2
MiniSráč
(SG Young S6310)
An 4.4.4
Anko 0/57/4.3 0/54/4.7 0/32/7.4 0/226/15.2 0/558/16.7 0/206/19.8
XML 0/122/7.4 0/327/8.3 0/31/11.2 0/332/24.9 0/494/22.6 0/154/37.9
33. How does it work?
• Because of Anko is Kotlin Library it can benefit from the easy
creation of TypeSafe builders
34. How does it work?
• Because of Anko is Kotlin Library it can benefit from the easy
creation of TypeSafe builders
• Lets create a very small and stupid subset of Anko on our own
35. How does it work?
• Because of Anko is Kotlin Library it can benefit from the easy
creation of TypeSafe builders
• Lets create a very small and stupid subset of Anko on our own
verticalLayout {
textView {
text = "Hello"
}}
textView(R.string.app_name) {
textSize = 26f
}}
}}
37. DSL
• We need 2 functions, verticalLayout and textView
verticalLayout {
textView {
text = "Hello"
}}
textView(R.string.app_name) {
textSize = 26f
}}
}}
38. DSL
• We need 2 functions, verticalLayout and textView
fun Activity.verticalLayout( ): LinearLayout {
val layout = LinearLayout(this)
with(layout) {
orientation = LinearLayout.VERTICAL
init()
}
return layout
}
init: LinearLayout.() -> Unit
verticalLayout {
textView {
text = "Hello"
}}
textView(R.string.app_name) {
textSize = 26f
}}
}}
39. DSL
• We need 2 functions, verticalLayout and textView
fun Activity.verticalLayout( ): LinearLayout {
val layout = LinearLayout(this)
with(layout) {
orientation = LinearLayout.VERTICAL
init()
}
return layout
}
init: LinearLayout.() -> Unit
verticalLayout {
textView {
text = "Hello"
}}
textView(R.string.app_name) {
textSize = 26f
}}
}}
40. DSL
• We need 2 functions, verticalLayout and textView
fun Activity.verticalLayout( ): LinearLayout {
val layout = LinearLayout(this)
with(layout) {
orientation = LinearLayout.VERTICAL
init()
}
return layout
}
init: LinearLayout.() -> Unit
verticalLayout {
textView {
text = "Hello"
}}
textView(R.string.app_name) {
textSize = 26f
}}
}}
41. DSL
• We need 2 functions, verticalLayout and textView
fun Activity.verticalLayout( ): LinearLayout {
val layout = LinearLayout(this)
with(layout) {
orientation = LinearLayout.VERTICAL
init()
}
return layout
}
fun ViewGroup.textView(init: TextView.() -> Unit): TextView {
val textView = TextView(context)
textView.init()
addView(textView)
return textView
}
init: LinearLayout.() -> Unit
verticalLayout {
textView {
text = "Hello"
}}
textView(R.string.app_name) {
textSize = 26f
}}
}}
42. DSL
• init has a type that is called function type with receiver T.() -> Unit
43. DSL
• init has a type that is called function type with receiver T.() -> Unit
• we need to pass an instance of T (receiver) to the function and we can
access members of that instnace
44. DSL
• init has a type that is called function type with receiver T.() -> Unit
• we need to pass an instance of T (receiver) to the function and we can
access members of that instnace
• The same restrictions applies as for Extensions functions - only public
members and methods can be accessed
45. DSL
• There is a little annoying “bug” that was also in Anko
46. DSL
• There is a little annoying “bug” that was also in Anko
• Nothing stops us from doing this
textView {
text = "Hello"
textView {
text = "World"
}
}
47. DSL
• There is a little annoying “bug” that was also in Anko
• Nothing stops us from doing this
• We can generate syntactically incorrect results
textView {
text = "Hello"
textView {
text = "World"
}
}
48. DSL
• There is a little annoying “bug” that was also in Anko
• Nothing stops us from doing this
• We can generate syntactically incorrect results
• It’s possible because we are still inside the context of the outer
verticalLayout
textView {
text = "Hello"
textView {
text = "World"
}
}
49. DSL
• Kotlin has a solution from v1.1
• We need to create annotation with which we mark our init
methods
LinearLayout.() -> Unit): LinearLayout {LinearLayout).() -> Unit): LinearLayout {(@UIDSLMaker
50. DSL
• Kotlin has a solution from v1.1
• We need to create annotation with which we mark our init
methods
@DslMarker
@Target(AnnotationTarget.TYPE)
annotation class UIDSLMaker
LinearLayout.() -> Unit): LinearLayout {LinearLayout).() -> Unit): LinearLayout {(@UIDSLMaker
51. DSL
• Kotlin has a solution from v1.1
• We need to create annotation with which we mark our init
methods
@DslMarker
@Target(AnnotationTarget.TYPE)
annotation class UIDSLMaker
fun Activity.verticalLayout(init:
val layout = LinearLayout(this)
with(layout) {
orientation = LinearLayout.VERTICAL
init()
}
return layout
}
LinearLayout.() -> Unit): LinearLayout {LinearLayout).() -> Unit): LinearLayout {(@UIDSLMaker
52. DSL
• Kotlin has a solution from v1.1
• We need to create annotation with which we mark our init
methods
@DslMarker
@Target(AnnotationTarget.TYPE)
annotation class UIDSLMaker
fun Activity.verticalLayout(init:
val layout = LinearLayout(this)
with(layout) {
orientation = LinearLayout.VERTICAL
init()
}
return layout
}
LinearLayout.() -> Unit): LinearLayout {LinearLayout).() -> Unit): LinearLayout {(@UIDSLMaker
53. DSL
• Kotlin has a solution from v1.1
• We need to create annotation with which we mark our init
methods
@DslMarker
@Target(AnnotationTarget.TYPE)
annotation class UIDSLMaker
fun Activity.verticalLayout(init:
val layout = LinearLayout(this)
with(layout) {
orientation = LinearLayout.VERTICAL
init()
}
return layout
}
LinearLayout.() -> Unit): LinearLayout {LinearLayout).() -> Unit): LinearLayout {(@UIDSLMaker
54. How does it work #2?
• DSL explains why the layout looks structurized but there is
more in Anko (or Kotlin) that makes building layouts easier
• Lets take a look at our first Anko sample
55. How does it work #2?
• DSL explains why the layout looks structurized but there is
more in Anko (or Kotlin) that makes building layouts easier
• Lets take a look at our first Anko sample
textView {
padding = dip(16)
text = "Awesome title"
textColor = color(android.R.color.white)
textSize = 26f
}.lparams(gravity = Gravity.BOTTOM)
56. How does it work #2?
textView {
padding = dip(16)
text = "Awesome title"
textColor = color(android.R.color.white)
textSize = 26f
}.lparams(gravity = Gravity.BOTTOM)
57. How does it work #2?
• text and textSize are synthetic extension properties bound to the getters and setters
from Java. Both of these must exists for compiler to create these properties
textView {
padding = dip(16)
text = "Awesome title"
textColor = color(android.R.color.white)
textSize = 26f
}.lparams(gravity = Gravity.BOTTOM)
58. How does it work #2?
• text and textSize are synthetic extension properties bound to the getters and setters
from Java. Both of these must exists for compiler to create these properties
• padding and textColor are extension properties built in Anko. They exists for the most
used properties in views that don’t provide getters/setters for given property
textView {
padding = dip(16)
text = "Awesome title"
textColor = color(android.R.color.white)
textSize = 26f
}.lparams(gravity = Gravity.BOTTOM)
59. How does it work #2?
• text and textSize are synthetic extension properties bound to the getters and setters
from Java. Both of these must exists for compiler to create these properties
• padding and textColor are extension properties built in Anko. They exists for the most
used properties in views that don’t provide getters/setters for given property
• dip(Int) is an extension property from Anko. Converts integer argument from dps to pxs
textView {
padding = dip(16)
text = "Awesome title"
textColor = color(android.R.color.white)
textSize = 26f
}.lparams(gravity = Gravity.BOTTOM)
60. How does it work #2?
• text and textSize are synthetic extension properties bound to the getters and setters
from Java. Both of these must exists for compiler to create these properties
• padding and textColor are extension properties built in Anko. They exists for the most
used properties in views that don’t provide getters/setters for given property
• dip(Int) is an extension property from Anko. Converts integer argument from dps to pxs
• color(Int) is our extension function for retrieving color resource
@ColorInt
fun View.color(@ColorRes colorRes: Int) = ContextCompat.getColor(context, colorRes)
textView {
padding = dip(16)
text = "Awesome title"
textColor = color(android.R.color.white)
textSize = 26f
}.lparams(gravity = Gravity.BOTTOM)
61. How does it work #3?
verticalLayout {
frameLayout { //rest of the views
}.lparams(width = matchParent, height = dip(180))
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent)
}
62. How does it work #3?
• horizontalPadding and verticalPadding are also extension properties, but
for attributes that don’t exist in View definition.
verticalLayout {
frameLayout { //rest of the views
}.lparams(width = matchParent, height = dip(180))
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent)
}
63. How does it work #3?
• horizontalPadding and verticalPadding are also extension properties, but
for attributes that don’t exist in View definition.
• verticalLayout (as in our own DSL) is a LinearLayout with orientation set to
VERTICAL
verticalLayout {
frameLayout { //rest of the views
}.lparams(width = matchParent, height = dip(180))
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent)
}
64. How does it work #3?
• horizontalPadding and verticalPadding are also extension properties, but
for attributes that don’t exist in View definition.
• verticalLayout (as in our own DSL) is a LinearLayout with orientation set to
VERTICAL
• textView(Int) is a helper block constructor that takes string resource as a
parameter. These helper blocks exist for some of the views like editText,
textView, imageView, …
verticalLayout {
frameLayout { //rest of the views
}.lparams(width = matchParent, height = dip(180))
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent)
}
65. How does it work #4?
verticalLayout {
frameLayout { //rest of the views
textView {
}.lparams(gravity = Gravity.BOTTOM)
}.lparams(width = matchParent, height = dip(180))
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
66. How does it work #4?
• lparams is a function for definition of Layout Parameters in the context of current ViewGroup
verticalLayout {
frameLayout { //rest of the views
textView {
}.lparams(gravity = Gravity.BOTTOM)
}.lparams(width = matchParent, height = dip(180))
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
67. How does it work #4?
• lparams is a function for definition of Layout Parameters in the context of current ViewGroup
• It should be defined after the View definition
verticalLayout {
frameLayout { //rest of the views
textView {
}.lparams(gravity = Gravity.BOTTOM)
}.lparams(width = matchParent, height = dip(180))
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
68. How does it work #4?
• lparams is a function for definition of Layout Parameters in the context of current ViewGroup
• It should be defined after the View definition
• All lparams accepts width and height parameters. Default value is wrapContent
(extension property of WRAP_CONTENT, same for matchParent)
verticalLayout {
frameLayout { //rest of the views
textView {
}.lparams(gravity = Gravity.BOTTOM)
}.lparams(width = matchParent, height = dip(180))
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
69. How does it work #4?
• lparams is a function for definition of Layout Parameters in the context of current ViewGroup
• It should be defined after the View definition
• All lparams accepts width and height parameters. Default value is wrapContent
(extension property of WRAP_CONTENT, same for matchParent)
• Some lparams have overloads, eg. LinearLayout.lparams accepts weight: Float or
FrameLayout.lparams accepts gravity: Int
verticalLayout {
frameLayout { //rest of the views
textView {
}.lparams(gravity = Gravity.BOTTOM)
}.lparams(width = matchParent, height = dip(180))
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
70. How does it work #4?
• lparams is a function for definition of Layout Parameters in the context of current ViewGroup
• It should be defined after the View definition
• All lparams accepts width and height parameters. Default value is wrapContent
(extension property of WRAP_CONTENT, same for matchParent)
• Some lparams have overloads, eg. LinearLayout.lparams accepts weight: Float or
FrameLayout.lparams accepts gravity: Int
• lparams also accepts lambda with parameters definition just as functions for View definitions
verticalLayout {
frameLayout { //rest of the views
textView {
}.lparams(gravity = Gravity.BOTTOM)
}.lparams(width = matchParent, height = dip(180))
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
71. Layout Parameters
• In some tutorials you can see lparams block inside of view definition
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
72. Layout Parameters
• In some tutorials you can see lparams block inside of view definition
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
73. Layout Parameters
• In some tutorials you can see lparams block inside of view definition
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
• Sometimes it works, but sometimes it doesn’t and you don’t know why
🤔. What happens in this case?
verticalLayout {
lparams { }
frameLayout {
lparams { }
}
}
74. Layout Parameters
• In some tutorials you can see lparams block inside of view definition
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
• Sometimes it works, but sometimes it doesn’t and you don’t know why
🤔. What happens in this case?
verticalLayout {
lparams { }
frameLayout {
lparams { }
}
}
// lparams is LinearLayout.LayoutParams
75. Layout Parameters
• In some tutorials you can see lparams block inside of view definition
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
• Sometimes it works, but sometimes it doesn’t and you don’t know why
🤔. What happens in this case?
verticalLayout {
lparams { }
frameLayout {
lparams { }
}
}
// lparams is LinearLayout.LayoutParams
// lparams is FrameLayout.LayoutParams => INCORRECT, it should be LinearLayout.LayoutParams
76. Layout Parameters
• In some tutorials you can see lparams block inside of view definition
textView(R.string.lorem_ipsum) {
horizontalPadding = dip(16)
verticalPadding = dip(12)
}.lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
lparams(matchParent, matchParent) {
bottomMargin = dip(8)
}
}
• Sometimes it works, but sometimes it doesn’t and you don’t know why
🤔. What happens in this case?
verticalLayout {
lparams { }
frameLayout {
lparams { }
}
}
// lparams is LinearLayout.LayoutParams
// lparams is FrameLayout.LayoutParams => INCORRECT, it should be LinearLayout.LayoutParams
• Because of that, you need to define lparams for the most outer
ViewGroup just like without Anko
77. Layout Parameters
• Because of that, you need to define lparams for the most outer
ViewGroup just like without Anko
verticalLayout {
layoutParams = ViewGroup.LayoutParams(matchParent, dip(180))
frameLayout {
}.lparams { }
}
78. Beyond the Anko
• If you find a View that doesn’t have corresponding extension fnc in
Anko or you have your own custom view, you can use customView
extension
79. Beyond the Anko
• If you find a View that doesn’t have corresponding extension fnc in
Anko or you have your own custom view, you can use customView
extension
customView<SquaredFrameLayout> {
backgroundResource = android.R.color.black
}
80. Beyond the Anko
• If you find a View that doesn’t have corresponding extension fnc in
Anko or you have your own custom view, you can use customView
extension
customView<SquaredFrameLayout> {
backgroundResource = android.R.color.black
}
• If you have a view that is used multiple times it may be convenient to
define your own extension function
81. Beyond the Anko
• If you find a View that doesn’t have corresponding extension fnc in
Anko or you have your own custom view, you can use customView
extension
customView<SquaredFrameLayout> {
backgroundResource = android.R.color.black
}
• If you have a view that is used multiple times it may be convenient to
define your own extension function
inline fun ViewManager.squaredFrameLayout(init: SquaredFrameLayout.() -> Unit): SquaredFrameLayout {
return ankoView(::SquaredFrameLayout, theme = 0, init = init)
}
82. Beyond the Anko
• If you find a View that doesn’t have corresponding extension fnc in
Anko or you have your own custom view, you can use customView
extension
customView<SquaredFrameLayout> {
backgroundResource = android.R.color.black
}
• If you have a view that is used multiple times it may be convenient to
define your own extension function
inline fun ViewManager.squaredFrameLayout(init: SquaredFrameLayout.() -> Unit): SquaredFrameLayout {
return ankoView(::SquaredFrameLayout, theme = 0, init = init)
}
squaredFrameLayout {
backgroundResource = android.R.color.black
}
84. Anko Components
• Every View extension is defined for ViewManager interface. That’s
convenient because every ViewGroup implements this interface. A few
of them are also defined for Activities (mostly ViewGroups eg.
linearLayout)
85. Anko Components
• Every View extension is defined for ViewManager interface. That’s
convenient because every ViewGroup implements this interface. A few
of them are also defined for Activities (mostly ViewGroups eg.
linearLayout)
• You can define your UI right inside an Activity or Fragment, but that
leads to loose coupling between your presentation logic and view
definition
86. Anko Components
• Every View extension is defined for ViewManager interface. That’s
convenient because every ViewGroup implements this interface. A few
of them are also defined for Activities (mostly ViewGroups eg.
linearLayout)
• You can define your UI right inside an Activity or Fragment, but that
leads to loose coupling between your presentation logic and view
definition
• Anko has concept of AnkoComponents
88. Anko Components
• AnkoComponent is meant to be a reusable piece of UI
interface AnkoComponent<in T> {
fun createView(ui: AnkoContext<T>): View
}
89. Anko Components
• AnkoComponent is meant to be a reusable piece of UI
interface AnkoComponent<in T> {
fun createView(ui: AnkoContext<T>): View
}
• You must provide AnkoContext which allows you to create views
without Activity or any particular ViewGroup. It implements
ViewManager
90. Anko Components
• AnkoComponent is meant to be a reusable piece of UI
interface AnkoComponent<in T> {
fun createView(ui: AnkoContext<T>): View
}
• You must provide AnkoContext which allows you to create views
without Activity or any particular ViewGroup. It implements
ViewManager
• With AnkoComponent you can see Preview of your layout.
91. Anko Components
• AnkoComponent is meant to be a reusable piece of UI
interface AnkoComponent<in T> {
fun createView(ui: AnkoContext<T>): View
}
• You must provide AnkoContext which allows you to create views
without Activity or any particular ViewGroup. It implements
ViewManager
• With AnkoComponent you can see Preview of your layout.
• Unfortunately not now. Preview has been broken since AS 1.6, then
fixed in 2.4 but now its broken once again :(
95. Mixing with XML
• If you would like to try Anko, but you have part of your layout in XML
and its not the right time to convert it you can use <include> tag
96. Mixing with XML
• If you would like to try Anko, but you have part of your layout in XML
and its not the right time to convert it you can use <include> tag
include<View>(R.layout.something) {
backgroundColor = Color.RED
}.lparams(width = matchParent)
97. Mixing with XML
• If you would like to try Anko, but you have part of your layout in XML
and its not the right time to convert it you can use <include> tag
include<View>(R.layout.something) {
backgroundColor = Color.RED
}.lparams(width = matchParent)
• You can specify the type of the parent View in XML and you can set
properties bound to this type
98. Mixing with XML
• If you would like to try Anko, but you have part of your layout in XML
and its not the right time to convert it you can use <include> tag
include<View>(R.layout.something) {
backgroundColor = Color.RED
}.lparams(width = matchParent)
• You can specify the type of the parent View in XML and you can set
properties bound to this type
include<TextView>(R.layout.item_text) {
textSize = 24f
}.lparams(width = matchParent)
108. Custom view attributes
• The same way how horizontalPadding and verticalPadding are
defined, we can create own extensions that simplify work
• How many times have you wrote variation of this?
109. Custom view attributes
• The same way how horizontalPadding and verticalPadding are
defined, we can create own extensions that simplify work
• How many times have you wrote variation of this?
fun showProgress(show: Boolean) {
progress.visibility = if (show) View.VISIBLE else View.GONE
}
110. Custom view attributes
• The same way how horizontalPadding and verticalPadding are
defined, we can create own extensions that simplify work
• How many times have you wrote variation of this?
fun showProgress(show: Boolean) {
progress.visibility = if (show) View.VISIBLE else View.GONE
}
• Don’t you (sometimes of course) wish that it would look like this?
fun showProgress(show: Boolean) {
progress.visible = show
}}
111. Custom view attributes
fun showProgress(show: Boolean) {
progress.visible = show
}}
var View.visible: Boolean
set(value) {
visibility = if (value) View.VISIBLE else View.GONE
}
get() = visibility == View.VISIBLE
112. Custom extensions
• Recap: Extension for retrieving color from resources
@ColorInt
fun View.color(@ColorRes colorRes: Int) = ContextCompat.getColor(context, colorRes)
113. Custom extensions
• Recap: Extension for retrieving color from resources
@ColorInt
fun View.color(@ColorRes colorRes: Int) = ContextCompat.getColor(context, colorRes)
• We have these methods for all types of resources (drawable, string, dimension),
but we can go further…
114. Custom extensions
• Recap: Extension for retrieving color from resources
@ColorInt
fun View.color(@ColorRes colorRes: Int) = ContextCompat.getColor(context, colorRes)
• We have these methods for all types of resources (drawable, string, dimension),
but we can go further…
• Our designers have a weird fetish: Reuse the same color but with different opacity
115. Custom extensions
• Recap: Extension for retrieving color from resources
@ColorInt
fun View.color(@ColorRes colorRes: Int) = ContextCompat.getColor(context, colorRes)
• We have these methods for all types of resources (drawable, string, dimension),
but we can go further…
• Our designers have a weird fetish: Reuse the same color but with different opacity
<color name="color_primary_40_alpha">#662d3241</color>
<color name="color_primary_50_alpha">#7f2d3241</color>
<color name="color_primary_30_alpha">#4d2d3241</color>
<color name="color_primary_20_alpha">#332d3241</color>
<color name="color_primary_70_alpha">#b32d3241</color>
<color name="color_primary_80_alpha">#cc2d3241</color>
<color name="color_primary_90_alpha">#e52d3241</color>
116. Custom extensions
• It would be really awesome to not redefine the same color, compute the
hex number from percentages etc.
117. Custom extensions
• It would be really awesome to not redefine the same color, compute the
hex number from percentages etc.
@ColorInt
fun View.color(@ColorRes colorRes: Int, alpha: Int = 100): Int {
val color = ContextCompat.getColor(context, colorRes)
if (alpha != 100) {
return Color.argb((alpha / 100f * 255).toInt(),
Color.red(color),
Color.green(color),
Color.blue(color))
} else {
return color
}
}
122. Configurations
• Anko has a function configuration() that takes configurations
attributes (screenSize, orientation, density,…) and lambda and it
execute given lambda only if current configuration matches attributes
123. Configurations
• Anko has a function configuration() that takes configurations
attributes (screenSize, orientation, density,…) and lambda and it
execute given lambda only if current configuration matches attributes
var isLandscape = false
configuration(orientation = Orientation.LANDSCAPE) {
isLandscape = true
}
124. Configurations
• Anko has a function configuration() that takes configurations
attributes (screenSize, orientation, density,…) and lambda and it
execute given lambda only if current configuration matches attributes
var isLandscape = false
configuration(orientation = Orientation.LANDSCAPE) {
isLandscape = true
}
• You can put a few ifs to your layout to change it according to device
orientation
126. linearLayout {
orientation = if (isLandscape) LinearLayout.HORIZONTAL else LinearLayout.VERTICA
if (!isLandscape) {
padding = dip(16)
}
imageView(R.drawable.castle) { }.lparams {
if (isLandscape) {
width = 0
weight = 1f
}
}
verticalLayout {
editText { hint = "Email" }
editText { hint = "Password" }
button("Login")
}.lparams {
if (isLandscape) {
width = 0
weight = 1f
} else {
width = matchParent
}
height = wrapContent
}}
SPA
G
H
ETTI
127. Configurations
• Its better to apply good old OOP principles.
abstract class ScreenLayout : AnkoComponent<ConfigChangeActivity> {
abstract var imageLogo: ImageView
abstract var editEmail: EditText
abstract var editPassword: EditText
abstract var btnLogin: Button
}
128. Configurations
• Its better to apply good old OOP principles.
abstract class ScreenLayout : AnkoComponent<ConfigChangeActivity> {
abstract var imageLogo: ImageView
abstract var editEmail: EditText
abstract var editPassword: EditText
abstract var btnLogin: Button
class LayoutPortrait : ScreenLayout() {
override lateinit var imageLogo: ImageView
override lateinit var editEmail: EditText
override lateinit var editPassword: EditText
override lateinit var btnLogin: Button
override fun createView(ui: AnkoContext<ConfigChangeActivity>): View {
return with(ui) {
verticalLayout {
padding = dip(16)
imageLogo = imageView(R.drawable.castle)
editEmail = editText { hint = "Email" }
editPassword = editText { hint = "Password" }
btnLogin = button("Login")
}
}
}
}
}
129. Configurations
• Its better to apply good old OOP principles.
abstract class ScreenLayout : AnkoComponent<ConfigChangeActivity> {
abstract var imageLogo: ImageView
abstract var editEmail: EditText
abstract var editPassword: EditText
abstract var btnLogin: Button
class LayoutPortrait : ScreenLayout() {
override lateinit var imageLogo: ImageView
override lateinit var editEmail: EditText
override lateinit var editPassword: EditText
override lateinit var btnLogin: Button
override fun createView(ui: AnkoContext<ConfigChangeActivity>): View {
return with(ui) {
verticalLayout {
padding = dip(16)
imageLogo = imageView(R.drawable.castle)
editEmail = editText { hint = "Email" }
editPassword = editText { hint = "Password" }
btnLogin = button("Login")
}
}
}
}
class LayoutLandscape : ScreenLayout() {
override lateinit var imageLogo: ImageView
override lateinit var editEmail: EditText
override lateinit var editPassword: EditText
override lateinit var btnLogin: Button
override fun createView(ui: AnkoContext<ConfigChangeActivity>): View {
return with(ui) {
linearLayout {
imageLogo = imageView(R.drawable.castle).lparams(width = 0, weight = 1f)
verticalLayout {
padding = dip(16)
editEmail = editText { hint = "Email" }
editPassword = editText { hint = "Password" }
btnLogin = button("Login")
}.lparams(width = 0, weight = 1f)
}
}
}}
}
130. Configurations
• Its better to apply good old OOP principles.
abstract class ScreenLayout : AnkoComponent<ConfigChangeActivity> {
abstract var imageLogo: ImageView
abstract var editEmail: EditText
abstract var editPassword: EditText
abstract var btnLogin: Button
class LayoutPortrait : ScreenLayout() {
override lateinit var imageLogo: ImageView
override lateinit var editEmail: EditText
override lateinit var editPassword: EditText
override lateinit var btnLogin: Button
override fun createView(ui: AnkoContext<ConfigChangeActivity>): View {
return with(ui) {
verticalLayout {
padding = dip(16)
imageLogo = imageView(R.drawable.castle)
editEmail = editText { hint = "Email" }
editPassword = editText { hint = "Password" }
btnLogin = button("Login")
}
}
}
}
class LayoutLandscape : ScreenLayout() {
override lateinit var imageLogo: ImageView
override lateinit var editEmail: EditText
override lateinit var editPassword: EditText
override lateinit var btnLogin: Button
override fun createView(ui: AnkoContext<ConfigChangeActivity>): View {
return with(ui) {
linearLayout {
imageLogo = imageView(R.drawable.castle).lparams(width = 0, weight = 1f)
verticalLayout {
padding = dip(16)
editEmail = editText { hint = "Email" }
editPassword = editText { hint = "Password" }
btnLogin = button("Login")
}.lparams(width = 0, weight = 1f)
}
}
}}
companion object {
fun createLayout(context: Context): ScreenLayout {
context.configuration(orientation = Orientation.LANDSCAPE) {
return LayoutLandscape()
}
return LayoutPortrait()
}
}
}
133. LayoutInflater
• Because Anko create views programmatically, LayoutInflater is not
in the game. What does it mean?
• Libraries like Calligraphy doesn't work, because they inject into inflation
process and replace TextViews typeface
134. LayoutInflater
• Because Anko create views programmatically, LayoutInflater is not
in the game. What does it mean?
• Libraries like Calligraphy doesn't work, because they inject into inflation
process and replace TextViews typeface
• But more importantly, AppCompat library doesn't work either
135. AppCompat
• AppCompat has its own LayoutInflater which replaces platform
Views with AppCompat equivalents. eg. if you have <Button/>
definition in your layout, it actually creates AppCompatButton instance
136. AppCompat
• AppCompat has its own LayoutInflater which replaces platform
Views with AppCompat equivalents. eg. if you have <Button/>
definition in your layout, it actually creates AppCompatButton instance
• Its convenient because you don't have to worry about backward
compatibility of tinting/theming and it “just works” on all version of
Androids.
137. AppCompat
• AppCompat has its own LayoutInflater which replaces platform
Views with AppCompat equivalents. eg. if you have <Button/>
definition in your layout, it actually creates AppCompatButton instance
• Its convenient because you don't have to worry about backward
compatibility of tinting/theming and it “just works” on all version of
Androids.
• But what if we don't have LayoutInflater?
139. AppCompat
• Anko has a set of extensions with prefix tinted, eg. tintedButton
• It creates AppCompat version of view for preLollipop versions and
platform version otherwise
140. AppCompat
• Anko has a set of extensions with prefix tinted, eg. tintedButton
• It creates AppCompat version of view for preLollipop versions and
platform version otherwise
• If you want to use AppCompat version everytime, you have to do it the
same as with custom view
146. ConstraintLayout
• How does the code look without any additional support?
• Lets build another simple UI example
147. ConstraintLayout
• How does the code look without any additional support?
• Lets build another simple UI example
148. ConstraintLayout
customView<ConstraintLayout> {
layoutParams = ViewGroup.LayoutParams(matchParent, matchParent)
val constraintSet = ConstraintSet()
constraintSet.clone(this@customView)
constraintSet.connect(image.id, START, PARENT_ID, START, dip(16))
constraintSet.connect(image.id, TOP, PARENT_ID, TOP, dip(16))
constraintSet.connect(title.id, START, image.id, END, dip(16))
constraintSet.connect(subtitle.id, START, title.id, START)
constraintSet.createVerticalChain(image.id, TOP, image.id, BOTTOM
intArrayOf(title.id, subtitle.id), null, CHAIN_SPREAD)
constraintSet.applyTo(this@customView)
val image = imageView(R.mipmap.ic_launcher) {
id = 1
layoutParams = ConstraintLayout.LayoutParams(dip(68), dip(68))
}
val title = textView("Title") { id = 2 }
val subtitle = textView("Subtitle") { id = 3 }
}
149. ConstraintLayout
customView<ConstraintLayout> {
layoutParams = ViewGroup.LayoutParams(matchParent, matchParent)
val constraintSet = ConstraintSet()
constraintSet.clone(this@customView)
constraintSet.connect(image.id, START, PARENT_ID, START, dip(16))
constraintSet.connect(image.id, TOP, PARENT_ID, TOP, dip(16))
constraintSet.connect(title.id, START, image.id, END, dip(16))
constraintSet.connect(subtitle.id, START, title.id, START)
constraintSet.createVerticalChain(image.id, TOP, image.id, BOTTOM
intArrayOf(title.id, subtitle.id), null, CHAIN_SPREAD)
constraintSet.applyTo(this@customView)
val image = imageView(R.mipmap.ic_launcher) {
id = 1
layoutParams = ConstraintLayout.LayoutParams(dip(68), dip(68))
}
val title = textView("Title") { id = 2 }
val subtitle = textView("Subtitle") { id = 3 }
}
150. ConstraintLayout
customView<ConstraintLayout> {
layoutParams = ViewGroup.LayoutParams(matchParent, matchParent)
val constraintSet = ConstraintSet()
constraintSet.clone(this@customView)
constraintSet.connect(image.id, START, PARENT_ID, START, dip(16))
constraintSet.connect(image.id, TOP, PARENT_ID, TOP, dip(16))
constraintSet.connect(title.id, START, image.id, END, dip(16))
constraintSet.connect(subtitle.id, START, title.id, START)
constraintSet.createVerticalChain(image.id, TOP, image.id, BOTTOM
intArrayOf(title.id, subtitle.id), null, CHAIN_SPREAD)
constraintSet.applyTo(this@customView)
val image = imageView(R.mipmap.ic_launcher) {
id = 1
layoutParams = ConstraintLayout.LayoutParams(dip(68), dip(68))
}
val title = textView("Title") { id = 2 }
val subtitle = textView("Subtitle") { id = 3 }
}
151. ConstraintLayout
• Fortunately, our colleague David (other than me) likes ConstraintLayout
so much that he developed library that is compatible with Anko
152. ConstraintLayout
constraintLayout {
layoutParams = ViewGroup.LayoutParams(matchParent, matchParent)
val image = imageView(R.mipmap.ic_launcher).lparams(dip(68), dip(68))
val title = textView("Title")
val subtitle = textView("Subtitle")
}
constraints {
image.connect(START to START of parentId with dip(16),
TOP to TOP of parentId with dip(16))
title.connect(START to END of image with dip(16))
subtitle.connect(LEFT to LEFT of title)
chain(TOP of image, BOTTOM of image) {
views(title, subtitle)
}
}
constraints {
image.connect(STARS of parentId with dip(16),
TOPS of parentId with dip(16))
title.connect(START to END of image with dip(16))
subtitle.connect(LEFTS of title)
chain(TOP of image, BOTTOM of image) {
views(title, subtitle)
}
}
153. ConstraintLayout
constraintLayout {
layoutParams = ViewGroup.LayoutParams(matchParent, matchParent)
val image = imageView(R.mipmap.ic_launcher).lparams(dip(68), dip(68))
val title = textView("Title")
val subtitle = textView("Subtitle")
}
constraints {
image.connect(START to START of parentId with dip(16),
TOP to TOP of parentId with dip(16))
title.connect(START to END of image with dip(16))
subtitle.connect(LEFT to LEFT of title)
chain(TOP of image, BOTTOM of image) {
views(title, subtitle)
}
}
constraints {
image.connect(STARS of parentId with dip(16),
TOPS of parentId with dip(16))
title.connect(START to END of image with dip(16))
subtitle.connect(LEFTS of title)
chain(TOP of image, BOTTOM of image) {
views(title, subtitle)
}
}
154. ConstraintLayout
constraintLayout {
layoutParams = ViewGroup.LayoutParams(matchParent, matchParent)
val image = imageView(R.mipmap.ic_launcher).lparams(dip(68), dip(68))
val title = textView("Title")
val subtitle = textView("Subtitle")
}
constraints {
image.connect(START to START of parentId with dip(16),
TOP to TOP of parentId with dip(16))
title.connect(START to END of image with dip(16))
subtitle.connect(LEFT to LEFT of title)
chain(TOP of image, BOTTOM of image) {
views(title, subtitle)
}
}
constraints {
image.connect(STARS of parentId with dip(16),
TOPS of parentId with dip(16))
title.connect(START to END of image with dip(16))
subtitle.connect(LEFTS of title)
chain(TOP of image, BOTTOM of image) {
views(title, subtitle)
}
}
156. AnkoComponent
• We don't use AnkoComponents, because they have no practical use for
us
• We've developed our own definition called ViewLayout
157. AnkoComponent
• We don't use AnkoComponents, because they have no practical use for
us
• We've developed our own definition called ViewLayout
abstract class ViewLayout(private val parent: ViewGroup) {
lateinit var itemView: View
val context = parent.context
fun createView(): View {
return createViewInternal(AnkoContext.create(parent.context, parent))
.also { itemView = it }
}
abstract protected fun createViewInternal(ui: AnkoContext<ViewGroup>): View
}
164. Missing attributes
• Every polite View has setter for each attribute that can be set in XML
• Unfortunately, rude views exists. Even in Android or Support Library
165. Missing attributes
• Every polite View has setter for each attribute that can be set in XML
• Unfortunately, rude views exists. Even in Android or Support Library
• eg. tabMaxWidth in TabLayout class.
166. Missing attributes
• Every polite View has setter for each attribute that can be set in XML
• Unfortunately, rude views exists. Even in Android or Support Library
• eg. tabMaxWidth in TabLayout class.
• Solution?
167. Missing attributes
• Every polite View has setter for each attribute that can be set in XML
• Unfortunately, rude views exists. Even in Android or Support Library
• eg. tabMaxWidth in TabLayout class.
• Solution?
• Use include function as described earlier and define your view in
XML
168. Missing attributes
• Every polite View has setter for each attribute that can be set in XML
• Unfortunately, rude views exists. Even in Android or Support Library
• eg. tabMaxWidth in TabLayout class.
• Solution?
• Use include function as described earlier and define your view in
XML
• Check the source code and perform reflection to set desired
attribute
169. Weird platform bugs
• Sometimes you are facing very weird behavior of your layouts,
especially on older versions of Android
170. Weird platform bugs
• Sometimes you are facing very weird behavior of your layouts,
especially on older versions of Android
• Example: On preKitKat versions, some views didn’t have padding even
when defined in code
171. Weird platform bugs
• Sometimes you are facing very weird behavior of your layouts,
especially on older versions of Android
• Example: On preKitKat versions, some views didn’t have padding even
when defined in code
• Several hours later we've found this SO question “Where'd padding go,
when setting background Drawable?"
172. Weird platform bugs
• Sometimes you are facing very weird behavior of your layouts,
especially on older versions of Android
• Example: On preKitKat versions, some views didn’t have padding even
when defined in code
• Several hours later we've found this SO question “Where'd padding go,
when setting background Drawable?"
• It turns out that if you set background after the padding, padding is lost
173. Weird platform bugs
• Sometimes you are facing very weird behavior of your layouts,
especially on older versions of Android
• Example: On preKitKat versions, some views didn’t have padding even
when defined in code
• Several hours later we've found this SO question “Where'd padding go,
when setting background Drawable?"
• It turns out that if you set background after the padding, padding is lost
• There is no such problem with XML because it sets attributes always in
the same order
174. Anko maintanence
• In the moment of writing there is 113 opened issues on Github and 18
pull request.
175. Anko maintanence
• In the moment of writing there is 113 opened issues on Github and 18
pull request.
• Contributions are very rare and since June there is no major feature/
bugfix
176. Anko maintanence
• In the moment of writing there is 113 opened issues on Github and 18
pull request.
• Contributions are very rare and since June there is no major feature/
bugfix
• Members of JB said in YouTrack that Anko is one-man project and that
one man doesn't have time for that right now :(
177. Anko maintanence
• In the moment of writing there is 113 opened issues on Github and 18
pull request.
• Contributions are very rare and since June there is no major feature/
bugfix
• Members of JB said in YouTrack that Anko is one-man project and that
one man doesn't have time for that right now :(
• Preview was broken since AS 1.6, fixed in 2.4 but broke once again in
3.0. But couple of days ago…
179. Anko maintanence
• Commit that fixed Preview appeared!
• Unfortunately no version with that fix was released …
180. Anko maintanence
• Commit that fixed Preview appeared!
• Unfortunately no version with that fix was released …
• but that did not stopped me
181. Anko maintanence
• Commit that fixed Preview appeared!
• Unfortunately no version with that fix was released …
• but that did not stopped me
• I built Anko from scratch and after apprx 1 hour later … PREVIEW IS
WORKING
182. Anko preview
• To see the most recent changes in view you must always rebuild project
183. Anko preview
• To see the most recent changes in view you must always rebuild project
• Works only with AnkoComponent as a class of your layout definition
184. Anko preview
• To see the most recent changes in view you must always rebuild project
• Works only with AnkoComponent as a class of your layout definition
• Does not provide Visual Editor of your layout or its view attributes
185. Final tips
• Don't forget to use ids of your views. Framework doesn’t retain View
state without them.
186. Final tips
• Don't forget to use ids of your views. Framework doesn’t retain View
state without them.
• With XML you can AutoFormat your layout and reorder attributes. There
is no such luxury in Anko. Codestyle needed.
187. Final tips
• Don't forget to use ids of your views. Framework doesn’t retain View
state without them.
• With XML you can AutoFormat your layout and reorder attributes. There
is no such luxury in Anko. Codestyle needed.
• Do not mix Anko with XML in one project. It really confuse newcomer to
the project or your future self
188. Final tips
• Don't forget to use ids of your views. Framework doesn’t retain View
state without them.
• With XML you can AutoFormat your layout and reorder attributes. There
is no such luxury in Anko. Codestyle needed.
• Do not mix Anko with XML in one project. It really confuse newcomer to
the project or your future self
• Always define your layout in separate class. It allows abstraction and it
strictly defines coupling between logic and UI definition