By Ravindra Kumar @ravidsrk
- Ravindra Kumar @ravidsrk
- Android Developer @Fueled
- Speaker at Droidcon In, Jsfoo, TiConf, Devfest
- Creator of AndroidStarters.com and KotlinExtensions.com
- Open source contributor @ravidsrk
- Author of Android Testing Guide
- Steps to Convert
- Common converter Issues
- Takeaways
- Eliminate all
!!
from your Kotlin code - Kotlin Extensions
- Kotlin Android Extensions
Once you learn basics syntax of Kotlin
- Convert files, one by one, via "⌥⇧⌘K", make sure tests still pass
Once you learn basics syntax of Kotlin
- Convert files, one by one, via "⌥⇧⌘K", make sure tests still pass
- Go over the Kotlin files and make them more idiomatic.
Once you learn basics syntax of Kotlin
- Convert files, one by one, via "⌥⇧⌘K", make sure tests still pass
- Go over the Kotlin files and make them more idiomatic.
- Repeat step 2 until you convert all the files.
Once you learn basics syntax of Kotlin
- Convert files, one by one, via "⌥⇧⌘K", make sure tests still pass
- Go over the Kotlin files and make them more idiomatic.
- Repeat step 2 until you convert all the files.
- Ship it.
- TypeCasting for the sake of Interoperability.
- Companion will add extra layer.
- If java method starting with getFoo(), converter looks for property with the name foo.
- Generics are hard to get it right on the first go.
- No argument captor.
- git diff If two developers are working on same java file and one guy converts it to Kotlin, it will be rework.
Here is the Java class:
public class DemoFragment extends BaseFragment implements DemoView {
@Override
public void displayMessageFromApi(String apiMessage) {
...
}
}
// Kotlin class
class DemoResponse {
@SerializedName("message") var message: String? = null
}
// Kotlin class
class DemoResponse {
@SerializedName("message") var message: String? = null
}
// Typecasting to String
mainView?.displayMessageFromApi(demoResponse.message)
// Kotlin class
class DemoResponse {
@SerializedName("message") var message: String? = null
}
// Typecasting to String
mainView?.displayMessageFromApi(demoResponse.message) // Compiler Error
// Kotlin class
class DemoResponse {
@SerializedName("message") var message: String? = null
}
// Typecasting to String
mainView?.displayMessageFromApi(demoResponse.message as String)
Here is Java class:
public class DetailActivity extends BaseActivity implements DetailMvpView{
public static final String EXTRA_POKEMON_NAME = "EXTRA_POKEMON_NAME";
public static Intent getStartIntent(Context context, String pokemonName) {
Intent intent = new Intent(context, DetailActivity.class);
intent.putExtra(EXTRA_POKEMON_NAME, pokemonName);
return intent;
}
}
Converted Kotlin class:
class DetailActivity : BaseActivity(), DetailMvpView {
companion object {
val EXTRA_POKEMON_NAME = "EXTRA_POKEMON_NAME"
fun getStartIntent(context: Context, pokemonName: String): Intent {
val intent = Intent(context, DetailActivity::class.java)
intent.putExtra(EXTRA_POKEMON_NAME, pokemonName)
return intent
}
}
}
public class MainActivity extends BaseActivity implements MainMvpView {
private void pokemonClicked(Pokemon pokemon) {
startActivity(DetailActivity.Companion.getStartIntent(this, pokemon))
}
}
Converted Kotlin class:
class DetailActivity : BaseActivity(), DetailMvpView {
companion object {
val EXTRA_POKEMON_NAME = "EXTRA_POKEMON_NAME"
@JvmStatic
fun getStartIntent(context: Context, pokemonName: String): Intent {
val intent = Intent(context, DetailActivity::class.java)
intent.putExtra(EXTRA_POKEMON_NAME, pokemonName)
return intent
}
}
}
public class MainActivity extends BaseActivity implements MainMvpView {
private void pokemonClicked(Pokemon pokemon) {
startActivity(DetailActivity.getStartIntent(this, pokemon))
}
}
public class MainActivity extends BaseActivity implements MainMvpView {
private void pokemonClicked(Pokemon pokemon) {
startActivity(DetailActivity.getStartIntent(this, pokemon))
}
}
Remember: *you do not need to stress about migrating the entire codebase.
Here is the Java class:
public interface DemoService {
@GET("posts")
Observable<PostResponse> getDemoResponse();
@GET("categories")
Observable<CategoryResponse> getDemoResponse2();
}
interface DemoService {
@get:GET("posts")
val demoResponse: Observable<PostResponse>
@get:GET("categories")
val demoResponse2: Observable<CategotyResponse>
}
Expecting methods demoResponse and demoResponse2, They are being interpreted as getter methods, this will cause lots of issues.
If you are using Mockito’s ArgumentCaptor you will most probably get following error
java.lang.IllegalStateException: classCaptor.capture() must not be null
If you are using Mockito’s ArgumentCaptor you will most probably get following error
java.lang.IllegalStateException: classCaptor.capture() must not be null
The return value of classCaptor.capture() is null, but the signature of SomeClass#someMethod(Class, Boolean) does not allow a null argument.
If you are using Mockito’s ArgumentCaptor you will most probably get following error
java.lang.IllegalStateException: classCaptor.capture() must not be null
The return value of classCaptor.capture() is null, but the signature of SomeClass#someMethod(Class, Boolean) does not allow a null argument.
mockito-kotlin library provides supporting functions to solve this problem
- annotationProcessor must be replaced by kapt in build.gradle
- annotationProcessor must be replaced by kapt in build.gradle
- Configure tests to mock final classes
- annotationProcessor must be replaced by kapt in build.gradle
- Configure tests to mock final classes
- If you are using android data-binding, include:
kapt com.android.databinding:compiler:3.0.0
- annotationProcessor must be replaced by kapt in build.gradle
- Configure tests to mock final classes
- If you are using android data-binding, include:
kapt com.android.databinding:compiler:3.0.0
@JvmField
to rescue while using ButterKnife@InjectView
and Espresso@Rule
-
Use val instead of var
-
Use lateinit
-
Use let function
-
User Elivis operator
- Kotlin makes you think about immutability on the language level and that’s great.
- var and val mean "writable" and "read-only"
- If you use them as immutables, you don’t have to care about nullability.
private var adapter: RecyclerAdapter<Droids>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mAdapter = RecyclerAdapter(R.layout.item_droid)
}
fun updateTransactions() {
adapter!!.notifyDataSetChanged()
}
private lateinit var adapter: RecyclerAdapter<Droids>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mAdapter = RecyclerAdapter(R.layout.item_droid)
}
fun updateTransactions() {
adapter?.notifyDataSetChanged()
}
private var photoUrl: String? = null
fun uploadClicked() {
if (photoUrl != null) {
uploadPhoto(photoUrl!!)
}
}
private var photoUrl: String? = null
fun uploadClicked() {
photoUrl?.let { uploadPhoto(it) }
}
Elvis operator is great when you have a fallback value for the null case. So you can replace this:
fun getUserName(): String {
if (mUserName != null) {
return mUserName!!
} else {
return "Anonymous"
}
}
Elvis operator is great when you have a fallback value for the null case. So you can replace this:
fun getUserName(): String {
return mUserName ?: "Anonymous"
}
Toast.makeText(context, "Hello #BlrKotlin :)", Toast.LENGTH_LONG).show()
Toast.makeText(context, "Hello #BlrKotlin :)", Toast.LENGTH_LONG).show()
/**
* Extension method to show toast for Context.
*/
fun Context?.toast(@StringRes textId: Int, duration: Int = Toast.LENGTH_LONG) =
this?.let { Toast.makeText(it, textId, duration).show() }
Toast.makeText(context, "Hello #BlrKotlin :)", Toast.LENGTH_LONG).show()
/**
* Extension method to show toast for Context.
*/
fun Context?.toast(@StringRes textId: Int, duration: Int = Toast.LENGTH_LONG) =
this?.let { Toast.makeText(it, textId, duration).show() }
Check http://kotlinextensions.com/
- Goodbye
findViewById
- Using
Parcelize
annotation for Parcelable
- Goodbye
findViewById
- Using
Parcelize
annotation for Parcelable
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/welcomeMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Hello World!"/>
</FrameLayout>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
TextView welcomeMessageView = findViewById(R.id.welcomeMessage),
welcomeMessageView.text = "Hello BlrKotlin!"
}
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main.*
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
welcomeMessage.text = "Hello BlrKotlin!"
}
Here is the Java Class:
public class MyParcelable implements Parcelable {
private int mData;
public int describeContents() { return 0; }
public void writeToParcel(Parcel out, int flags) { out.writeInt(mData); }
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) { return new MyParcelable(in); }
public MyParcelable[] newArray(int size) { return new MyParcelable[size];}
};
private MyParcelable(Parcel in) { mData = in.readInt(); }
}
Converted Kotlin Class:
data class MyParcelable(var data: Int): Parcelable {
override fun describeContents() = 1
override fun writeToParcel(dest: Parcel, flags: Int) { dest.writeInt(data)}
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<MyParcelable> {
override fun createFromParcel(source: Parcel): MyParcelable {
val data = source.readInt(); return MyParcelable(data)
}
override fun newArray(size: Int) = arrayOfNulls<MyParcelable>(size)
}
}
}
To use @Parcelize
we need to set experimental flag in build.gradle
androidExtensions {
experimental = true
}
To use @Parcelize
we need to set experimental flag in build.gradle
androidExtensions {
experimental = true
}
@Parcelize
class MyParcelable(val data: Int): Parcelable
Don’t try to learn the whole language at once
- Everything to start writing tests for Android App.
- 75% discount on my upcoming book use BLRKOTLIN
- https://leanpub.com/android-testing/c/BLRKOTLIN