The Kotlin RoadMap

Temidayo Adefioye
8 min readApr 24, 2018
The road to Kotlin Downtown

Every great developer you know got there by solving problems they were unqualified to solve until they actually did it.” — Patrick McKenzie

In my previous article, I shared everything you need to know about Kotlin. If you haven’t read it yet, I will advice you to check it out as that article would give you a broader view of Kotlin as a whole.

A couple of months ago, I found myself in Kotlin downtown, as soon as I got to the street, I saw some strange signs written everywhere on its walls, I asked myself, oh my!!! what are all these “!!” and why is the street littered with “?” and “:”. I was overwhelmed with the whole stuff, however I picked up the map of the street to decipher these signs, voila it worked!!! I could figure out everything in no time.This is so cool, super cool, i said to myself.

Have you been to Kotlin downtown before? Well if you have, congratulations!! and if not, I am ready to take a walk with you downtown. I will be sharing the coolest part of this street with a simple Login android App. Kotlin has come to stay, lets take a smooth ride into downtown.

Smooth Drive to Kotlin Downtown

To get started with Kotlin and Android you need android studio running on your machine. To download latest android studio click here. Kotlin is fully supported in Android Studio 3.0 and higher, so it’s quite easy to create new projects with Kotlin files, add Kotlin files to your existing project, and convert Java language code to Kotlin. You can then use all of Android Studio’s existing tools with your Kotlin code, such as autocomplete, lint checking, refactoring, debugging, and more.

Create a new project with Kotlin

Using Kotlin with a new project requires just one extra click in the New Project wizard:

  • In Android Studio, click File > New > New Project. Or if you’ve just opened Android Studio and see the Welcome to Android Studio window, click Start a new Android Studio project.
  • On the first screen, check Include Kotlin support. That’s the only difference.
  • Click Next and continue through the wizard until you’re done.
Get started with Kotlin
Target Android Devices

Add an Activity to your project. Select Empty Activity

Add an Activity

Now change the Activity name to LoginActivity then click finish!!!. Yesssssss! now we have an empty project, we are going to create a simple app with a login activity. The app will be able to validate login credentials as well

Let us look at our gradle files

<PROJECT_ROOT>\app\build.gradle is specific for app module

<PROJECT_ROOT>\build.gradle is a "Top-level build file" where you can add configuration options common to all sub-projects/modules.

If you use the same project name as mine, your gradle should look like this.

Lets update our activity_login.xml layout for the Login.

Here is the code

If you have done that, awesome!!!

Now, Go to your LoginActivity.kt

We are about to experience the power of Kotlin.

I am going to explain the whole project one after the other so you can have a clearer picture of how it works!!

See the source code for LoginActivity.kt at the end of the article.

In kotlin you extend AppCompatActivity and implement an interface like this

class LoginActivity : AppCompatActivity(), LoaderCallbacks<Cursor> {}

In your Activity, we are going to have the following methods:

override fun onCreate(savedInstanceState: Bundle?) {}
private fun populateAutoComplete() {}
private fun mayRequestContacts(): Boolean {}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,grantResults: IntArray) {}
private fun attemptLogin() {}
private fun isEmailValid(email: String): Boolean {}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private fun showProgress(show: Boolean) {}
override fun onCreateLoader(i: Int, bundle: Bundle?): Loader<Cursor>{}
override fun onLoadFinished(cursorLoader: Loader<Cursor>, cursor: Cursor) {}
override fun onLoaderReset(cursorLoader: Loader<Cursor>) {}
private fun addEmailsToAutoComplete(emailAddressCollection: List<String>) {}
object ProfileQuery {}
inner class UserLoginTask internal constructor(private val mEmail: String, private val mPassword: String) : AsyncTask<Void, Void, Boolean>() {}
companion object {}
  1. Lets start with our OnCreate method in your Activity.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
// Set up the login form.
populateAutoComplete() // A method to populate AUTOCOMPLETE on your form
// Goodbye to findViewById. call the id of your editText.
password.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
if
(id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
attemptLogin()
return@OnEditorActionListener true
}
false
}
)
// Goodbye to findViewById. call the id of your Button.
email_sign_in_button.setOnClickListener { attemptLogin() }
}

2. You should notice two undeclared methods populateAutoComplete() and attemptLogin(). Let us create that right away

private fun populateAutoComplete() {
if (!mayRequestContacts()) {
return
}

loaderManager.initLoader(0, null, this)
}
private fun mayRequestContacts(): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true
}
if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
return true
}
if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
Snackbar.make(email, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
.setAction(android.R.string.ok,
{ requestPermissions(arrayOf(READ_CONTACTS), REQUEST_READ_CONTACTS) })
} else {
requestPermissions(arrayOf(READ_CONTACTS), REQUEST_READ_CONTACTS)
}
return false
}
/**
* Callback received when a permissions request has been completed.
*/
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
grantResults: IntArray) {
if (requestCode == REQUEST_READ_CONTACTS) {
if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
populateAutoComplete()
}
}
}

For attemptLogin();

/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
private fun attemptLogin() {
if (mAuthTask != null) {
return
}

// Reset errors.
email.error = null
password.error = null

// Store values at the time of the login attempt.
val emailStr = email.text.toString()
val passwordStr = password.text.toString()

var cancel = false
var
focusView: View? = null

// Check for a valid password, if the user entered one.
if (!TextUtils.isEmpty(passwordStr) && !isPasswordValid(passwordStr)) {
password.error = getString(R.string.error_invalid_password)
focusView = password
cancel = true
}

// Check for a valid email address.
if (TextUtils.isEmpty(emailStr)) {
email.error = getString(R.string.error_field_required)
focusView = email
cancel = true
} else if (!isEmailValid(emailStr)) {
email.error = getString(R.string.error_invalid_email)
focusView = email
cancel = true
}

if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView?.requestFocus()
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true)
mAuthTask = UserLoginTask(emailStr, passwordStr)
mAuthTask!!.execute(null as Void?)
}
}
private fun isEmailValid(email: String): Boolean {
//TODO: Replace this with your own logic
return
email.contains("@")
}

private fun isPasswordValid(password: String): Boolean {
//TODO: Replace this with your own logic
return
password.length > 4
}

In this method, I implemented a method called UserLoginTask(); This Represents an asynchronous login/registration task used to authenticate the user from your server. In my project, I only used a mock up data to validate user, so no server was built for the purpose of this article.

/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
inner class UserLoginTask internal constructor(private val mEmail: String, private val mPassword: String) : AsyncTask<Void, Void, Boolean>() {

override fun doInBackground(vararg params: Void): Boolean? {
// TODO: attempt authentication against a network service.

try
{
// Simulate network access.
Thread.sleep(2000)
} catch (e: InterruptedException) {
return false
}

return DUMMY_CREDENTIALS
.map { it.split(":") }
.firstOrNull { it[0] == mEmail }
?.let {
// Account exists, return true if the password matches.
it[1] == mPassword
}
?: true
}

override fun onPostExecute(success: Boolean?) {
mAuthTask = null
showProgress(false)

if (success!!) {
Toast.makeText(baseContext,"Login Successful!!",Toast.LENGTH_LONG).show();
} else {
password.error = getString(R.string.error_incorrect_password)
password.requestFocus()
}
}

override fun onCancelled() {
mAuthTask = null
showProgress(false)
}
}

companion object {

/**
* Id to identity READ_CONTACTS permission request.
*/
private val REQUEST_READ_CONTACTS = 0

/**
* A dummy authentication store containing known user names and passwords.
*
TODO: remove after connecting to a real authentication system.
*/
private val DUMMY_CREDENTIALS = arrayOf("boo@example.com:kotlin", "bar@example.com:world")
}
/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private fun showProgress(show: Boolean) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime).toLong()

login_form.visibility = if (show) View.GONE else View.VISIBLE
login_form.animate()
.setDuration(shortAnimTime)
.alpha((if (show) 0 else 1).toFloat())
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
login_form.visibility = if (show) View.GONE else View.VISIBLE
}
})

login_progress.visibility = if (show) View.VISIBLE else View.GONE
login_progress.animate()
.setDuration(shortAnimTime)
.alpha((if (show) 1 else 0).toFloat())
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
login_progress.visibility = if (show) View.VISIBLE else View.GONE
}
})
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
login_progress.visibility = if (show) View.VISIBLE else View.GONE
login_form.visibility = if (show) View.GONE else View.VISIBLE
}
}

The final part is the override methods for the interface we implemented that is LoaderCallbacks

In java if you want to declare an override method you need to add @override annotation, but Kotlin offers something more awesome, override. Don’t forget that fun means function in Kotlin unlike java.


override fun
onCreateLoader(i: Int, bundle: Bundle?): Loader<Cursor> {
return CursorLoader(this,
// Retrieve data rows for the device user's 'profile' contact.
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,

// Select only email addresses.
ContactsContract.Contacts.Data.MIMETYPE + " = ?", arrayOf(ContactsContract.CommonDataKinds.Email
.CONTENT_ITEM_TYPE),

// Show primary email addresses first. Note that there won't be
// a primary email address if the user hasn't specified one.
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC")
}

override fun onLoadFinished(cursorLoader: Loader<Cursor>, cursor: Cursor) {
val emails = ArrayList<String>()
cursor.moveToFirst()
while (!cursor.isAfterLast) {
emails.add(cursor.getString(ProfileQuery.ADDRESS))
cursor.moveToNext()
}

addEmailsToAutoComplete(emails)
}

override fun onLoaderReset(cursorLoader: Loader<Cursor>) {

}

private fun addEmailsToAutoComplete(emailAddressCollection: List<String>) {
//Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
val adapter = ArrayAdapter(this@LoginActivity,
android.R.layout.simple_dropdown_item_1line, emailAddressCollection)

email.setAdapter(adapter)
}

object ProfileQuery {
val PROJECTION = arrayOf(
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY)
val ADDRESS = 0
val IS_PRIMARY = 1
}

Great!!!! Now let’s take a look at the complete version of LoginActivity.kt

If i wanted to write this in java, it would have costed me more lines of code which is time consuming, but thanks to Kotlin, my code is precise and well simplified!!

Run your project and you will have this on your emulator or android device

To download the source code of this project, you can do that here.

You can share your thoughts and questions on the comment section.

Join me on my next trip to Kotlin Downtown, you don’t wanna miss it!!!

--

--

Temidayo Adefioye

Founder, CodeNest Africa | [in]structor | Software Engineer | Speaker | Author