A Kotlin service with request queue - kotlin

I would like to design an a service with the following API:
suspend fun getUsers(request: Request): List<User>
Under the hood I would send a request to the server (doesn't matter how, but lets say it's a reactive WebClient), but here's a trick: I can only send requests as often as every 500 ms, otherwise I will get an error.
Could someone recommend me how I could implement it such way that when I call getUsers from a coroutine it suspends, the unit of work is being added to some queue of the service that has this method, then implemented at some point in time and returned the result?
I assume I can use some ReceiveChannel as a queue, have a for loop for its elements with a delay inside, but I'm a bit lost where to put this logic. Should this be like a background method that will run forever and gets called by getUsers? Probably the close method will never be called, so this method can also be suspended, but how do I pass the value back from this infinite running method to getUsers that needs the results?
EDIT
At the moment I'm thinking of a solution like this:
private const val REQUEST_INTERVAL = 500
#Service
class DelayedRequestSenderImpl<T> : DelayedRequestSender<T> {
private var lastRequestTime: LocalDateTime = LocalDateTime.now()
private val requestChannel: Channel<Deferred<T>> = Channel()
override suspend fun requestAsync(block: () -> T): Deferred<T> {
val deferred = GlobalScope.async(start = CoroutineStart.LAZY) { block() }
requestChannel.send(deferred)
return deferred
}
#PostConstruct
private fun startRequestProcessing() = GlobalScope.launch {
for (request in requestChannel) {
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < REQUEST_INTERVAL) {
delay(REQUEST_INTERVAL - diff)
lastRequestTime = now
}
request.start()
}
}
}
The problem I see here is that I have to generify the class to make the requestChannel generic, since the result of request may be anything. But this means that each instance of DelayedRequestSender will be tied to a particular type. Any advice on how to avoid this?
EDIT 2
Here's a refined version. The only possible flow that I see at the moment is that we have to make #PostConstruct method public in order to write any tests if we want or use reflection.
The idea was to not use GlobalScope and also have a separate Job for the processing method. Is this a fine approach?
interface DelayingSupplier {
suspend fun <T> supply(block: () -> T): T
}
#Service
class DelayingSupplierImpl(#Value("\${vk.request.interval}") private val interval: Int) : DelayingSupplier {
private var lastRequestTime: LocalDateTime = LocalDateTime.now()
private val requestChannel: Channel<Deferred<*>> = Channel()
private val coroutineScope = CoroutineScope(EmptyCoroutineContext)
override suspend fun <T> supply(block: () -> T): T {
val deferred = coroutineScope.async(start = CoroutineStart.LAZY) { block() }
requestChannel.send(deferred)
return deferred.await()
}
#PostConstruct
fun startProcessing() = coroutineScope.launch(context = Job(coroutineScope.coroutineContext[Job])) {
for (request in requestChannel) {
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < interval) {
delay(interval - diff)
}
lastRequestTime = LocalDateTime.now()
request.start()
}
}
}

I would recommend:
pushing your generics down to the function level
using an actor instead of your coroutine implementation (but possibly you prefer this).
Either way, this solution should let you use a single instance of your queue to handle the delay of all requests regardless of return type. (Apologies, I renamed some things to help my own conceptualization, hopefully this still makes sense):
private const val REQUEST_INTERVAL = 500
interface DelayedRequestHandler {
suspend fun <T> handleWithDelay(block: () -> T): T
}
class DelayedRequestHandlerImpl(requestInterval: Int = REQUEST_INTERVAL) : DelayedRequestHandler, CoroutineScope {
private val job = Job()
override val coroutineContext = Dispatchers.Unconfined + job
private val delayedHandlerActor = delayedRequestHandlerActor(requestInterval)
override suspend fun <T> handleWithDelay(block: () -> T): T {
val result = CompletableDeferred<T>()
delayedHandlerActor.send(DelayedHandlerMsg(result, block))
return result.await()
}
}
private data class DelayedHandlerMsg<RESULT>(val result: CompletableDeferred<RESULT>, val block: () -> RESULT)
private fun CoroutineScope.delayedRequestHandlerActor(requestInterval: Int) = actor<DelayedHandlerMsg<*>>() {
var lastRequestTime: LocalDateTime = LocalDateTime.now()
for (message in channel) {
try {
println("got a message processing")
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < requestInterval) {
delay(requestInterval - diff)
}
lastRequestTime = LocalDateTime.now()
#Suppress("UNCHECKED_CAST")
val msgCast = message as DelayedHandlerMsg<Any?>
val result = msgCast.block()
println(result)
msgCast.result.complete(result)
} catch (e: Exception) {
message.result.completeExceptionally(e)
}
}
}
fun main() = runBlocking {
val mydelayHandler = DelayedRequestHandlerImpl(2000)
val jobs = List(10) {
launch {
mydelayHandler.handleWithDelay {
"Result $it"
}
}
}
jobs.forEach { it.join() }
}

So this is the final implementation I came up with. Note the SupevisorJob as we don't want the processing to stop if one of requests fails, which is totally possible and fine (in my case at least).
Also, the option suggested by #Laurence might be better, but I decided to not use actors for now due to API being marked as obsolete.
#Service
class DelayingRequestSenderImpl(#Value("\${vk.request.interval}") private val interval: Int) : DelayingRequestSender {
private var lastRequestTime: LocalDateTime = LocalDateTime.now()
private val requestChannel: Channel<Deferred<*>> = Channel()
//SupervisorJob is used because we want to have continuous processing of requestChannel
//even if one of the requests fails
private val coroutineScope = CoroutineScope(SupervisorJob())
override suspend fun <T> request(block: () -> T): T {
val deferred = coroutineScope.async(start = CoroutineStart.LAZY) { block() }
requestChannel.send(deferred)
return deferred.await()
}
#PostConstruct
fun startProcessing() = coroutineScope.launch {
for (request in requestChannel) {
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < interval) {
delay(interval - diff)
}
lastRequestTime = LocalDateTime.now()
request.start()
}
}
}

Related

what is Best practices for designing asynchronous task in this case( in kotlin, coroutine or thread)

my android app need to call more than 10 APIs at the same time.
this call api's is use other library it made in other teams
and result receive by listener in JsonString format.
this multiple calling api is need to call at same time.
Because it takes a lot of time to call one API
i made it by callback structure like this.
but i hope refactor this code covert to coroutine.
private val library : OtherLibrary = OtherLibrary()
private val retryCount: HashMap<String?, Int> = HashMap()
private val listener = object : ApiListener {
override fun onSucceeded(apiName: String, result: String?) {
when (apiName) {
"UserInfo" -> handleResultUserInfo(result)
"ProductInfo" -> handleResultProductInfo(result)
"....Info" -> handleResult___Info(result)
// ... and Others
}
}
override fun onUpdate(apiName: String, version: String) = library.callApi(apiName, this)
override fun onFailed(apiName: String) = retry(apiName, this)
}
fun start() {
callAPI("UserInfo")
callAPI("ProductInfo")
// ... and Others
}
fun callAPI(apiName: String, listener: ApiListener? = null) {
val listener = listener ?: this.listener
retryCount[apiName] = 0
library.callApi(apiName, listener)
}
fun retry(apiName: String, listener: ApiListener) {
if (retryCount[apiName]!! < 3) {
retryCount[apiName]!!.plus(1)
library.callApi(apiName, listener)
}else{
throw RuntimeException("API Call Failed: $apiName")
}
}
fun handleResultUserInfo(result: String?) {
// TODO parse & do something
}
fun handleResultProductInfo(result: String?) {
// TODO parse & do something
}
fun handleResult___Info(result: String?) {
// TODO parse & do something
}
// ... and Others
i want use coroutine for readability not callback structure.
callback structure is not good method for readability i think.
so, i applied suspendCoroutine to library's listener for look like synchronous readability.
but, suspendCoroutine is suspend it functions when to until call it.resume
what is best practice in this case?
private val library : OtherLibrary = OtherLibrary()
private val retryCount: HashMap<String?, Int> = HashMap()
fun start(){
CoroutineScope(Dispatchers.IO).launch{
handleResultUserInfo(callAPI("UserInfo"))
handleResultProductInfo(callAPI("ProductInfo"))
handleResult___Info(callAPI("___Info"))
}
}
suspend fun callAPI(apiName: String, listener:ApiListener? = null) : String? = suspendCoroutine{
val listener = listener ?: object : ApiListener {
override fun onSucceeded(apiName: String, result: String?) = it.resume(result)
override fun onUpdate(apiName: String, version: String) = library.callApi(apiName, this)
override fun onFailed(apiName: String) = retry(apiName, this)
}
retryCount[apiName] = 0
library.callApi(apiName, listener)
}
↑ it waiting complete of previous work. it's not call api at same time
so i try to like this.
fun start(){
val callDataArr = arrayOf(
CallData("UserInfo", ::handleResultUserInfo),
CallData("ProductInfo", ::handleResultProductInfo),
CallData("___Info", ::handleResult___Info),
// ... and others
)
callDataArr.forEach {
CoroutineScope(Dispatchers.IO).launch{
it.handler(callAPI(it.apiName))
}
}
}
but... it doesn't look good.
because, CoroutineScope(Dispatchers.IO).launch called a lot of times
is not good for performance or have other problems?

How to access class methods from anonymous suspend function inside constructor in kotlin?

I want to be able to call functions from the anonymous constructor's suspend function in the following example:
data class SuspendableStep(
val condition: SuspendableCondition,
val continuation: Continuation<Unit>
)
class WaitCondition(cycles: Int) : SuspendableCondition() {
private val timer = SomeTimer(cycles)
override fun resume(): Boolean = timer.elapsed() // timer is handled somewhere else
override fun toString(): String = "WaitCondition_$timer"
}
class BasicContinuation : Continuation<Unit> {
var coroutine: Continuation<Unit>
override val context: CoroutineContext = EmptyCoroutineContext
private var nextStep: SuspendableStep? = null
constructor(task: suspend () -> Unit) {
coroutine = task.createCoroutine(completion = this)
}
override fun resumeWith(result: Result<Unit>) {
nextStep = null
result.exceptionOrNull()?.let { e -> Logger.handle("Error with plugin!", e) }
}
suspend fun wait(cycles: Int): Unit = suspendCoroutine {
check(cycles > 0) { "Wait cycles must be greater than 0." }
nextStep = SuspendableStep(WaitCondition(cycles), it)
}
}
fun main() {
BasicContinuation({
println("HELLO")
wait(1)
println("WORLD")
}).coroutine.resume(Unit)
}
There only other option I found was to override a suspend function by creating an anonymous inner class and calling another function to set the coroutine:
fun main() {
val bc = BasicContinuation() {
override suspend fun test() : Unit {
println("HELLO")
wait(1)
println("WORLD")
}
}
bc.set() // assign coroutine to suspend { test }.createCoroutine(completion = this)
bc.coroutine.resume(Unit)
}
I used CoroutineScope to extend the scope of the functions I could access:
class BasicContinuation : Continuation<Unit> {
var coroutine: Continuation<Unit>
override val context: CoroutineContext = EmptyCoroutineContext
private var nextStep: SuspendableStep? = null
constructor(task: (suspend BasicContinuation.(CoroutineScope) -> Unit)) {
coroutine = suspend { task.invoke(this, CoroutineScope(context)) }.createCoroutine(completion = this)
}
override fun resumeWith(result: Result<Unit>) {
nextStep = null
result.exceptionOrNull()?.let { e -> Logger.handle("Error with plugin!", e) }
}
suspend fun wait(cycles: Int): Unit = suspendCoroutine {
check(cycles > 0) { "Wait cycles must be greater than 0." }
nextStep = SuspendableStep(WaitCondition(cycles), it)
}
}
fun main() {
val bc = BasicContinuation({
println("Hello")
wait(1)
println("World")
})
bc.coroutine.resume(Unit) // print "Hello"
// increment timer
bc.coroutine.resume(Unit) // print "World
}

How to ignore empty database result for the first time and wait for server result in application?

My app using room as a database and retrofit as a network calling api.
i am observing database only as a single source of truth. every thing is working fine. But i am not finding solution of one scenario.
Like for the first time when user open app it do following operations
fetch data from db
fetch data from server
because currently database is empty so it sends empty result to observer which hide progress bar . i want to discard that event and send result to observer when server dump data to database. even server result is empty. so progress bar should always hide once their is confirmation no data exists.
in other words application should always rely on database but if it empty then it should wait until server response and then notify observer.
this is my code
observer
viewModel.characters.observe(viewLifecycleOwner, Observer {
Log.e("status is ", "${it.message} at ${System.currentTimeMillis()}")
when (it.status) {
Resource.Status.SUCCESS -> {
binding.progressBar.visibility = View.GONE
if (!it.data.isNullOrEmpty()) adapter.setItems(ArrayList(it.data))
}
Resource.Status.ERROR -> {
Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
binding.progressBar.visibility = View.GONE
}
Resource.Status.LOADING ->
binding.progressBar.visibility = View.VISIBLE
}
})
ViewModel
#HiltViewModel
class CharactersViewModel #Inject constructor(
private val repository: CharacterRepository
) : ViewModel() {
val characters = repository.getCharacters()
}
Repository
class CharacterRepository #Inject constructor(
private val remoteDataSource: CharacterRemoteDataSource,
private val localDataSource: CharacterDao
) {
fun getCharacters() : LiveData<Resource<List<Character>>> {
return performGetOperation(
databaseQuery = { localDataSource.getAllCharacters() },
networkCall = { remoteDataSource.getCharacters() },
saveCallResult = { localDataSource.insertAll(it.results) }
)
}
}
Utility function for all api and database handling
fun <T, A> performGetOperation(databaseQuery: () -> LiveData<T>,
countQuery: () -> Int,
networkCall: suspend () -> Resource<A>,
saveCallResult: suspend (A) -> Unit): LiveData<Resource<T>> =
liveData(Dispatchers.IO) {
emit(Resource.loading())
val source = databaseQuery().map { Resource.success(it,"database") }.distinctUntilChanged()
emitSource(source)
val responseStatus = networkCall()
if (responseStatus.status == SUCCESS) {
saveCallResult(responseStatus.data!!)
} else if (responseStatus.status == ERROR) {
emit(Resource.error(responseStatus.message!!))
}
}
LocalDataSource
#Dao
interface CharacterDao {
#Query("SELECT * FROM characters")
fun getAllCharacters() : LiveData<List<Character>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(characters: List<Character>)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(character: Character)
}
DataSource
class CharacterRemoteDataSource #Inject constructor(
private val characterService: CharacterService
): BaseDataSource() {
suspend fun getCharacters() = getResult { characterService.getAllCharacters() }}
}
Base Data Source
abstract class BaseDataSource {
protected suspend fun <T> getResult(call: suspend () -> Response<T>): Resource<T> {
try {
Log.e("status is", "started")
val response = call()
if (response.isSuccessful) {
val body = response.body()
if (body != null) return Resource.success(body,"server")
}
return error(" ${response.code()} ${response.message()}")
} catch (e: Exception) {
return error(e.message ?: e.toString())
}
}
private fun <T> error(message: String): Resource<T> {
Timber.d(message)
return Resource.error("Network call has failed for a following reason: $message")
}
}
Character Service
interface CharacterService {
#GET("character")
suspend fun getAllCharacters() : Response<CharacterList>
}
Resource
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
enum class Status {
SUCCESS,
ERROR,
LOADING
}
companion object {
fun <T> success(data: T,message : String): Resource<T> {
return Resource(Status.SUCCESS, data, message)
}
fun <T> error(message: String, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, message)
}
fun <T> loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, "loading")
}
}
}
CharacterList
data class CharacterList(
val info: Info,
val results: List<Character>
)
What is the best way by that i ignore database if it is empty and wait for server response and then notify observer

Test a view model with livedata, coroutines (Kotlin)

I've been trying to test my view model for several days without success.
This is my view model :
class AdvertViewModel : ViewModel() {
private val parentJob = Job()
private val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.Default
private val scope = CoroutineScope(coroutineContext)
private val repository : AdvertRepository = AdvertRepository(ApiFactory.Apifactory.advertService)
val advertContactLiveData = MutableLiveData<String>()
fun fetchRequestContact(requestContact: RequestContact) {
scope.launch {
val advertContact = repository.requestContact(requestContact)
advertContactLiveData.postValue(advertContact)
}
}
}
This is my repository :
class AdvertRepository (private val api : AdvertService) : BaseRepository() {
suspend fun requestContact(requestContact: RequestContact) : String? {
val advertResponse = safeApiCall(
call = {api.requestContact(requestContact).await()},
errorMessage = "Error Request Contact"
)
return advertResponse
}
}
This is my view model test :
#RunWith(JUnit4::class)
class AdvertViewModelTest {
private val goodContact = RequestContact(...)
private lateinit var advertViewModel: AdvertViewModel
private var observer: Observer<String> = mock()
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Before
fun setUp() {
advertViewModel = AdvertViewModel()
advertViewModel.advertContactLiveData.observeForever(observer)
}
#Test
fun fetchRequestContact_goodResponse() {
advertViewModel.fetchRequestContact(goodContact)
val captor = ArgumentCaptor.forClass(String::class.java)
captor.run {
verify(observer, times(1)).onChanged(capture())
assertEquals("someValue", value)
}
}
}
The method mock() :
inline fun <reified T> mock(): T = Mockito.mock(T::class.java)
I got this error :
Wanted but not invoked: observer.onChanged();
-> at com.vizzit.AdvertViewModelTest.fetchRequestContact_goodResponse(AdvertViewModelTest.kt:52)
Actually, there were zero interactions with this mock.
I don't understand how to retrieve the result of my query.
You would need to write a OneTimeObserver to observe livedata from the ViewModel
class OneTimeObserver<T>(private val handler: (T) -> Unit) : Observer<T>, LifecycleOwner {
private val lifecycle = LifecycleRegistry(this)
init {
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
override fun getLifecycle(): Lifecycle = lifecycle
override fun onChanged(t: T) {
handler(t)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
}
After that you can write an extension function:
fun <T> LiveData<T>.observeOnce(onChangeHandler: (T) -> Unit) {
val observer = OneTimeObserver(handler = onChangeHandler)
observe(observer, observer)
}
Than you can check this ViewModel class class that I have from a project to check what's going on with your LiveData after you act (when) with invoking a method.
As for your error, it just says that the onChanged() method is not being called ever.

Single method to launch a coroutine

I have a StorageRepository that talks with RoomDB and also shared prefs. I want this communication to happen through a single method on a IO thread. I have done this until now -
class StorageRepository(private val coroutineDispatcher: CoroutineContext = Dispatchers.Main
) : CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + coroutineDispatcher
override fun storeUserDetails(userDetails: UserDetails) {
roomDB.store(userDetails)
}
override fun storeTimeStamp(timeStamp: String) {
sharedPrefs.store(timeStamp)
}
private fun executeAllOpsOnIOThread() = launch {
withContext(Dispatchers.IO) {
//Any DB write, read operations to be done here
}
}
}
My question is how can I pass roomDB.store(userDetails) and sharedPrefs.store(timeStamp) to executeAllOpsOnIOThread() so that all DB communication happens on IO thread?
Hmm.. Maybe I misunderstand you but it seems you can just pass a block of code as lambda function like this:
class StorageRepository(
private val coroutineDispatcher: CoroutineContext = Dispatchers.Main
) : CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + coroutineDispatcher
override fun storeUserDetails(userDetails: UserDetails) = executeAllOpsOnIOThread {
roomDB.store(userDetails)
}
override fun storeTimeStamp(timeStamp: String) = executeAllOpsOnIOThread {
sharedPrefs.store(timeStamp)
}
private fun executeAllOpsOnIOThread(block: () -> Unit) = launch {
withContext(Dispatchers.IO) {
block()
}
}
//async get
fun getTimestamp(): Deferred<String> = getOnIOThread { sharedPrefs.getTime() }
private fun <T> getOnIOThread(block: () -> T):Deferred<T> = async {
withContext(Dispatchers.IO) {
block()
}
}
}