With the release of Android back in 2008, the go-to language for app development has always been Java – however, that’s beginning to change. At Google IO 2017, it was announced that Android would be receiving “first class support” for Kotlin. Since then, the language has been slowly ramping up to becoming the preferred language for development on the Android platform.
In the following article I’m going to give you some ways you can refactor/rewrite your existing Java app code to Kotlin, while also pointing out the advantages Kotlin gives us.
But what about Java?
I still feel like Java is worth knowing, since a lot of the Android APIs, as well as their documentation, are written in it. If you’re just starting Android development, I would reccommend developing at least an intermediate level of Java knowledge before moving on to Kotlin. If you’d like an in-depth guide to getting started with Android development, you can read an article I wrote detailing how to become an Android developer in under a year.
I should also mention that Kotlin is interoperable with Java. For this reason, you aren’t forced to choose one or the other. You’re able to call Java code with Kotlin and vise versa. So, don’t feel like you’re wasting time just because you’re learning Java.
Refactoring Java to Kotlin
I’m going to present some Java classes from my own personal projects, with the Kotlin counterpart following. The advantages Kotlin presents should be evident from simply looking at the code, but I’ll be sure to go through each.
SharedPreferences
public class SharedPrefsManagerImpl implements SharedPrefsManager {
private static final String KEY_SOUND_ENABLED = "key_sound_enabled";
private static final String KEY_SECOND_COUNT = "key_second_count";
private final SharedPreferences preferences;
public SharedPrefsManagerImpl(Context context) {
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
}
@Override
public boolean isSoundEnabled() {
return preferences.getBoolean(KEY_SOUND_ENABLED, true);
}
@Override
public void setSoundEnabled(boolean enabled) {
preferences.edit().putBoolean(KEY_SOUND_ENABLED, enabled).apply();
}
@Override
public int getStartingCountdownSeconds() {
return preferences.getInt(KEY_SECOND_COUNT, 30);
}
@Override
public void changeStartingCountdown() {
switch (getStartingCountdownSeconds()){
case 30:
setSavedSecondCount(10);
break;
case 10:
setSavedSecondCount(5);
break;
case 5:
setSavedSecondCount(30);
break;
}
}
private void setSavedSecondCount(int secondCount){
preferences.edit().putInt(KEY_SECOND_COUNT, secondCount).apply();
}
}
Here’s an implementation of an interface used for managing a few SharedPreferences values. The class is quite straight-forward, as far as preference managers go. Let’s take a look at the same class in Kotlin.
class SharedPrefsManagerImpl(context: Context?) : SharedPrefsManager {
companion object {
private const val KEY_SOUND_ENABLED = "key_sound_enabled"
private const val KEY_SECOND_COUNT = "key_second_count"
}
private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
override var isSoundEnabled: Boolean
get() = preferences.getBoolean(KEY_SOUND_ENABLED, true)
set(value) = preferences.edit { putBoolean(KEY_SOUND_ENABLED, value) }
override var savedCountdownSeconds: Int
get() = preferences.getInt(KEY_SECOND_COUNT, 30)
set(value) = preferences.edit { putInt(KEY_SECOND_COUNT, value)}
override fun changeStartingCountdown() {
when (savedCountdownSeconds) {
30 -> savedCountdownSeconds = 10
10 -> savedCountdownSeconds = 5
5 -> savedCountdownSeconds = 30
}
}
}
Immediately, you can already see the amount of code we need is less than that of the Java class. Not only is it less code, but it is much more concise and easier to read. You will notice we also take advantage of a few Kotlin specific features. Since we’re allowed to define our own custom setters and getters directly on class properties, we can go ahead and remove the individual set and get methods that we had in our Java code. In our changeStartingCountdown() function, we use a when expression – it is the Kotlin equivalent of a switch statement. Furthermore, we use a companion object to define our constants in. You can read more on companion objects here. Finally, we take advantage of an extension function when calling edit() on our SharedPreferences. In my project, this function comes from the Android ktx library.
Fragment
public class PressButtonFragment extends PressButtonFragmentBase {
@BindView(R.id.b_red_button)
Button bRedButton;
@BindView(R.id.tv_timer)
TextView tvTimer;
@BindView(R.id.tv_press_count)
TextView tvPressCount;
@BindView(R.id.tv_gameover_amt)
TextView tvGameoverAmt;
@BindView(R.id.gameover_view)
ConstraintLayout gameoverView;
@BindView(R.id.main_view)
ConstraintLayout mainView;
@BindView(R.id.tv_gameover_avg)
TextView tvGameoverAvg;
Unbinder unbinder;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_press_button, container, false);
unbinder = ButterKnife.bind(this, view);
return view;
}
@Override
public PressButtonContract.Actions createPresenter() {
FragmentComponent component = DaggerFragmentComponent
.builder()
.schedulerProviderModule(new SchedulerProviderModule(new BaseSchedulerProvider()))
.contextModule(new ContextModule(getActivity().getApplicationContext()))
.build();
PressButtonPresenter pressButtonPresenter = new PressButtonPresenter();
component.injectPressButtonPresenter(pressButtonPresenter);
return pressButtonPresenter;
}
@Override
public void onNewViewStateInstance() {
Timber.d("new viewstate");
presenter.reset();
}
@Override
public void showMainView() {
hideGameOverView();
viewState.setPressAmt(0);
tvPressCount.setText(String.valueOf(0));
}
@Override
public void setStartingTime(int milliseconds) {
viewState.setStartingTime(milliseconds / 1000);
tvTimer.setText(Util.formatTime(milliseconds));
}
@Override
public void decrementTime(int milliseconds) {
tvTimer.setText(Util.formatTime(milliseconds));
}
@Override
public void incrementPress(int presses) {
viewState.setPressAmt(presses);
tvPressCount.setText(String.valueOf(presses));
}
@Override
public void setGameOverMsg(int pressAmt, int initial) {
showGameOverView();
tvGameoverAmt.setText(getString(R.string.gameover_pressed_amt, String.valueOf(pressAmt)));
tvGameoverAvg.setText(getString(R.string.gameover_pressed_average, Util.getAverage(pressAmt, initial)));
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@OnClick({R.id.b_try_again, R.id.b_red_button, R.id.tv_timer})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.b_try_again:
presenter.reset();
break;
case R.id.b_red_button:
presenter.incrementPress();
break;
case R.id.tv_timer:
presenter.changeInitialTime();
break;
}
}
@Override
public void hideGameOverView() {
mainView.setVisibility(View.VISIBLE);
gameoverView.setVisibility(View.GONE);
}
@Override
public void showGameOverView() {
mainView.setVisibility(View.GONE);
gameoverView.setVisibility(View.VISIBLE);
}
}
Here we have a standard Fragment class with some UI related methods. At the top of the class, you will notice the standard Butterknife annotations used to remove a lot of the findViewById() boilerplate code. I’ve also delegated all my view clicks to a single method, onViewClicked() which then calls the correct method depending on the id of the view. At the time I wrote this class, lambdas, without the use of a 3rd party library, were still unsupported in Android Studio (pre 3.0).
The biggest difference in our Kotlin equivalent class should be immediately apparent. Where are we defining and initializing our views? Thanks to the Kotlin Android Extensions plugin, we no longer have to do that. With synthetic properties, we can access our views by the exact id we give them in our layouts. The plugin is taking care of inflating and caching our views (for configuration changes) under the hood. You may also notice I’ve removed the onViewClicked() method. Instead of a switch statement, I am now explicitly calling the presenter methods in lambdas. However, they look a bit strange don’t they? In Kotlin, if we are writing a lambda that takes one parameter, we can completely omit the use of -> – as you can see in the setOnClickListener() methods.
Model Objects
public class ServerModel implements Comparable<ServerModel> {
@SerializedName("id")
@Expose
private int id;
@SerializedName("name")
@Expose
private String name;
@SerializedName("population")
@Expose
private String population;
private int populationLevel;
public ServerModel(int id, String name, String population) {
this.id = id;
this.name = name;
this.population = population;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPopulation() {
return population;
}
public void setPopulation(String population) {
this.population = population;
}
public void setPopulationLevel(int level){
this.populationLevel = level;
}
public int getPopulationLevel(){
switch (population){
case "Low":
return 0;
case "Medium":
return 1;
case "High":
return 2;
case "VeryHigh":
return 3;
case "Full":
return 4;
default:
return 0;
}
}
public int getPopulationColor(){
switch (population){
case "Low":
return Color.parseColor("#C6E0B4");
case "Medium":
return Color.parseColor("#A9D08E");
case "High":
return Color.parseColor("#FFC000");
case "VeryHigh":
return Color.parseColor("#ED7D31");
case "Full":
return Color.parseColor("#FF0000");
default:
return Color.parseColor("#C6E0B4");
}
}
public String getIdString(){
return String.valueOf(this.id);
}
@Override
public int compareTo(@NonNull ServerModel serverModel) {
return serverModel.getPopulationLevel() - this.getPopulationLevel();
}
public static ServerModel getFakeServer(){
return new ServerModel(9999, "TestServer", "null");
}
}
Here’s a standard model class I wrote in an older project. Notice the explicit use of writing our setters and getters in the Java code. Let’s see the Kotlin equivalent.
data class ServerModel(@field:SerializedName("id")
@field:Expose
val id: Int,
@field:SerializedName("name")
@field:Expose
val name: String,
@field:SerializedName("population")
@field:Expose
val population: String) : Comparable<ServerModel> {
private val populationLevel: Int
get() {
return when (population) {
"Low" -> 0
"Medium" -> 1
"High" -> 2
"VeryHigh" -> 3
"Full" -> 4
else -> 0
}
}
val populationColor: Int
get() {
return when (population) {
"Low" -> Color.parseColor("#C6E0B4")
"Medium" -> Color.parseColor("#A9D08E")
"High" -> Color.parseColor("#FFC000")
"VeryHigh" -> Color.parseColor("#ED7D31")
"Full" -> Color.parseColor("#FF0000")
else -> Color.parseColor("#C6E0B4")
}
}
val idString: String
get() = this.id.toString()
override fun compareTo(other: ServerModel): Int {
return other.populationLevel - this.populationLevel
}
companion object {
val fakeServer: ServerModel
get() = ServerModel(9999, "TestServer", "null")
}
}
As standard in Kotlin, we can omit the use of set and get methods. However, we still define them for custom behaviour as can be seen in populationColor and populationLevel. We also mark this class as a data class in Kotlin. Doing so provides our class with a number of features which can be seen here. It is worth noting that the use of the data keyword should only be reserved for model objects.
Wrapping up
If you’ve been on the fence with switching to Kotlin – I’d highly suggest you to give it a try. The language provides many great advantages over Java that make our lives as developers much easier. A good way to learn would be to refactor your older Java projects. I’d advise you start with the less complex classes and work your way up to the more involved ones such as Fragments, Activities or any code related fetching network data.