Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Handsome Codes with Kotlin

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for sembozdemir sembozdemir
November 26, 2017

Handsome Codes with Kotlin

Presented in GDG DevFest Istanbul 2017, GDG DevFest Izmir 2017

Kotlin is a very flexible programming language. You have a chance to write more readable and good-looking code. The only limit is your imagination and creativity. I'll show you some examples which are really handsome :)

- Extension Functions
- Higher-order Functions
- Operator Overloading
- Infix Notations
- Delegate Properties
- Reified Types
- Semantic Validations
- Standard.kt (let, apply, also, with...)
- DSLs
- Elegant DiffUtil
- Android Runtime Permissions in Kotlin way

Avatar for sembozdemir

sembozdemir

November 26, 2017
Tweet

More Decks by sembozdemir

Other Decks in Programming

Transcript

  1. #dfist #Devfest17 What will I tell? Some tricks and features

    to write more readable and good-looking code in Kotlin
  2. #dfist #Devfest17 Extension Functions ENTER FILENAME/LANG fun View.setVisible(visible: Boolean) {

    visibility = if (visible) View.VISIBLE else View.GONE } Views.kt Kotlin
  3. #dfist #Devfest17 Extension Functions ENTER FILENAME/LANG fun View.setVisible(visible: Boolean, falseVisibility:

    Int = View.GONE) { visibility = if (visible) View.VISIBLE else falseVisibility } Views.kt Kotlin
  4. #dfist #Devfest17 Extension Functions ENTER FILENAME/LANG fun Context.colorRes(@ColorRes colorRes: Int):

    Int { return ContextCompat.getColor(this, colorRes) } Contexts.kt Kotlin
  5. #dfist #Devfest17 Extension Functions ENTER FILENAME/LANG Intent launchIntent = getPackageManager().getLaunchIntentForPackage(packageName);

    if (launchIntent == null) { String playStoreUrl = "http://play.google.com/store/apps/details?id=" + packageName; launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(playStoreUrl)); } launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(launchIntent); Java
  6. #dfist #Devfest17 Extension Functions ENTER FILENAME/LANG fun Context.launchApp(packageName: String) {

    val launchIntent = packageManager.getLaunchIntentForPackage(packageName) ?: Intent( Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=$packageName") ) launchIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK startActivity(launchIntent) } Intents.kt Kotlin
  7. #dfist #Devfest17 Extension Functions ENTER FILENAME/LANG fun ImageView.setImageUrl(url: String) {

    Picasso.with(context).load(url).into(this) } ImageViews.kt Kotlin
  8. #dfist #Devfest17 Higher-order Functions ENTER FILENAME/LANG fun ImageView.setImageUrl( url: String,

    init: (RequestCreator.() -> RequestCreator)? = null) { val requestCreator = Picasso.with(context).load(url) if (init != null) requestCreator.init() requestCreator.into(this) } ImageViews.kt Kotlin
  9. #dfist #Devfest17 Higher-order Functions ENTER FILENAME/LANG SharedPreferences.Editor editor = prefs.edit();

    editor.putInt(KEY_ID, 1); editor.putString(KEY_FOO, "bar"); editor.apply(); Java
  10. #dfist #Devfest17 Higher-order Functions ENTER FILENAME/LANG inline fun SharedPreferences.edit( body:

    SharedPreferences.Editor.() -> Unit) { val editor = edit() editor.body() editor.apply() } SharedPrefs.kt Kotlin
  11. #dfist #Devfest17 Higher-order Functions ENTER FILENAME/LANG fun SharedPreferences.Editor.put(pair: Pair<String, Any>)

    { val key = pair.first val value = pair.second when(value) { is String -> putString(key, value) is Int -> putInt(key, value) is Boolean -> putBoolean(key, value) is Long -> putLong(key, value) is Float -> putFloat(key, value) else -> error("Only primitive types can be stored with put() function.") } } SharedPrefs.kt Kotlin
  12. #dfist #Devfest17 Higher-order Functions ENTER FILENAME/LANG Snackbar.make(rootView, "Hello!", Snackbar.LENGTH_LONG) .setAction("Bye!",

    new View.OnClickListener() { @Override public void onClick(View view) { sayBye(); } }) .show(); Java
  13. #dfist #Devfest17 Higher-order Functions ENTER FILENAME/LANG fun Activity.snackbar(message: CharSequence, view:

    View = findViewById(android.R.id.content), duration: Int = Snackbar.LENGTH_LONG, init: (Snackbar.() -> Unit)? = null) { val snackbar = Snackbar.make(view, message, duration) with(snackbar) { init?.invoke(snackbar) show() } } Snackbars.kt Kotlin
  14. #dfist #Devfest17 Operator Overloading ENTER FILENAME/LANG operator fun ViewGroup.plusAssign(view: View)

    = addView(view) operator fun ViewGroup.minusAssign(view: View) = removeView(view) Views.kt Kotlin
  15. #dfist #Devfest17 Infix Notations ENTER FILENAME/LANG public infix fun and(other:

    Boolean): Boolean public infix fun or(other: Boolean): Boolean public infix fun xor(other: Boolean): Boolean Boolean.kt Kotlin
  16. #dfist #Devfest17 Infix Notations ENTER FILENAME/LANG public infix fun <A,

    B> A.to(that: B): Pair<A, B> = Pair(this, that) Kotlin
  17. #dfist #Devfest17 Delegate Properties ENTER FILENAME/LANG var items: MutableList<String> by

    Delegates.observable(mutableListOf()) { p, old, new -> adapter.notifyDataSetChanged() } val adapter = RecyclerViewAdapter(items) // somewhere in code items = newItems Kotlin
  18. #dfist #Devfest17 Delegate Properties ENTER FILENAME/LANG SharedPreferences prefs = getSharedPreferences("prefs",

    Context.MODE_PRIVATE); boolean isToolTipShown = prefs.getBoolean("isToolTipShown", false); if (!isToolTipShown) { showToolTip(); prefs.edit().putBoolean("isToolTipShown", true).apply(); } Java
  19. #dfist #Devfest17 Delegate Properties ENTER FILENAME/LANG var isToolTipShown by sharedPref("isToolTipShown",

    false) //... if (!isToolTipShown) { showToolTip() isToolTipShown = true } Kotlin
  20. #dfist #Devfest17 Delegate Properties ENTER FILENAME/LANG class SharedPrefDelegate<T>(private val context:

    Context, private val key: String, private val defaultValue: T, private val fileName: String, private val mode: Int) { private val prefs: SharedPreferences by lazy { context.getSharedPreferences(fileName, mode) } operator fun getValue(thisRef: Any?, property: KProperty<*>): T { return prefs.get(key, defaultValue) } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { prefs.edit { put(key to value) } } } SharedPrefs.kt Kotlin
  21. #dfist #Devfest17 Delegate Properties ENTER FILENAME/LANG fun <T> Context.sharedPref(key: String,

    defaultValue: T, fileName: String = "prefs", mode: Int = Context.MODE_PRIVATE): SharedPrefDelegate<T> { return SharedPrefDelegate(this, key, defaultValue, fileName, mode) } SharedPrefs.kt Kotlin
  22. #dfist #Devfest17 Reified Types ENTER FILENAME/LANG Intent intent = new

    Intent(this, PostActivity.class); startActivity(intent); Java
  23. #dfist #Devfest17 Reified Types ENTER FILENAME/LANG inline fun <reified T

    : Activity> Context.start() { startActivity(Intent(this, T::class.java)) } Intents.kt Kotlin
  24. #dfist #Devfest17 Reified Types ENTER FILENAME/LANG Intent intent = new

    Intent(this, PostActivity.class); intent.putExtra("fooString", "bar"); intent.putExtra("fooInt", 2); startActivity(intent); Java
  25. #dfist #Devfest17 Reified Types ENTER FILENAME/LANG inline fun <reified T

    : Activity> Context.start(vararg params: Pair<String, Any>) { val intent = Intent(this, T::class.java).apply { params.forEach { val value = it.second when (value) { is Int -> putExtra(it.first, value) is String -> putExtra(it.first, value) //... else -> throw IllegalArgumentException("Wrong param type!") } return@forEach } } startActivity(intent) } Intents.kt Kotlin
  26. #dfist #Devfest17 Semantic Validations ENTER FILENAME/LANG public <T> T getFifthElement(T[]

    array) { if (array == null || array.length < 5) { throw new IllegalArgumentException( "array should be non-null and have at least 5" ); } return array[4]; } Java
  27. #dfist #Devfest17 Semantic Validations ENTER FILENAME/LANG fun <T> getFifthElement(array: Array<T>):

    T { require(array.size >= 5) { "array should have at least five elements, " + "but has ${array.size}!" } return array[4] } Kotlin
  28. #dfist #Devfest17 Semantic Validations ENTER FILENAME/LANG inline fun require(value: Boolean,

    lazyMessage: () -> Any): Unit { if (!value) { val message = lazyMessage() throw IllegalArgumentException(message.toString()) } } Preconditions.kt Kotlin
  29. #dfist #Devfest17 Semantic Validations ENTER FILENAME/LANG public void doSomething() {

    if (!isInitialized()) { throw new IllegalStateException( "Helper is not initialized!" ); } // do something } Java
  30. #dfist #Devfest17 Semantic Validations ENTER FILENAME/LANG inline fun check(value: Boolean,

    lazyMessage: () -> Any): Unit { if (!value) { val message = lazyMessage() throw IllegalStateException(message.toString()) } } Preconditions.kt Kotlin
  31. #dfist #Devfest17 Semantic Validations ENTER FILENAME/LANG fun doSomething(param: Int?) {

    requireNotNull(param) { "Param must not be null" } // do something } Kotlin
  32. #dfist #Devfest17 Semantic Validations ENTER FILENAME/LANG inline fun <T:Any> requireNotNull(value:

    T?, lazyMessage: () -> Any): T { if (value == null) { val message = lazyMessage() throw IllegalArgumentException(message.toString()) } else { return value } } Preconditions.kt Kotlin
  33. #dfist #Devfest17 Standard.kt ENTER FILENAME/LANG if (foo != null) {

    foo.doSomething(); foo.doSomethingDifferent(); } Java
  34. #dfist #Devfest17 Standard.kt ENTER FILENAME/LANG val foo = getFoo().apply {

    doSomething() doSomethingDifferent() } foo.doSomething() Kotlin
  35. #dfist #Devfest17 Standard.kt ENTER FILENAME/LANG inline fun <T> T.apply(block: T.()

    -> Unit): T { block() return this } Standard.kt Kotlin
  36. #dfist #Devfest17 Standard.kt ENTER FILENAME/LANG val foo = getFoo().also {

    doSomethingWithFoo(it) } foo.doSomethingDifferent() Kotlin
  37. #dfist #Devfest17 Standard.kt ENTER FILENAME/LANG inline fun <T> T.also(block: (T)

    -> Unit): T { block(this) return this } Standard.kt Kotlin
  38. #dfist #Devfest17 Standard.kt ENTER FILENAME/LANG inline fun <T, R> with(receiver:

    T, block: T.() -> R): R { return receiver.block() } Standard.kt Kotlin
  39. #dfist #Devfest17 DSLs ENTER FILENAME/LANG Picasso.with(context).load(url).into(imageView, new Callback() { @Override

    public void onSuccess() { Log.d(TAG, "Yay!"); } @Override public void onError() { // no need } }); Java
  40. #dfist #Devfest17 DSLs class __Callback : com.squareup.picasso.Callback { private var

    _onSuccess: (() -> Unit)? = null private var _onError: (() -> Unit)? = null fun onSuccess(func: () -> Unit) { _onSuccess = func } fun onError(func: () -> Unit) { _onError = func } override fun onSuccess() { _onSuccess?.invoke() } override fun onError() { _onError?.invoke() } } Picassos.kt Kotlin
  41. #dfist #Devfest17 DSLs ENTER FILENAME/LANG fun RequestCreator.into(target: ImageView, func: __Callback.()

    -> Unit) { val callback = __Callback() callback.func() into(target, callback) } Picassos.kt Kotlin
  42. #dfist #Devfest17 Elegant DiffUtil ENTER FILENAME/LANG fun notifyChanges(oldList: List<Item>, newList:

    List<Item>) { val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldList[oldItemPosition].id == newList[newItemPosition].id } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldList[oldItemPosition] == newList[newItemPosition] } override fun getOldListSize() = oldList.size override fun getNewListSize() = newList.size }) diff.dispatchUpdatesTo(this) } Kotlin
  43. #dfist #Devfest17 Elegant DiffUtil ENTER FILENAME/LANG fun <T> RecyclerView.Adapter<*>.autoNotify(oldList: List<T>,

    newList: List<T>, compare: (T, T) -> Boolean) { val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return compare(oldList[oldItemPosition], newList[newItemPosition]) } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldList[oldItemPosition] == newList[newItemPosition] } override fun getOldListSize() = oldList.size override fun getNewListSize() = newList.size }) diff.dispatchUpdatesTo(this) } RecyclerViews.kt Kotlin
  44. #dfist #Devfest17 Permissions ENTER FILENAME/LANG if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED)

    { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) { Toast.makeText(this, "You better grant permission!", Toast.LENGTH_LONG).show(); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS); } } else { doSomething() } Java
  45. #dfist #Devfest17 Permissions ENTER FILENAME/LANG @Override public void onRequestPermissionsResult(int requestCode,

    String permissions[], int[] grantResults) { switch (requestCode) { case REQUEST_READ_CONTACTS: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { doSomething(); } else { Log.d(TAG, "Permission Denied!"); } return; } } } Java
  46. #dfist #Devfest17 Permissions ENTER FILENAME/LANG override fun onRequestPermissionsResult(requestCode: Int, permissions:

    Array<out String>, grantResults: IntArray) { when (requestCode) { REQUEST_READ_CONTACTS -> handlePermissionsResult(permissions, grantResults) { doSomething() } } } Kotlin
  47. #dfist #Devfest17 Permissions ENTER FILENAME/LANG inline fun Activity.doIfGranted(permissions: Array<String>, requestCode:

    Int, onGranted: () -> Unit) { val permissionsNeeded = permissions.filter { !isPermissionGranted(it) } if (permissionsNeeded.isNotEmpty()) { ActivityCompat.requestPermissions(this, permissionsNeeded.toTypedArray(), requestCode) } else { onGranted() } } Permissions.kt Kotlin
  48. #dfist #Devfest17 Permissions ENTER FILENAME/LANG inline fun Activity.doIfGranted(permission: String, requestCode:

    Int, onGranted: () -> Unit) { doIfGranted(arrayOf(permission), requestCode, onGranted) } fun Activity.isPermissionGranted(permission: String): Boolean { return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED } Permissions.kt Kotlin
  49. #dfist #Devfest17 Permissions ENTER FILENAME/LANG inline fun Activity.handlePermissionsResult( permissions: Array<out

    String>, grantResults: IntArray, noinline onDenied: ((List<String>) -> Unit)? = null, onGranted: (() -> Unit)) { val deniedPermissions = permissions.indices .filter { grantResults[it] == PackageManager.PERMISSION_DENIED } .map { permissions[it] } if (deniedPermissions.isEmpty()) { onGranted() } else { onDenied?.invoke(deniedPermissions.toList()) } } Permissions.kt Kotlin
  50. #dfist #Devfest17 Permissions ENTER FILENAME/LANG Full version of Permissions is

    here: https://gist.github.com/sembozdemir/ce8bebc7000adef04360b4f47ce478fd Permissions.kt