Home > Software engineering >  Kotlin Companion Object - Init Block - Typealias
Kotlin Companion Object - Init Block - Typealias

Time:10-03

I'm trying to solve a Kotlin problem and it has a mixed OOP structure so I can't solve it, sorry if this question is basic. I'm coming from Java so I'm newbie for Kotlin.

I have DataSource.kt and it has DataSource, Person, FetchResponse and FetchError classes itself. And the thing which I don't understand is FetchCompletionHandler typealias

import android.os.Handler
import android.os.Looper
import kotlin.collections.ArrayList
import kotlin.math.min
import kotlin.random.Random


data class Person(val id: Int, val fullName: String)

data class FetchResponse(val people: List<Person>, val next: String?)

data class FetchError(val errorDescription: String)

typealias FetchCompletionHandler = (FetchResponse?, FetchError?) -> Unit

private data class ProcessResult(val fetchResponse: FetchResponse?, val fetchError: FetchError?, val waitTime: Double)

class DataSource {
    companion object {
        private var people: List<Person> = listOf()
    }

    private object Constants {
        val peopleCountRange: ClosedRange<Int> = 100..200 // lower bound must be > 0, upper bound must be > lower bound
        val fetchCountRange: ClosedRange<Int> = 5..20 // lower bound must be > 0, upper bound must be > lower bound
        val lowWaitTimeRange: ClosedRange<Double> = 0.0..0.3 // lower bound must be >= 0.0, upper bound must be > lower bound
        val highWaitTimeRange: ClosedRange<Double> = 1.0..2.0 // lower bound must be >= 0.0, upper bound must be > lower bound
        const val errorProbability = 0.05 // must be > 0.0
        const val backendBugTriggerProbability = 0.05 // must be > 0.0
        const val emptyFirstResultsProbability = 0.1 // must be > 0.0
    }

    init {
        initializeData()
    }

    public fun fetch(next: String?, completionHandler: FetchCompletionHandler) {
        val processResult = processRequest(next)

        Handler(Looper.getMainLooper()).postDelayed({
            completionHandler(processResult.fetchResponse, processResult.fetchError)
        },(processResult.waitTime * 1000).toLong())
    }

    private fun initializeData() {
        if (people.isNotEmpty()) {
            return
        }
        val newPeople: ArrayList<Person> = arrayListOf()
        val peopleCount: Int = RandomUtils.generateRandomInt(range = Constants.peopleCountRange)
        for (index in 0 until peopleCount) {
            val person = Person(id = index   1, fullName = PeopleGen.generateRandomFullName())
            newPeople.add(person)
        }
        people = newPeople.shuffled()
    }

    private fun processRequest(next: String?): ProcessResult {
        var error: FetchError? = null
        var response: FetchResponse? = null
        val isError = RandomUtils.roll(probability = Constants.errorProbability)
        val waitTime: Double
        if (isError) {
            waitTime = RandomUtils.generateRandomDouble(range = Constants.lowWaitTimeRange)
            error = FetchError(errorDescription = "Internal Server Error")
        } else {
            waitTime = RandomUtils.generateRandomDouble(range = Constants.highWaitTimeRange)
            val fetchCount = RandomUtils.generateRandomInt(range = Constants.fetchCountRange)
            val peopleCount = people.size
            val nextIntValue = try {
                next!!.toInt()
            } catch (ex: Exception) {
                null
            }
            if (next != null && (nextIntValue == null || nextIntValue < 0)) {
                error = FetchError(errorDescription = "Parameter error")
            } else {
                val endIndex: Int = min(peopleCount, fetchCount   (nextIntValue ?: 0))
                val beginIndex: Int = if (next == null) 0 else min(nextIntValue!!, endIndex)
                var responseNext: String? = if (endIndex >= peopleCount) null else endIndex.toString()
                var fetchedPeople: ArrayList<Person> = ArrayList(people.subList(beginIndex, endIndex)) // begin ile end ayni olunca bos donuyor mu?
                if (beginIndex > 0 && RandomUtils.roll(probability = Constants.backendBugTriggerProbability)) {
                    fetchedPeople.add(0, people[beginIndex - 1])
                } else if (beginIndex == 0 && RandomUtils.roll(probability = Constants.emptyFirstResultsProbability)) {
                    fetchedPeople = arrayListOf()
                    responseNext = null
                }
                response = FetchResponse(people = fetchedPeople, next = responseNext)
            }
        }
        return ProcessResult(response, error, waitTime)
    }
}

// Utils
private object RandomUtils {

    fun generateRandomInt(range: ClosedRange<Int>): Int = Random.nextInt(range.start, range.endInclusive)

    fun generateRandomDouble(range: ClosedRange<Double>): Double = Random.nextDouble(range.start, range.endInclusive)

    fun roll(probability: Double): Boolean {
        val random = Random.nextDouble(0.0, 1.0)
        return random <= probability
    }
}

private object PeopleGen {
    private val firstNames = listOf("Fatma", "Mehmet", "Ayşe", "Mustafa", "Emine", "Ahmet", "Hatice", "Ali", "Zeynep", "Hüseyin", "Elif", "Hasan", "İbrahim", "Can", "Murat", "Özlem")
    private val lastNames = listOf("Yılmaz", "Şahin", "Demir", "Çelik", "Şahin", "Öztürk", "Kılıç", "Arslan", "Taş", "Aksoy", "Barış", "Dalkıran")

    fun generateRandomFullName(): String {
        val firstNamesCount = firstNames.size
        val lastNamesCount = lastNames.size
        val firstName = firstNames[RandomUtils.generateRandomInt(range = 0 until firstNamesCount)]
        val lastName = lastNames[RandomUtils.generateRandomInt(range = 0 until lastNamesCount)]
        return "${firstName} ${lastName}"
    }
}

In the documents, I see these rules;

1) In order to fetch first set of people, use `DataSource`s `fetch` method by passing null into its `next` parameter and a completion handler.
2) "Completion Handler" has 2 parameters in its signature. A response or an error instance is passed when the operation finishes. They can't be both null or hold non-null references at a time.
3) When success, `FetchResponse` instance is passed into completion handler. This instance has people array and a `next` identifier which can be used for pagination.
4) Pass successful response's `next` identifier into `fetch` method in order to get next "pages".

In my Main class, simply I'm trying to get a list of people with their ids, I tried like this;

import DataSource
import FetchCompletionHandler
import FetchResponse
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val people = DataSource
        val fetchResponse: FetchResponse(people,null)
    }
}

or like;

        var fetchCompletionHandler:FetchCompletionHandler? = null
        var myHandler = fetchCompletionHandler?.let { DataSource().fetch(null, it) }
        var mySource = DataSource
        var x = FetchResponse(mySource,null)

But it shows thats wrong so I'm in stuck at the beginning. How can I solve this problem?

CodePudding user response:

DataSource is a class, not an object, so you must instantiate it before you can use it. The fact that it has a companion object is irrelevant to you, because the companion object doesn't have any public properties or functions.

FetchCompletionHandler is a callback function. The typealias just makes it easier to describe in code without having to write (FetchResponse?, FetchError?) -> Unit every time you need to mention the type.

Since the callback is the last parameter of the fetch function, you can call it using trailing lambda syntax. In this case, the lambda is your FetchCompletionHandler. The two parameters of the lambda are FetchResponse? and FetchError?, as defined in the typealias.

So, you'd use it like this:

class MainActivity : AppCompatActivity() {

    private val dataSource = DataSource()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        dataSource.fetch(null) { response, error ->
            // do something with the FetchResponse? and FetchError? here when they arrive
            when {
                response != null -> { }
                error!= null -> { }
            }
        }
    }
}

You should actually put the DataSource in a ViewModel so it doesn't have to get re-created every time the screen rotates. Since it supports pagination, you don't want to lose your place when the screen rotates.

Instead of trailing lambda syntax, you can assign the function to a property:

val callback: FetchCompletionHandler = { response, error ->
    //...
}

// ...
dataSource.fetch(null, callback)

Or you can write a member function and pass it using function reference syntax ::.

private fun handleFetch(response: FetchResponse?, error: FetchError?) {
    //...
}

// ...
dataSource.fetch(null, ::handleFetch)
  • Related