Kotlin is a first-class language for Android development since Google I/O 2017. And it’s here to stay!
Thanks to Android Studio it’s really easy to introduce Kotlin in an existing project, the configuration is trivial and then we can convert Java classes to Kotlin using a Alt+Shift+Cmd+K. But the new syntax is the just beginning, using Kotlin we can improve our code making it more readable and simpler to write.
In this talk we’ll see how to use some Kotlin features (for example data classes, collections, coroutines and delegates) to simplify Android development comparing the code with the equivalent “modern” Java code. It’s not fair to compare Kotlin code with plain Java 6 code so the Java examples will use lambdas and some external libraries like RxJava and AutoValue.
8. for (Person person : people) {
City city = person.getAddress().getCity();
if (city.getRegion().equals("Tuscany")) {
cities.add(city.getName() + " (" + city.getCode() + ")");
}1
}2
System.out.println(b.toString());
List<Person> people = Arrays.asList(...);
StringBuilder b = new StringBuilder();
for (String s : cities) {
if (b.length() > 0) {
b.append(", ");
}3
b.append(s);
}4
Set<String> cities = new TreeSet<>();
9. val people = listOf(...)
val s = people
.map { it.address.city }
.filter { it.region == "Tuscany" }
.distinct()
.sortedBy { it.name }
.joinToString { "${it.name} (${it.code})" }
println(s)
24. private const val MY_PARAM = "my_param"
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
val param
get() = arguments!!.getString(MY_PARAM)
companion object {
fun newInstance(param: String): MyFragment {
return MyFragment().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
}9
}0
25. public class MyFragment extends Fragment {
public static final Companion Companion = new Companion();
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String myStringParam = getParam();
//...
}9
public String getParam() {
return getArguments().getString("my_param");
}8
public static final class Companion {
public final MyFragment newInstance(String param) {
MyFragment fragment = new MyFragment();
Bundle bundle = BundleKt.bundleOf(
new Pair[] {
TuplesKt.to("my_param", param)
}1
);
fragment.setArguments(bundle);
return fragment;
}2
}3
}4
private const val MY_PARAM = "my_param"
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
val param
get() = arguments!!.getString(MY_PARAM)
companion object {
fun newInstance(param: String): MyFragment {
return MyFragment().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
}9
}0
26. private const val MY_PARAM = "my_param"
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
val param
get() = arguments!!.getString(MY_PARAM)
companion object {
@JvmStatic
fun newInstance(param: String): MyFragment {
return MyFragment().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
}9
}0
public class MyFragment extends Fragment {
public static final Companion Companion = new Companion();
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String myStringParam = getParam();
//...
}9
public String getParam() {
return getArguments().getString("my_param");
}8
public static MyFragment newInstance(String param) {
return Companion.newInstance(param);
}x
public static final class Companion {
public final MyFragment newInstance(String param) {
MyFragment fragment = new MyFragment();
Bundle bundle = BundleKt.bundleOf(
new Pair[] {
TuplesKt.to("my_param", param)
}1
);
fragment.setArguments(bundle);
return fragment;
}2
}3
}4
27. private const val MY_PARAM = "my_param"
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
val param
get() = arguments!!.getString(MY_PARAM)
companion object {
fun newInstance(param: String): MyFragment {
return MyFragment().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
}9
}0
28. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
}1
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
val param
get() = arguments!!.getString(MY_PARAM)
companion object : FragmentCreator<String>(::MyFragment) {2
fun newInstance(param: String): MyFragment {
return MyFragment().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
}9
}0
29. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
fun newInstance(param: T) : Fragment {
return factory().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
}1
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
val param
get() = arguments!!.getString(MY_PARAM)
companion object : FragmentCreator<String>(::MyFragment)
}0
30. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
fun newInstance(param: T) : Fragment {
return factory().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
}1
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
val param
get() = arguments!!.getString(MY_PARAM)
companion object : FragmentCreator<String>(::MyFragment)
}0
31. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
fun newInstance(param: T) : Fragment {
return factory().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
val param
get() = arguments!!.get(MY_PARAM) as T
}1
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
companion object : FragmentCreator<String>(::MyFragment)
}0
32. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
fun newInstance(param: T) : Fragment {
return factory().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
val Fragment.param
get() = arguments!!.get(MY_PARAM) as T
}1
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
companion object : FragmentCreator<String>(::MyFragment)
}0
33. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
fun newInstance(param: T) : Fragment {
return factory().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
val Fragment.param
get() = arguments!!.get(MY_PARAM) as T
}1
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
companion object : FragmentCreator<String>(::MyFragment)
}0
34. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
fun newInstance(param: T) : Fragment {
return factory().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
val Fragment.param
get() = arguments!!.get(MY_PARAM) as T
fun param(fragment: Fragment): T {
return fragment.arguments!!.get(MY_PARAM) as T
}
}1
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
companion object : FragmentCreator<String>(::MyFragment)
}0
35. class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myStringParam = param
//...
}5
companion object : FragmentCreator<String>(::MyFragment)
}0
val fragment = MyFragment.newInstance("ABC")
36. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
fun newInstance(param: T) : Fragment {
return factory().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
val Fragment.param: T
get() = arguments!!.get(MY_PARAM) as T
fun param(fragment: Fragment): T {
return fragment.arguments!!.get(MY_PARAM) as T
}
}1
37. fun bundleOf(vararg pairs: Pair<String, Any?>) = Bundle(pairs.size).apply {
for ((key, value) in pairs) {
when (value) {
null -> putString(key, null) // Any nullable type will suffice.
// Scalars
is Boolean -> putBoolean(key, value)
is Byte -> putByte(key, value)
is Char -> putChar(key, value)
is Double -> putDouble(key, value)
is Float -> putFloat(key, value)
is Int -> putInt(key, value)
is Long -> putLong(key, value)
is Short -> putShort(key, value)
// References
is Bundle -> putBundle(key, value)
is CharSequence -> putCharSequence(key, value)
is Parcelable -> putParcelable(key, value)
// Scalar arrays
is BooleanArray -> putBooleanArray(key, value)
is ByteArray -> putByteArray(key, value)
is CharArray -> putCharArray(key, value)
is DoubleArray -> putDoubleArray(key, value)
is FloatArray -> putFloatArray(key, value)
is IntArray -> putIntArray(key, value)
is LongArray -> putLongArray(key, value)
is ShortArray -> putShortArray(key, value)
// Reference arrays
is Array<*> -> {
val componentType = value::class.java.componentType
@Suppress("UNCHECKED_CAST") // Checked by reflection.
when {
Parcelable::class.java.isAssignableFrom(componentType) -> {
putParcelableArray(key, value as Array<Parcelable>)
}
String::class.java.isAssignableFrom(componentType) -> {
putStringArray(key, value as Array<String>)
}
CharSequence::class.java.isAssignableFrom(componentType) -> {
putCharSequenceArray(key, value as Array<CharSequence>)
}
Serializable::class.java.isAssignableFrom(componentType) -> {
putSerializable(key, value)
}
else -> {
val valueType = componentType.canonicalName
throw IllegalArgumentException(
"Illegal value array type $valueType for key "$key"")
}5
}6
}7
// Last resort. Also we must check this after Array<*> as all arrays are serializable.
is Serializable -> putSerializable(key, value)
else -> {
if (Build.VERSION.SDK_INT >= 18 && value is Binder) {
putBinder(key, value)
} else if (Build.VERSION.SDK_INT >= 21 && value is Size) {
putSize(key, value)
} else if (Build.VERSION.SDK_INT >= 21 && value is SizeF) {
putSizeF(key, value)
} else {
val valueType = value.javaClass.canonicalName
throw IllegalArgumentException("Illegal value type $valueType for key "$key"")
}1
}2
}3
}4
}5
38. fun bundleOf(vararg pairs: Pair<String, Any?>) = Bundle(pairs.size).apply {
for ((key, value) in pairs) {
when (value) {
null -> putString(key, null) // Any nullable type will suffice.
// Scalars
is Boolean -> putBoolean(key, value)
is Byte -> putByte(key, value)
is Char -> putChar(key, value)
is Double -> putDouble(key, value)
is Float -> putFloat(key, value)
is Int -> putInt(key, value)
is Long -> putLong(key, value)
is Short -> putShort(key, value)
// References
is Bundle -> putBundle(key, value)
is CharSequence -> putCharSequence(key, value)
is Parcelable -> putParcelable(key, value)
// Scalar arrays
is BooleanArray -> putBooleanArray(key, value)
is ByteArray -> putByteArray(key, value)
is CharArray -> putCharArray(key, value)
is DoubleArray -> putDoubleArray(key, value)
is FloatArray -> putFloatArray(key, value)
is IntArray -> putIntArray(key, value)
is LongArray -> putLongArray(key, value)
39. }
Serializable::class.java.isAssignableFrom(componentType) -> {
putSerializable(key, value)
}
else -> {
val valueType = componentType.canonicalName
throw IllegalArgumentException(
"Illegal value array type $valueType for key "$key"")
}5
}6
}7
// Last resort. Also we must check this after Array<*> as all arrays are serializable.
is Serializable -> putSerializable(key, value)
else -> {
if (Build.VERSION.SDK_INT >= 18 && value is Binder) {
putBinder(key, value)
} else if (Build.VERSION.SDK_INT >= 21 && value is Size) {
putSize(key, value)
} else if (Build.VERSION.SDK_INT >= 21 && value is SizeF) {
putSizeF(key, value)
} else {
val valueType = value.javaClass.canonicalName
throw IllegalArgumentException("Illegal value type $valueType for key "$key"")
}1
}2
}3
}4
}5
40. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
fun newInstance(param: T) : Fragment {
return factory().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
val Fragment.param: T
get() = arguments!!.get(MY_PARAM) as T
fun param(fragment: Fragment): T {
return fragment.arguments!!.get(MY_PARAM) as T
}2
}1
41. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
val Fragment.param: T
get() = arguments!!.get(MY_PARAM) as T
fun param(fragment: Fragment): T {
return fragment.arguments!!.get(MY_PARAM) as T
}2
}1
fun <T : Parcelable> FragmentCreator<T>.newInstance(param: T): Fragment {
return factory().apply {
arguments = bundleOf(MY_PARAM to param)
}7
}8
42. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
val Fragment.param: T
get() = arguments!!.get(MY_PARAM) as T
fun param(fragment: Fragment): T {
return fragment.arguments!!.get(MY_PARAM) as T
}2
}1
fun <T : Parcelable> FragmentCreator<T>.newInstance(param: T): Fragment {
return factory().apply {4
arguments = Bundle().apply {5
putParcelable(MY_PARAM, param)
}6
}7
}8
43. private const val MY_PARAM = "my_param"
open class FragmentCreator<T>(
val factory: () -> Fragment
) {
val Fragment.param: T
get() = arguments!!.get(MY_PARAM) as T
fun param(fragment: Fragment): T {
return fragment.arguments!!.get(MY_PARAM) as T
}2
}1
fun <T : Parcelable> FragmentCreator<T>.newInstance(param: T): Fragment {
return factory().apply {4
arguments = Bundle().apply {5
putParcelable(MY_PARAM, param)
}6
}7
}8
fun FragmentCreator<String>.newInstance(param: String): Fragment {
return factory().apply {
arguments = Bundle().apply {
putString(MY_PARAM, param)
}
}
}
47. /**
* Delays coroutine for a given time without blocking
* a thread and resumes it after a specified time.
* ...
*/
suspend fun delay(time: Long, unit: TimeUnit = MILLISECONDS) {
//...
}
74. launch
executes a task on a coroutine context
an uncaught exception stops the app
used to start a computation
Deferred
await returns the result or throws an exception
can be used to execute something in parallel
can be created using async
withContext
suspending method
changes the execution thread
useful to force the execution of a method on a thread
75. My 2 cents
Suspending methods are easier to
use than RxJava Singles
Observables/Flowables are the best
abstraction for a stream of data
81. class TokenHolder(private val prefs: SharedPreferences) {
var token
get() = prefs.getString("token", "")
set(value) = prefs.edit().putString("token", value).apply()
}1
82. class TokenHolder(private val prefs: SharedPreferences) {
var token
get() = prefs.getString("token", "")
set(value) = prefs.edit().putString("token", value).apply()
}1
public class TokenHolder {
private final SharedPreferences prefs;
public TokenHolder(SharedPreferences prefs) {
this.prefs = prefs;
}1
public final String getToken() {
return this.prefs.getString("token", "");
}2
public final void setToken(String value) {
this.prefs.edit().putString("token", value).apply();
}3
}4
83. class TokenHolder(private val prefs: SharedPreferences) {
var token
get() = prefs.getString("token", "")
set(value) = prefs.edit().putString("token", value).apply()
}1
85. class TokenHolder(prefs: SharedPreferences) {
var token by prefs.string()
}1
fun SharedPreferences.string(): ReadWriteProperty<Any, String> {
return object : ReadWriteProperty<Any, String> {
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return getString(property.name, "")
}2
override fun setValue(thisRef: Any, property: KProperty<*>,
value: String) {
edit().putString(property.name, value).apply()
}3
}4
}5
86. fun SharedPreferences.string(
defaultValue: String = ""
): ReadWriteProperty<Any, String> {
return object : ReadWriteProperty<Any, String> {
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return getString(property.name, defaultValue)
}2
override fun setValue(thisRef: Any, property: KProperty<*>,
value: String) {
edit().putString(property.name, value).apply()
}3
}4
}5
class TokenHolder(prefs: SharedPreferences) {
var token by prefs.string()
}1
87. fun SharedPreferences.string(
defaultValue: String = "",
key: String? = null
): ReadWriteProperty<Any, String> {
return object : ReadWriteProperty<Any, String> {
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return getString(key ?: property.name, defaultValue)
}2
override fun setValue(thisRef: Any, property: KProperty<*>,
value: String) {
edit().putString(key ?: property.name, value).apply()
}3
}4
}5
class TokenHolder(prefs: SharedPreferences) {
var token by prefs.string()
}1
88. class TokenHolder(prefs: SharedPreferences) {
var token by prefs.string()
}1
public class TokenHolder {
static final KProperty delegatedProperty = //..
private final ReadWriteProperty token$delegate;
public TokenHolder(SharedPreferences prefs) {
this.token$delegate = PrefsKt.string(prefs, null, null);
}1
public final String getToken() {
return (String)token$delegate.getValue(this, delegatedProperty);
}2
public final void setToken(String var1) {
token$delegate.setValue(this, delegatedProperty, var1);
}3
}4
89. class TokenHolder(prefs: SharedPreferences) {
var token by prefs.string()
}1
tokenHolder.token += "ABC"
prefs.edit().putString(
"token",
prefs.getString("token", "") + "ABC"
).apply()
90. Wrapping up
Alt+Shift+Cmd+K is just the beginning
functional code is natural in Kotlin
code can be simplified using
companion objects, coroutines and
delegates
91. Links
Demo Project
github.com/fabioCollini/ArchitectureComponentsDemo
Libraries
Lightweight Stream github.com/aNNiMON/Lightweight-Stream-API
Guides and posts
Guide to kotlinx.coroutines by example
github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md
Async code using Kotlin Coroutines
proandroiddev.com/async-code-using-kotlin-coroutines-233d201099ff
Kotlin delegates in Android development part 1
hackernoon.com/kotlin-delegates-in-android-development-part-1-50346cf4aed7
Kotlin delegates in Android development part 2
proandroiddev.com/kotlin-delegates-in-android-development-part-2-2c15c11ff438