1. 程式人生 > >Converting Plaid to Kotlin: Lessons learned (Part 1)

Converting Plaid to Kotlin: Lessons learned (Part 1)

People often ask me about the advantages of writing Android Apps using Kotlin. The thing is that I never directly converted an Android App from Java to Kotlin, so it’s always a difficult question to answer. Explaining a lot of abstract features without putting them into context is not always the best way to tell about the goodness of a language.

So, after testing Plaid, the app developed by Nick Butcher, and being marvelled about the awesome look and transitions of the App, I wanted to know more about it. And what a better way than rewriting the App into Kotlin?

I just converted HomeActivity and wanted to compare the resulting code, what things are drastically improved and, of course, give you access to the code so that you can extract your own conclusions. My first disclaimer is that, though it may happen or not, my main goal is not converting the whole App to Kotlin. It’s quite big, so not sure if I’ll find the time (or the need) to do it.

View Binding

Nick decided to use Butterknife to recover views, which is an excellent solution for Java code, but Kotlin provides Kotlin Android Extensions, which automatically binds them to the activity. So that way, we are saving all the @Bind lines.

We have a couple of extra things, however, that Butterknife provides, such as onClick

and resources binding. For the first one, it’s so simple in Kotlin it doesn’t really add much boilerplate. In onCreate we do:

fab.onClick { fabClick() }

I’m using an Anko function here, but using setOnClickListener will be as easy.

Regarding the columns value that is recovered, it’s only necessary in onCreate, so I moved the declaration there, but a similar thing can be achieved with property delegation:

private val columns by lazy { resources.getInteger(R.integer.num_columns) }

The lazy delegate delays the value assignment until the property is called, when the activity is already instantiated and we have access to resources.

Want to learn Kotlin?

Check my free guide to create your first project in 15 minutes!

Properties declaration

In Java, we have to delay the fields assignment until our activity is ready. But properties in Kotlin need a value since the very beginning if we don’t want to deal with unnecessary nulls and mutable variables. So it’s very common to do the assignment directly in the declaration.

Once more, we have the problem of needing a context for many of these properties. So lazy will be really useful here:

private val dribbblePrefs by lazy { DribbblePrefs.get(ctx) }
private val designerNewsPrefs by lazy { DesignerNewsPrefs.get(ctx) }

Of course, these declarations can be as complex as we need. DataManager for instance, needs to extend a class and override a method:

private val dataManager by lazy {
    object : DataManager(this, filtersAdapter) {
        override fun onDataLoaded(data: MutableList<out PlaidItem>?) {
            feedAdapter.addAndResort(data)
            checkEmptyState()
        }
    }
}

This way, we can see how items are declared just in the declaration section, instead of having to do it in the middle of onCreate. And as plus, we make sure that by the time we use them, they won’t be null, preventing unnecessary NullPointerExceptions.

Kotlin for Android Developers

Use of standard functions

Kotlin standard library provides a good set of functions that are really useful. You can check part 1 and part 2 of Cedric’s articles about Standard Library.

We, for instance, have the apply() function, which works as an extension function for the object that calls it, and returns the same object. A complete example of that is the way I inflate the no_filters ViewStub. First, it is done lazy so that the stub is not inflated until it is called, and second, it applies an initialisation over the result of this inflation:

private val noFilterEmptyText by lazy {
    // create the no filters empty text
    (stub_no_filters.inflate() as TextView).apply {
        ...
        onClick { drawer.openDrawer(GravityCompat.END) }
    }
}

As you can see, the function is applied over the result of the inflation, and apply() will return that same object, which is assigned to noFilterEmptyText. Another good example is just inside that code. A SpannableStringBuilder is a perfect candidate for this:

text = SpannableStringBuilder(emptyText).apply {
    // show an image of the filter icon
    setSpan(ImageSpan(ctx, R.drawable.ic_filter_small, ImageSpan.ALIGN_BASELINE),
            filterPlaceholderStart,
            filterPlaceholderStart + 1,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    // make the alt method (swipe from right) less prominent and italic
    setSpan(ForegroundColorSpan(
            ContextCompat.getColor(ctx, R.color.text_secondary_light)),
            altMethodStart,
            emptyText.length,
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    setSpan(StyleSpan(Typeface.ITALIC),
            altMethodStart,
            emptyText.length,
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}

The apply() function is also useful to do initialisations for our views. First, it visibly divides the code in blocks, which makes it easier to read. And second, the code is executed as if it was inside the class, so we can use all the public methods of the object without prepending the name of the object.

stories_grid.apply {
    adapter = feedAdapter
    val columns = resources.getInteger(R.integer.num_columns)
    val gridManager = GridLayoutManager(ctx, columns).apply {
        setSpanSizeLookup { pos -> if (pos == feedAdapter.dataItemCount) columns else 1 }
    }
    layoutManager = gridManager
    addOnScrollListener { recycler, dx, dy ->
        gridScrollY += dy
        if (gridScrollY > 0 && toolbar.translationZ != -1f) {
            toolbar.translationZ = -1f
        } else if (gridScrollY == 0 && toolbar.translationZ != 0f) {
            toolbar.translationZ = 0f
        }
    }
    addOnScrollListener(object : InfiniteScrollListener(gridManager, dataManager) {
        override fun onLoadMore() = dataManager.loadAllDataSources()
    })
    setHasFixedSize(true)
}

Here, the adapter, layout manager and listeners are added to the RecyclerView. You can also see the use of the synthetic properties that are generated for Java getters and setters. Instead of doing setLayoutManager(gridManager), we can just do layoutManager = gridManager.

Lambdas

Though the use of functions is everywhere, there are some evident simplifications here and there. We can use some calls by passing lambdas instead of creating the objects Java would need. A very nice example is the closeDrawerRunnable. This is the code you need in Java:

final Runnable closeDrawerRunnable = new Runnable() {
    @Override
    public void run() {
        drawer.closeDrawer(GravityCompat.END);
    }
};
    ...
drawer.postDelayed(closeDrawerRunnable, 2000);
};

And the same in Kotlin:

val closeDrawerRunnable = { drawer.closeDrawer(GravityCompat.END) }
...
drawer.postDelayed(closeDrawerRunnable, 2000)

We also saw the example of onClick before, and it also helps with setOnApplyWindowInsetsListener:

drawer.setOnApplyWindowInsetsListener { v, insets ->
    ...
}

4 Dealing with nullity

That’s another great feature of Kotlin, and improves the way we deal with nullity, and the amount of code we need for that. For instance in animateToolbar method, this is what we have in Java, to ensure we are dealing with a non-null TextView:

View t = toolbar.getChildAt(0);
if (t != null && t instanceof TextView) {
    TextView title = (TextView) t;
    ...
}

We can do this in Kotlin:

val title = toolbar.getChildAt(0) as? TextView
title?.apply {
    ...
}

With the advantage that now the code inside apply behaves as an extension function, so you don’t need to write title anymore. The first line tries to convert whatever is returned into a TextView. If the child is null or is not a TextView, title will be null. The second line would behave the same as if (title != null) title.apply { }. The apply() function is only executed if title is not null.

You can find many more improvements throughout the code of the Activity. It’s not very beautiful because this activity is dealing with too much (there is even an instantiation of a Retrofit client around), but it’s a good start to understand how different developing with Kotlin is.

Kotlin vs Java: Numbers

Finally, I want to share some numbers, of course as inaccurate as all external factors can generate, but it could help us get an idea. Not everything is good in Kotlin, compilation times for instance are a thing that needs to be improved.

KotlinJavaComparison

Line count 576 702 -22% Character count 24001 30589 -27% Clean compilation 1m 40s 1m 5s +67% Compilation after 1 line change 29s 10s +190% APK size 4.7MB 4.1MB +14% Method count 41615 30129 +38%

The main problem Kotlin compiler has right now is that it can’t do partial compilations, so if we change one line, it needs to recompile all the classes. These things will probably change in the future, but that’s what we have now.

As you can see, Kotlin + Anko are adding around 11000 methods. The Anko library is quite huge (3000+ methods) and we could think of just creating our own functions if we are not using its core. Here it is the comparison:

apk_method_count

Conclusion

Programming with Kotlin is delightful. You can achieve a lot more with less amount of code. This example could be improved a lot more if the whole App was done using Kotlin, because we could get rid of more boilerplate. But it’s a good way to understand some parts where Kotlin help us to improve readability and save some code.

While I continue converting the App to Kotlin code, I’ll probably find some more interesting things to tell. So stay tuned for new articles!. In the meanwhile, you can continue learning Kotlin with my book and the rest of articles about Kotlin. And of course, you can review the complete HomeActivity.

I’m in love with Kotlin. I’ve been learning about it for a couple of years, applying it to Android and digesting all this knowledge so that you can learn it with no effort.

Shares

Like this:

Like Loading...

相關推薦

Converting Plaid to Kotlin: Lessons learned (Part 1)

People often ask me about the advantages of writing Android Apps using Kotlin. The thing is that I never directly converted an Android App from Java

Converting Plaid to Kotlin: Lessons learned (Part 2)

We saw pretty amazing improvements in the first article thanks to the use of Kotlin in an activity. But the thing is that these kind of classes are n

[Testing] Config jest to test Javascript Application -- Part 1

Transpile Modules with Babel in Jest Tests Jest automatically loads and applies our babel configuration. However, because our project takes advantage of t

Engineering to Improve Marketing Effectiveness (Part 1)

Engineering to Improve Marketing Effectiveness (Part 1)“Make people so excited about our content that they sign up and watch”- Kelly Bennett, Netflix Chief

Many-to-many relationships in EF Core 2.0 – Part 1: The basics

ati pda 希望 pairs prot pair protect simple tid 轉載這個系列的文章,主要是因為EF Core 2.0在映射數據庫的多對多關系時,並不像老的EntityFramework那樣有原生的方法進行支持,希望微軟在以後EF Core的版本中

Brian2學習教程——Intro to Brian part 1: Neurons

參考原英文教程網址:https://brian2.readthedocs.io/en/stable/resources/tutorials/1-intro-to-brian-neurons.html 推薦一個不錯的中文翻譯教程:https://blog.csdn.net/u013751642/a

Recurrent Neural Networks Tutorial, Part 1 – Introduction to RNNs學習筆記

介紹-什麼是RNN 1.RNN的主要思想是利用序列資訊。 The idea behind RNNs is to make use of sequential information. In a traditional neural network we assu

Seq2seq pay Attention to Self Attention: Part 1

Attention Model has been a rising star and a powerful model in the deep learning in recent years. Especially the concept of self attention proposed by Goog

How to Handle Context with Dialogflow (Part 1: Knock Knock Jokes)

Step 1: Build a trigger for the bot to say “Knock Knock”To get the bot to start the “knock knock” joke, you have to trigger it. In this example, we’ll use

Using TensorFlow.js to Automate the Chrome Dinosaur Game (part 1)

In this blog post, we’ll be learning how to automate the Chrome Dinosaur Game using the neural networks with TensorFlow.js. If you haven’t played it before

An introduction to pmemobj (part 1) - accessing the persistent memory

參考連結:https://pmem.io/2015/06/13/accessing-pmem.html 這篇文章主要還是純C語言有關 全文API函式: pmemobj_creat() //建立持久化記憶體池 pmemobj_open() //開啟已建立的持久化記憶體池 pmemo

How to create role based accounts for your Saas App using FEAN? (Part 1)

Setup firebase in your angular app and express js// Front-endng new exampleAppcd exampleApp && cd exampleApp// For adding firebase to angular appng

Intro to Fintech Concepts, part 1

About this Tech Talk The first talk in a series where we'll explore different types of financial securities, and provide foundational

Coming September 27: Introduction to Eclipse Microprofile, part 1

Add "Introduction to Eclipse Microprofile, part 1" to your calendar Thursday Sept 27, 1pm – 1:30 EDT About Introduction to Eclipse M

Part 1: Build an Action to make Google Assistant find the food you are craving for.

Part 1: Build an Action to make Google Assistant find the food you are craving for!Actions on Google is a developer platform that lets you create applicati

Creating visualizations to better understand your data and models (Part 1)

The Cancer Genome Atlas Breast Cancer DatasetThe Cancer Genome Atlas (TCGA) breast cancer RNA-Seq dataset (I’m using an old freeze from 2015) has 20,532 fe

[Tutorial, Part 1] How to develop Go gRPC microservice with HTTP/REST endpoint, middleware…

[Tutorial, Part 1] How to develop Go gRPC microservice with HTTP/REST endpoint, middleware, Kubernetes deployment, etc.There are a lot of article how to cr

AI Newsletters You Should Subscribe To (Part 1/3: Research & Nonprofits)

Founded in 2017 by students and researchers at the Stanford Artificial Intelligence Laboratory (SAIL), The Gradient democratises research in AI and machine

Packaging Python Project to Debian .deb Part 1

Packaging Python Project to Debian .deb Part 1Source: Debian PackagingHi, Have you ever try to package your code/project to became .deb or in official Debi

Growing Our SaaS Company To $1M+ ARR: 7 People, 3 Years, No VC Money. Key Lessons Learned

We started in 2010 as a small outsourcing company — Weavora. The idea of ​​the product was born here from our internal needs. We needed to report and invoi