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 id
s, 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)