How to achieve concurrency with Kotlin coroutines? - android

I have list of carousels and run over each carousel and based on the carousel query I do fetchAssets() and fetchAssets() is Kotlin suspended function but the problem is each function is called when the previous one is finished I want to achieve concurency?
uiScope.launch {
carousels.mapIndexed { index, carousel ->
when (val assetsResult = assetRepository.fetchAssets(carousel.query)) {
is Response.Success<List<Asset>> -> {
if (assetsResult.data.isNotEmpty()) {
val contentRow = ContentRow(assetsResult.data)
contentRows.add(contentRow)
contentRowsmutableData.postValue(contentRows)
}
}
is Response.Failure -> {
}
}
}
}
override suspend fun fetchAssets(query: String): Response<List<Asset>> {
return suspendCoroutine { cont ->doHttp(assetsEndpoint, JsonHttpCall("GET"),
object : JsonReaderResponseHandler() {
override fun onSuccess(jsonReader: JsonReader) {
val apiAsset = ApiAssetList(jsonReader)
cont.resume(Response.Success(apiAsset.items))
}
override fun onError(error: Error) {
cont.resume(Response.Failure("errorMessage"))
}
})
}
}```

You have to wrap your suspend function in an async block, then wait for all async operations to complete:
uiScope.launch {
val asyncList = carousels.map { carousel ->
async { assetRepository.fetchAssets(carousel.query) }
}
val results = asyncList.awaitAll()
results.forEach { result ->
when (result) {
is Response.Success -> TODO()
is Response.Failure -> TODO()
}
}
}
suspend fun fetchAssets(query: String): Response<List<Asset>>
Edit: if you want to update the UI as each completes, you need to change it like this:
carousels.forEach { carousel ->
uiScope.launch {
val result = fetchAssets(carousel.query)
when (result) {
is Response.Success -> {
if (result.data.isNotEmpty()) {
val contentRow = ContentRow(result.data)
contentRows.add(contentRow)
contentRowsmutableData.postValue(contentRows)
}
}
is Response.Failure -> TODO()
}
}
}
Check this for concurrency with Coroutines.

Related

Creating a Kotlin DSL to accept processing pipeline and deliver an end result

I want to try to write a DSL, which can launch multiple methods piped one after the other to give an end result of a specific type, lets call it “Result”. The intermediate results of each of the calls can have different types, lets call them for example “Type 1", “Type 2” etc. I tried to accomplish something like this with following code.
class PipelineBuilder<I, R> {
val steps = mutableListOf<suspend (Any?) -> Any?>()
var initialFunction: (suspend () -> I)? = null
fun startWith(step: suspend () -> I) {
initialFunction = step
}
inline fun <reified T> then(noinline step: suspend (T) -> Any) {
steps.add { step }
}
suspend fun execute(): Any? {
if (steps.isEmpty()) {
throw Exception("Your pipeline is empty.")
}
var result: Any? = initialFunction?.invoke()
if (steps.size == 1) {
return result
}
for (index in 0 until steps.size) {
result = steps[index].invoke(result)
}
return result
}
suspend fun <T> executeConcurrent(vararg steps: suspend () -> T): List<T> {
return steps.map { step ->
coroutineScope {
async { step() }
}
}.awaitAll()
}
}
suspend fun <I, R> pipeline(block: suspend PipelineBuilder<I, R>.() -> Unit): Any? {
val builder = PipelineBuilder<I, R>()
builder.block()
return builder.execute()
}
suspend fun type1Method(input: Int): Type1 {
delay(1000)
return Type1()
}
suspend fun type2Method(input: Type1): Type2 {
delay(1000)
return Type2()
}
suspend fun type3Method(input: Type2): Int {
delay(1000)
return 3
}
class Type1
class Type2
class Type3
fun main() {
runBlocking {
val result = pipeline<Type1, Int> {
startWith { type1Method(1) }
then<Type1> {
type2Method(it)
}
then<Type2> {
type3Method(it)
}
}
println(result)
}
}
The pipeline should be able to execute multiple steps concurrently as well and combine the results in a combined result. When calling execute, you should execute the entire pipeline and then get an end result.
When I execute the code above, I don't get a "3" as a result, but "Function2<com.unisoft.myapplication.Type2, kotlin.coroutines.Continuation<? super java.lang.Object>, java.lang.Object>
". Where did I make a mistake and is there a simpler way of achieving this?

Flow block is not executing, call generated from ViewModel

Can't understand why the flow block doesn't execute and control returns back to ViewModel.
ViewModel code
fun getFilesFromServer() {
viewModelScope.launch(Dispatchers.IO) {
ftpRepository.getFilesFromServer()
.onEach { state ->
when (state) {
is Resource.Loading -> {
liveData.postValue(Resource.Loading())
}
}
}
}
Repository (Here the problem, control doesn't go inside flow {} block)
override suspend fun getFilesFromServer(): Flow<Resource<Response>> = flow {
emit(Resource.Loading())
ftpClient.connect(
Constants.SERVER_IP, Constants.PORT,
Constants.USER_NAME,
Constants.PASSWORD,
object : OnEZFtpCallBack<Void?> {
override fun onSuccess(response: Void?) {
requestFtpFileList()
}
override fun onFail(code: Int, msg: String) {
}
}
)
}
Thanks for your time...
Forgot to launch the flow
launchIn(this)
Complete code
fun getFilesFromServer() {
viewModelScope.launch(Dispatchers.IO) {
ftpRepository.getFilesFromServer()
.onEach { state ->
when (state) {
is Resource.Loading -> {
liveData.postValue(Resource.Loading())
}
}
}.launchIn(this)
}

Observe callback is not being triggered with Kotlin coroutines Flow and LiveData?

I'm new to Android development and trying to understand Coroutines and LiveData from various example projects. I have currently setup a function to call my api when the user has input a username and password. However after 1 button press, the app seems to jam and I can't make another api call as if its stuck on a pending process.
This is my first android app made with a mash of ideas so please let me know where I've made mistakes!
Activity:
binding.bLogin.setOnClickListener {
val username = binding.etUsername.text.toString()
val password = binding.etPassword.text.toString()
viewModel.userClicked(username, password).observe(this, Observer {
it?.let { resource ->
when (resource.status) {
Status.SUCCESS -> {
print(resource.data)
}
Status.ERROR -> {
print(resource.message)
}
Status.LOADING -> {
// loader stuff
}
}
}
})
}
ViewModel:
fun userClicked(username: String, password: String) = liveData(dispatcherIO) {
viewModelScope.launch {
emit(Resource.loading(data = null))
try {
userRepository.login(username, password).apply {
emit(Resource.success(null))
}
} catch (exception: Exception) {
emit(Resource.error(exception.message ?: "Error Occurred!", data = null))
}
}
}
Repository:
#WorkerThread
suspend fun login(
username: String,
password: String
): Flow<Resource<String?>> {
return flow {
emit(Resource.loading(null))
api.login(LoginRequest(username, password)).apply {
this.onSuccessSuspend {
data?.let {
prefs.apiToken = it.key
emit(Resource.success(null))
}
}
}.onErrorSuspend {
emit(Resource.error(message(), null))
}.onExceptionSuspend {
emit(Resource.error(message(), null))
}
}.flowOn(dispatcherIO)
}
API:
suspend fun login(#Body request: LoginRequest): ApiResponse<Auth>
You don't need to launch a coroutine in liveData builder, it is already suspend so you can call suspend functions there:
fun userClicked(username: String, password: String) = liveData(dispatcherIO) {
emit(Resource.loading(data = null))
try {
userRepository.login(username, password).apply {
emit(Resource.success(null))
}
} catch (exception: Exception) {
emit(Resource.error(exception.message ?: "Error Occurred!", data = null))
}
}
If you want to use LiveDate with Flow you can convert Flow to LiveData object using asLiveData function:
fun userClicked(username: String, password: String): LiveData<Resource<String?>> {
return userRepository.login(username, password).asLiveData()
}
But I wouldn't recommend to mix up LiveData and Flow streams in the project. I suggest to use only Flow.
Using only Flow:
// In ViewModel:
fun userClicked(username: String, password: String): Flow<Resource<String?>> {
return userRepository.login(username, password)
}
// Activity
binding.bLogin.setOnClickListener {
val username = binding.etUsername.text.toString()
val password = binding.etPassword.text.toString()
lifecycleScope.launch {
viewModel.userClicked(username, password).collect { resource ->
when (resource.status) {
Status.SUCCESS -> {
print(resource.data)
}
Status.ERROR -> {
print(resource.message)
}
Status.LOADING -> {
// loader stuff
}
}
}
}
}
Remove suspend keyword from the login function in Repository.
lifecycleScope docs.

NetworkBoundResource with Kotlin coroutines

Do you have any ideas how to implement repository pattern with NetworkBoundResource and Kotlin coroutines? I know we can launch a coroutine withing a GlobalScope, but it may lead to coroutine leak. I would like to pass a viewModelScope as a parameter, but it is a bit tricky, when it comes to implementation (because my repository doesn't know a CoroutineScope of any ViewModel).
abstract class NetworkBoundResource<ResultType, RequestType>
#MainThread constructor(
private val coroutineScope: CoroutineScope
) {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
#Suppress("LeakingThis")
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
if (shouldFetch(data)) {
fetchFromNetwork(dbSource)
} else {
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
#MainThread
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
val apiResponse = createCall()
result.addSource(dbSource) { newData ->
setValue(Resource.loading(newData))
}
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
result.removeSource(dbSource)
when (response) {
is ApiSuccessResponse -> {
coroutineScope.launch(Dispatchers.IO) {
saveCallResult(processResponse(response))
withContext(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
}
is ApiEmptyResponse -> {
coroutineScope.launch(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
is ApiErrorResponse -> {
onFetchFailed()
result.addSource(dbSource) { newData ->
setValue(Resource.error(response.errorMessage, newData))
}
}
}
}
}
}
Update (2020-05-27):
A way which is more idiomatic to the Kotlin language than my previous examples, uses the Flow APIs, and borrows from Juan's answer can be represented as a standalone function like the following:
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { Unit },
crossinline shouldFetch: (ResultType) -> Boolean = { true }
) = flow<Resource<ResultType>> {
emit(Resource.Loading(null))
val data = query().first()
val flow = if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
emitAll(flow)
}
The above code can be called from a class, e.g. a Repository, like so:
fun getItems(request: MyRequest): Flow<Resource<List<MyItem>>> {
return networkBoundResource(
query = { dao.queryAll() },
fetch = { retrofitService.getItems(request) },
saveFetchResult = { items -> dao.insert(items) }
)
}
Original answer:
This is how I've been doing it using the livedata-ktx artifact; no need to pass in any CoroutineScope. The class also uses just one type instead of two (e.g. ResultType/RequestType) since I always end up using an adapter elsewhere for mapping those.
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.map
import nihk.core.Resource
// Adapted from: https://developer.android.com/topic/libraries/architecture/coroutines
abstract class NetworkBoundResource<T> {
fun asLiveData() = liveData<Resource<T>> {
emit(Resource.Loading(null))
if (shouldFetch(query())) {
val disposable = emitSource(queryObservable().map { Resource.Loading(it) })
try {
val fetchedData = fetch()
// Stop the previous emission to avoid dispatching the saveCallResult as `Resource.Loading`.
disposable.dispose()
saveFetchResult(fetchedData)
// Re-establish the emission as `Resource.Success`.
emitSource(queryObservable().map { Resource.Success(it) })
} catch (e: Exception) {
onFetchFailed(e)
emitSource(queryObservable().map { Resource.Error(e, it) })
}
} else {
emitSource(queryObservable().map { Resource.Success(it) })
}
}
abstract suspend fun query(): T
abstract fun queryObservable(): LiveData<T>
abstract suspend fun fetch(): T
abstract suspend fun saveFetchResult(data: T)
open fun onFetchFailed(exception: Exception) = Unit
open fun shouldFetch(data: T) = true
}
Like #CommonsWare said in the comments, however, it'd be nicer to just expose a Flow<T>. Here's what I've tried coming up with to do that. Note that I haven't used this code in production, so buyer beware.
import kotlinx.coroutines.flow.*
import nihk.core.Resource
abstract class NetworkBoundResource<T> {
fun asFlow(): Flow<Resource<T>> = flow {
val flow = query()
.onStart { emit(Resource.Loading<T>(null)) }
.flatMapConcat { data ->
if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
}
emitAll(flow)
}
abstract fun query(): Flow<T>
abstract suspend fun fetch(): T
abstract suspend fun saveFetchResult(data: T)
open fun onFetchFailed(throwable: Throwable) = Unit
open fun shouldFetch(data: T) = true
}
#N1hk answer works right, this is just a different implementation that doesn't use the flatMapConcat operator (it is marked as FlowPreview at this moment)
#FlowPreview
#ExperimentalCoroutinesApi
abstract class NetworkBoundResource<ResultType, RequestType> {
fun asFlow() = flow {
emit(Resource.loading(null))
val dbValue = loadFromDb().first()
if (shouldFetch(dbValue)) {
emit(Resource.loading(dbValue))
when (val apiResponse = fetchFromNetwork()) {
is ApiSuccessResponse -> {
saveNetworkResult(processResponse(apiResponse))
emitAll(loadFromDb().map { Resource.success(it) })
}
is ApiErrorResponse -> {
onFetchFailed()
emitAll(loadFromDb().map { Resource.error(apiResponse.errorMessage, it) })
}
}
} else {
emitAll(loadFromDb().map { Resource.success(it) })
}
}
protected open fun onFetchFailed() {
// Implement in sub-classes to handle errors
}
#WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
#WorkerThread
protected abstract suspend fun saveNetworkResult(item: RequestType)
#MainThread
protected abstract fun shouldFetch(data: ResultType?): Boolean
#MainThread
protected abstract fun loadFromDb(): Flow<ResultType>
#MainThread
protected abstract suspend fun fetchFromNetwork(): ApiResponse<RequestType>
}
I am new to Kotlin Coroutine. I just come across this problem this week.
I think if you go with the repository pattern as mentioned in the post above, my opinion is feeling free to pass a CoroutineScope into the NetworkBoundResource. The CoroutineScope can be one of the parameters of the function in the Repository, which returns a LiveData, like:
suspend fun getData(scope: CoroutineScope): LiveDate<T>
Pass the build-in scope viewmodelscope as the CoroutineScope when calling getData() in your ViewModel, so NetworkBoundResource will work within the viewmodelscope and be bound with the Viewmodel's lifecycle. The coroutine in the NetworkBoundResource will be cancelled when ViewModel is dead, which would be a benefit.
To use the build-in scope viewmodelscope, don't forget add below in your build.gradle.
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha01'

Better way to chain rxjava2 calls with conditional operations

I have the following code, that does one single call, gets the result of the call, which is a boolean, then makes the second call if the result is false.
private fun linkEmailAndTextTogether(contactPhoneNumber: ContactPhoneNumbers,phoneNumber : PhoneNumber) {
val single = SingleOnSubscribe<Boolean> {
contactPhoneNumber.doesEmailContactExist(phoneNumber)
}
Single.create(single)
.subscribeOn(Schedulers.io())
.subscribeWith(object : SingleObserver<Boolean> {
override fun onSuccess(phoneNumberDoesExist: Boolean) {
if (!phoneNumberDoesExist) {
val completable = CompletableOnSubscribe {
contactPhoneNumber.linkEmailAndTextTogether(phoneNumber)
}
compositeDisposable.add(Completable.create(completable)
.subscribeOn(Schedulers.io())
.subscribe())
}
}
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onError(e: Throwable) {
Timber.e(e,e.localizedMessage)
}
})
}
It seems like there should be a more elegant way to do this in some kind of chain.
you could use the flatMap operator - the downside is that you won't know if the first or the second failed.
Single.just(phoneNumber)
.subscribeOn(Schedulers.io())
.map { it -> contactPhoneNumber.doesEmailContactExist(it) }
.flatMap { it ->
if (it) {
return#flatMap contactPhoneNumber.linkEmailAndTextTogether(phoneNumber)
}
Single.just(it)
}.subscribe({}, Throwable::printStackTrace);
This should help.
val single = SingleOnSubscribe<Boolean> {
getSingle()
}
Single.create(single).map({
if (it){
return#map getCompleteable()
}
return#map Completable.complete()
})

Categories

Resources