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

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 not the best candidates to notice the benefits because they are mostly overriding methods and doing a couple of things, which it still inevitably needs some boilerplate.

I’ve continued porting the App to Kotlin (you can see all the changes in the repo), and some interesting things are arising. I want to focus today in the transformation of a specific class: DataManager. One spoiler, the size of the class has been reduced from 422 lines to 177. And, in my opinion, is much easier to understand.

When

The first big thing we see when taking a look at the class is a huge if/else inside loadSource class. This could have been a switch in first instance, which could have improved readability, but it would’ve kept being a little hard to understand anyway. In kotlin, we can use when expression, which is the most similar thing to switch

in Java, but much more powerful. Conditions can be as complex as we need, and are always a good substitute for if/else. In this case we are using one of its simplest versions, but it helps this method look better:

when (source.key) {
    SourceManager.SOURCE_DESIGNER_NEWS_POPULAR -> loadDesignerNewsTopStories(page)
    SourceManager.SOURCE_DESIGNER_NEWS_RECENT -> loadDesignerNewsRecent(page)
    SourceManager.SOURCE_DRIBBBLE_POPULAR -> loadDribbblePopular(page)
    SourceManager.SOURCE_DRIBBBLE_FOLLOWING -> loadDribbbleFollowing(page)
    SourceManager.SOURCE_DRIBBBLE_USER_LIKES -> loadDribbbleUserLikes(page)
    SourceManager.SOURCE_DRIBBBLE_USER_SHOTS -> loadDribbbleUserShots(page)
    SourceManager.SOURCE_DRIBBBLE_RECENT -> loadDribbbleRecent(page)
    SourceManager.SOURCE_DRIBBBLE_DEBUTS -> loadDribbbleDebuts(page)
    SourceManager.SOURCE_DRIBBBLE_ANIMATED -> loadDribbbleAnimated(page)
    SourceManager.SOURCE_PRODUCT_HUNT -> loadProductHunt(page)
    else -> when (source) {
        is Source.DribbbleSearchSource -> loadDribbbleSearch(source, page)
        is Source.DesignerNewsSearchSource -> loadDesignerNewsSearch(source, page)

    }
}

Though this is not the case, when is an expression, so it can return a value, which can be really useful.

Dealing with maps

Though this is not an example of a huge line reduction, it is interesting to see how we can deal with maps in Kotlin. This is how getNextPageIndex looks in Java:

private int getNextPageIndex(String dataSource) {
    int nextPage = 1; // default to one – i.e. for newly added sources
    if (pageIndexes.containsKey(dataSource)) {
        nextPage = pageIndexes.get(dataSource) + 1;
    }
    pageIndexes.put(dataSource, nextPage);
    return nextPage;
}

How this looks in Kotlin?

private fun getNextPageIndex(dataSource: String): Int {
    val nextPage = 1 + pageIndexes.getOrElse(dataSource) { 0 }
    pageIndexes += dataSource to nextPage
    return nextPage
}

We have a couple of interesting things here. The first one, is the function getOrElse, that allows to return a default value if an element is not found in the map:

val nextPage = 1 + pageIndexes.getOrElse(dataSource) { 0 }

Thanks to this, we save the condition that checks it. But another even more interesting thing is how we add a new item to the map. Maps in Kotlin have implemented + operator, so we can add a new item just doing map = map + Pair, which is the same as map += Pair. This is how it would be:

Want to learn Kotlin?

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

pageIndexes += Pair(dataSource, nextPage)

But as I probably told before, we can make use of to, an infix function that returns a Pair. So the previous line is the same as:

pageIndexes += dataSource to nextPage
Kotlin for Android Developers

Converting callbacks into lambdas

Here it is where the magic really happens. There is a lot of repetitive code in loader classes. Most of them have the same structure that is duplicated once and another in all the calls to the API. First, a Retrofit callback is created. If the request is successful, it extracts the necessary information from the result and does whatever it needs with it. It finally calls the “data loaded” listener. In any case (success or failure), the loadingCount is updated.

Just an example:

private void loadDesignerNewsTopStories(final int page) {
    getDesignerNewsApi().getTopStories(page, new Callback<StoriesResponse>() {
        @Override
        public void success(StoriesResponse storiesResponse, Response response) {
            if (storiesResponse != null
                    && sourceIsEnabled(SourceManager.SOURCE_DESIGNER_NEWS_POPULAR)) {
                setPage(storiesResponse.stories, page);
                setDataSource(storiesResponse.stories,
                        SourceManager.SOURCE_DESIGNER_NEWS_POPULAR);
                onDataLoaded(storiesResponse.stories);
            }
            loadingCount.decrementAndGet();
        }

        @Override
        public void failure(RetrofitError error) {
            loadingCount.decrementAndGet();
        }
    });
}

This code is rather difficult to understand if it’s not analyzed. We can create a callback function that creates the retrofit callback and implements the structure of the previous callback. The only different thing it does is to extract the necessary info from the result:

private inline fun <T> callback(page: Int, sourceKey: String, 
            crossinline extract: (T) -> List<PlaidItem>)
        = retrofitCallback<T> { result, response ->
    if (sourceIsEnabled(sourceKey)) {
        val items = extract(result)
        setPage(items, page)
        setDataSource(items, sourceKey)
        onDataLoaded(items)
    }
}

It does exactly the same: checks if the source is enabled, and if it is, it will extract the items from the result, call setPage, setDataSource and onDataLoaded. The implementation of retrofitCallback is just to make previous function simpler, but it could be omitted:

private inline fun <T> retrofitCallback(crossinline code: (T, Response) -> Unit): Callback<T> 
        = object : Callback<T> {
    override fun success(t: T?, response: Response) {
        t?.let { code(it, response) }
        loadingCount.decrementAndGet()
    }

    override fun failure(error: RetrofitError) {
        loadingCount.decrementAndGet()
    }
}

The inline modifier is a way to optimize functions that have other functions as parameters. When a function includes a lambda, the translation is equivalent to an object which has a function that implements that code. If we use inline, however, the lambda will be substituted by its code whenever it’s called. If the lambda returns a value, we also have to use crossinline. Check Kotlin reference for more info.

Both functions are generic so that they can accept any required type. With this, the previous request looks like this:

    private fun loadDesignerNewsTopStories(page: Int) {
        designerNewsApi.getTopStories(page,
                callback(page, SourceManager.SOURCE_DESIGNER_NEWS_POPULAR) { it.stories })
    }

The function calls to getTopStories, and creates a callback that receives the page, the key for the source, and a function that gets the stories from the result. The similar structure works for the rest of calls, but they can do whatever they need with the result. For instance, there’s another response that needs to be modified to include the user:

private fun loadDribbbleUserShots(page: Int) = dribbbleLogged {
    val user = dribbblePrefs.user
    dribbbleApi.getUserShots(page, DribbbleService.PER_PAGE_DEFAULT,
            callback(page, SourceManager.SOURCE_DRIBBBLE_USER_SHOTS) {
                // this api call doesn't populate the shot user field but we need it
                it.apply { forEach { it.user = user } }
            })
}

As you can see, the previous one also requires to be logged to dribbble. dribbbleLogged will be the function in charge of checking that and doing something else if it isn’t.

private inline fun dribbbleLogged(code: () -> Unit) {
    if (dribbblePrefs.isLoggedIn) {
        code()
    } else {
        loadingCount.decrementAndGet()
    }
}

Conclusion

This part has shown the power of lambdas and the use of functions as first class citizens. The reduction of code is huge (238% smaller), but that’s not the most important improvement. The code is now much easier to read and much less prone to errors. Don’t forget to take a look at the latest commits in my Plaid fork at Github.

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 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

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

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

Setup an afterEach Test Hook for all tests with Jest setupTestFrameworkScriptFile With our current test situation, we have a commonality between most of o

The Hitchhiker’s Guide to Waves Smart Contracts. Part 2

The Hitchhiker’s Guide to Waves Smart Contracts. Part 2On September 10 Waves Platform released a new version of node, which has the support of Smart Contra

What to expect from PMs, part 2

What Should Designers Expect from PMs? Part 2Q: I argue with my PM all the time. He works well with everyone else on the team, and the engineers and analys

Brian2學習教程——Intro to Brian part 2: Synapses【補充】STDP

前言: 在學習brian2中,最後一部分歷程模擬了STDP突觸模型。在閱讀相關文獻中瞭解到脈衝時間依賴可塑性(STDP,Spike Timing Dependent Plasticity)屬於一種非監督學習機制。 最初Hebbian提出: 當兩個位置上臨近的神經

AI Newsletters You Should Subscribe To (Part 2/3: Individuals)

A weekly newsletter curating AI and deep learning, published by Waikit Lau and Arthur Chan, whose background spans MIT, CMU, Bessemer Venture Partners, Nua

Seq2seq pay Attention to Self Attention: Part 2

We have talked about Seq2seq and Attention model in the first part. In this part, I will be focusing on Self attention, proposed by Google in the paper “At

How to use Context with Dialogflow to handle errors (Part 2: Knock Knock, It’s me)

How to use Contextual Fallback with Dialogflow to handle errors (Part 2: Knock Knock Jokes)(This is Part 2 of a four-part series on how to use Context with

An introduction to pmemobj (part 2) - transactions

參考連結:https://pmem.io/2015/06/15/transactions.html API 函式: pmemobj_tx_process() //用來移動事務 pmemobj_tx_abort() //狀態跳轉 pmemobj_tx_add_rang(root obj

From Scikit-learn to TensorFlow: Part 2

Continuing from where we left, we delve deeper into how to develop machine learning (ML) algorithms using TensorFlow from a scikit-learn developer's perspe

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

6 Ways to Monetize Your Ethereum DApps (Part 2)

6 Ways to Monetize Your Ethereum DApps (Part 2)In Part 1 of this 2-article series we had a theoretical approach towards monetizing your Ethereum DApps. In

Introduction to Web Shells – Part 2(Web-shells using PHP)

Web shells exist for almost every web programming language you can think of. We chose to focus on PHP because it is the most widely-used programming l

【譯】Introduction to Byteball — Part 2: The DAG

這是關於Byteball的系列文章的第2部分。 寧可觀看視訊,而不要讀故事? 然後觀看下面的YouTube連結。 如果不是,請繼續閱讀。藉助Byteball,您可以制定安全合同,並按照約定執行。 即使對陌生人來說,你也不必信任任何人。 它適用於其他方法無效的情況。 即使他們工

Lesson 2 Building your first web page: Part 2

examples pear reads port example eth span contain animation Tag Diagram You may have noticed that HTML tags come in pairs; HTML has bot

如何使用GitLab和Rancher構建CI/CD流水線 – Part 2

docker 鏡像 gitlab 配置 持續集成 部署 這是我們使用GitLab和Rancher構建CI/CD流水線系列教程的第二部分。第一部分的內容介紹了如何部署、配置和確保GitLab在Rancher的運行。這一部分中,我們將介紹如何使用GitLab CI Multi-Runner

JSP復習(part 2

war setattr current ren 客戶端 代碼 body 練習 params 3.4.2 訪問(獲取)請求參數 1.方法 String 字符串變量 =request.getParamete

Converting HTML to PDF with pdfHTML

tag add sta leo spec gin html4 padding pos https://itextpdf.com/itext7/pdfHTML pdfHTML 的一個例子 一個基本的例子將顯示使用 pdfHTML。為此, 我

逆向破解H.Koenig 遙控器 Part 2

sda com fec 逆向 title width 優化 數據 http 逆向破解H.Koenig 遙控器 Part 2 到目前為止,我們已經知道了RF收發器的安裝過程,下面是我們所掌握的東西 : l 無線電收發器是一個Avantcom A7105 l 調制是FS