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

Handsome Codes with Kotlin

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

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