loadSingle return Single object, if it fails I want to call getObservable(rsList) which return Observable.
I am trying with onErrorResumeNext but it needs Single object.
How can I call getObservable(rsList) on failure of loadSingle() ?
Thanks in advance!!
repo.loadSingle()
.subscribeOn(Schedulers.io())
.onErrorResumeNext {
repo.getObservable(rsList)
}
.flatMapObservable {
if (it != null && it.status == Status.SUCCESS) {
upRsList(it.data)
}
repo.getObservable(rsList)
}
({ //observable success
}, {
//observable error
})
Api interface
interface HomeApi{
fun getSingel():Single<List<String>>
fun getObservable():Observable<HomeResponse>
}
Dependencies
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.6.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.6.2")
implementation "io.reactivex.rxjava3:rxjava:3.0.4"
implementation "io.reactivex.rxjava3:rxkotlin:3.0.0"
Required classes
internal interface Repo {
fun loadSingle(): Single<Result<List<String>>>
fun getObservable(list: List<String>): Observable<String>
}
internal class RepoImpl : Repo {
override fun loadSingle(): Single<Result<List<String>>> {
return Single.error(RuntimeException("fail"))
}
override fun getObservable(list: List<String>): Observable<String> {
if (list === emptyList<String>()) {
return Observable.just("42")
}
return Observable.just("success")
}
}
internal sealed class Result<T> {
data class Success<T>(val value: T) : Result<T>()
data class Failure<T>(private val failure: Throwable) : Result<T>()
}
Test
Wrap the error via #onErrorReturn into a default value, and handle the result accordingly.
class So64751341 {
#Test
fun `64751341`() {
val repo: Repo = RepoImpl()
val testScheduler = TestScheduler()
val flatMapObservable = repo.loadSingle()
.subscribeOn(testScheduler)
.onErrorReturn { failure -> Result.Failure(failure) }
.flatMapObservable { result ->
when (result) {
is Result.Success -> repo.getObservable(result.value)
is Result.Failure -> repo.getObservable(emptyList())
}
}
val test = flatMapObservable.test()
testScheduler.triggerActions()
test // return default value 42 onError
.assertValue("42")
}
}
Repo#loadSingle() throws exception synchronously
internal class RepoExceptionImpl : Repo {
override fun loadSingle(): Single<Result<List<String>>> {
throw java.lang.RuntimeException("whatever")
}
override fun getObservable(list: List<String>): Observable<String> {
if (list === emptyList<String>()) {
return Observable.just("42")
}
return Observable.just("success")
}
}
Test
Repo#loadSingle must be wrapped with Single#defer. Single#defer will catch the exception and emit it as #onError to the subscriber, which in turn will be handled by #onErrorReturn
#Test
fun `64751341_exception`() {
val repo: Repo = RepoExceptionImpl()
val testScheduler = TestScheduler()
val flatMapObservable = Single.defer {
repo.loadSingle()
}
.subscribeOn(testScheduler)
.onErrorReturn { failure -> Result.Failure(failure) }
.flatMapObservable { result ->
when (result) {
is Result.Success -> repo.getObservable(result.value)
is Result.Failure -> repo.getObservable(emptyList())
}
}
val test = flatMapObservable.test()
testScheduler.triggerActions()
test // return default value 42 onError
.assertValue("42")
}
Related
i am working on an online shopping application using retrofit, coroutine, livedata, mvvm,...
i want to show progress bar before fetching data from server for afew seconds
if i have one api request i can show that but in this app i have multiple request
what should i do in this situation how i should show progress bar??
Api Service
#GET("homeslider.php")
suspend fun getSliderImages(): Response<List<Model.Slider>>
#GET("amazingoffer.php")
suspend fun getAmazingProduct(): Response<List<Model.AmazingProduct>>
#GET("handsImages.php")
suspend fun getHandsFreeData(
#Query(
"handsfree_id"
) handsfree_id: Int
): Response<List<Model.HandsFreeImages>>
#GET("handsfreemoreinfo.php")
suspend fun gethandsfreemoreinfo(): Response<List<Model.HandsFreeMore>>
#GET("wristmetadata.php")
suspend fun getWristWatchMetaData(
#Query(
"wrist_id"
) wrist_id: Int
): Response<List<Model.WristWatch>>
repository
fun getSliderImages(): LiveData<List<Model.Slider>> {
val data = MutableLiveData<List<Model.Slider>>()
val job = Job()
applicationScope.launch(IO + job) {
val response = api.getSliderImages()
withContext(Main + SupervisorJob(job)) {
data.value = response.body()
}
job.complete()
job.cancel()
}
return data
}
fun getAmazingOffer(): LiveData<List<Model.AmazingProduct>> {
val data = MutableLiveData<List<Model.AmazingProduct>>()
val job = Job()
applicationScope.launch(IO + job) {
val response = api.getAmazingProduct()
withContext(Main + SupervisorJob(job)) {
data.value = response.body()
}
job.complete()
job.cancel()
}
return data
}
fun getHandsFreeData(handsree_id: Int): LiveData<List<Model.HandsFreeImages>> {
val dfData = MutableLiveData<List<Model.HandsFreeImages>>()
val job = Job()
applicationScope.launch(IO + job) {
val response = api.getHandsFreeData(handsree_id)
withContext(Main + SupervisorJob(job)) {
dfData.value = response.body()
}
job.complete()
job.cancel()
}
return dfData
}
fun getHandsFreeMore(): LiveData<List<Model.HandsFreeMore>> {
val data = MutableLiveData<List<Model.HandsFreeMore>>()
val job = Job()
applicationScope.launch(IO + job) {
val response = api.gethandsfreemoreinfo()
withContext(Main + SupervisorJob(job)) {
data.value = response.body()
}
job.complete()
job.cancel()
}
return data
}
VIEWMODEL
fun getSliderImages() = repository.getSliderImages()
fun getAmazingOffer() = repository.getAmazingOffer()
fun recieveAdvertise() = repository.recieveAdvertise()
fun dailyShoes(context: Context) = repository.getDailyShoes(context)
i will appreciate your help
I couldn't help but notice that your repository contains lots of repetitive code. first point to learn here is that all that logic in Repository, it usually goes in the ViewModel. second thing is that you are using applicationScope to launch your coroutines, which usually is done using viewModelScope(takes care of cancellation) object which is available in every viewModel.
So first we have to take care of that repetitive code and move it to ViewModel. So your viewModel would now look like
class YourViewModel: ViewModel() {
// Your other init code, repo creation etc
// Live data objects for progressBar and error, we will observe these in Fragment/Activity
val showProgress: MutableLiveData<Boolean> = MutableLiveData()
val errorMessage: MutableLiveData<String> = MutableLiveData()
/**
* A Generic api caller, which updates the given live data object with the api result
* and internally takes care of progress bar visibility. */
private fun <T> callApiAndPost(liveData: MutableLiveData<T>,
apiCall: () -> Response<T> ) = viewModelScope.launch {
try{
showProgress.postValue(true) // Show prgress bar when api call is active
if(result.code() == 200) { liveData.postValue(result.body()) }
else{ errorMessage.postValue("Network call failed, try again") }
showProgress.postValue(false)
}
catch (e: Exception){
errorMessage.postValue("Network call failed, try again")
showProgress.postValue(false)
}
}
/******** Now all your API call methods should be called as *************/
// First declare the live data object which will contain the api result
val sliderData: MutableLiveData<List<Model.Slider>> = MutableLiveData()
// Now call the API as
fun getSliderImages() = callApiAndPost(sliderData) {
repository.getSliderImages()
}
}
After that remove all the logic from Repository and make it simply call the network methods as
suspend fun getSliderImages() = api.getSliderImages() // simply delegate to network layer
And finally to display the progress bar, simply observe the showProgress LiveData object in your Activity/Fragment as
viewModel.showProgress.observer(this, Observer{
progressBar.visibility = if(it) View.VISIBLE else View.GONE
}
First create a enum class status:
enum class Status {
SUCCESS,
ERROR,
LOADING
}
Then create resource class like this:
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(msg: String, data: T?): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> loading(data: T?): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
Now add your request to a list of response:
var list = java.util.ArrayList<Response<*>>()
suspend fun getApis() = list.addAll(
listOf(
api.advertise(),
api.getAmazingProduct(),
api.dailyShoes(),
api.getSliderImages(),
.
.
.
)
)
In your viewmodel class:
private val _apis = MutableLiveData<Resource<*>>()
val apis: LiveData<Resource<*>>
get() = _apis
init {
getAllApi()
}
fun getAllApi() {
val job = Job()
viewModelScope.launch(IO + job) {
_apis.postValue(
Resource.loading(null)
)
delay(2000)
repository.getApis().let {
withContext(Main + SupervisorJob(job)) {
it.let {
if (it) {
_apis.postValue(Resource.success(it))
} else {
_apis.postValue(Resource.error("Unknown error eccured", null))
}
}
}
}
job.complete()
job.cancel()
}
}
Now you can use status to show progress like this . use this part in your target fragment:
private fun setProgress() {
viewModel.apis.observe(viewLifecycleOwner) {
when (it.status) {
Status.SUCCESS -> {
binding.apply {
progress.visibility = View.INVISIBLE
line1.visibility = View.VISIBLE
parentscroll.visibility = View.VISIBLE
}
}
Status.ERROR -> {
binding.apply {
progress.visibility = View.INVISIBLE
line1.visibility = View.INVISIBLE
parentscroll.visibility = View.INVISIBLE
}
}
Status.LOADING -> {
binding.apply {
progress.visibility = View.VISIBLE
line1.visibility = View.INVISIBLE
parentscroll.visibility = View.INVISIBLE
}
}
}
}
}
I hope you find it useful.
Context: I found few tutorials explaining how consume mutilple endpoints from Kotlin at same time but they are based on Android and in my case it is a backend application. I have some experience using CompleteableFuture but I assume I should use Coroutine since it is a Kotlin and there is no Spring dependency.
Following some suggestions, I reached
#Singleton
class PersonEndpoint()
{
#Inject
lateinit var employeClient: EmployeClient
override suspend fun getPersonDetails(request: PersonRequest): PersonResponse {
var combinedResult: String
GlobalScope.launch {
val resultA: String
val resultB: String
val employeesA = async{ employeClient.getEmployeesA()}
val employeesB = async{ employeClient.getEmployeesB()}
try{
combinedResult = employeesA.await() + employeesB.await()
print(combinedResult)
} catch (ex: Exception) {
ex.printStackTrace()
}
// ISSUE 1
if I try add return over here it is not allowed.
I understand it is working how it is designed to work: GlobalScope is running in different thread
}
// ISSUE 2
if I try return combinedResult over here combinedResult isn't initialized.
I understand it is working how it is designed to work: GlobalScope is running in different thread and I can
debug and see that return over here executes earlier than employeesA.await = employeesB.await
}
So, how can I execute combinedResult = employeesA.await() + employeesB.await() before returning to the client?
*** Edited after Denis/ answer
#Singleton
class CustomerEndpoint(){
fun serve(): Collection<Int> {
return runBlocking {
async {
getItemDouble(1)
}
async {
getItemTriple(1)
}
}.map { it.await() }
}
suspend fun getItemDouble(i: Int): Int {
delay(1000)
return i * 2
}
suspend fun getItemTriple(i: Int): Int {
delay(1000)
return i * 3
}
override suspend fun getPersonDetails(request: PersonRequest): PersonResponse {
val result = serve()
println("Got result $result")
...
}
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillis
fun main() {
val durationMs = measureTimeMillis {
val result = serve()
println("Got result $result")
}
println("The processing is done in $durationMs ms")
}
fun serve(): Collection<Int> {
return runBlocking {
(1..2).map {
async {
getItem(it)
}
}.map { it.await() }
}
}
suspend fun getItem(i: Int): Int {
delay(1000) // Emulate item retrieval work
return i * 2
}
Note that here we have two nested calls - getItem(1) and getItem(2). We can see that they are executed in parallel as overall running time is ~1 second.
Edited in August 05th 2021
private suspend fun myMethod(): List<Any> {
return runBlocking {
listOf(
async { method1() },
async { method2() }
).map { it.await() }
}
}
method1 and method2 are methods calling different endpoints.
I have a function that loop the request using Retrofit and RXJava as below
for (i in month..12) {
if (Conn.connection(applicationContext)) {
Api.create().getDateInMonth("2019-$i-01")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<DateDataResponse> {
override fun onSubscribe(d: Disposable) {}
#SuppressLint("NewApi")
override fun onNext(dateDataResponse: DateDataResponse) {
Log.d("OnSuccess", "success")
}
#SuppressLint("NewApi")
override fun onError(e: Throwable) {
Log.d("onError", "error" + e.message)
}
override fun onComplete() {
Log.d("onComplete", "onComplete")
}
})
} else
Log.d("onError", "No Internet Connection")
}
}
so if some request error or success it will go on until the 12 request is finish. I want to detect if I already got all response from my request
If you turn this into a single chain, then you can use the onComplete() callback to verify that all your requests have finished. For example:
Observable.range(0, 12)
.filter { i-> Conn.connection(applicationContext) }
.flatMap { i -> Api.create().getDateInMonth("2019-$i-01") }
.subscribeOn(io())
.observeOn(mainThread())
.subscribe({ i-> }, { t-> }, {/*onComplete*/ })
I'm trying to consume some API links. So in order to do that I have created a base AsyncTask class that differs by SOURCE and RESULT for each action in the app using generics.
The problem is when I try to initialize one of the sub classes, it returns a ClassCastException error.
Here's the base Async class
abstract class MvpBackgroundInteraction<SOURCE, RESULT> : AsyncTask<SOURCE, Int, RESULT?>(), MvpFeedContract.MvpFeedInteraction<SOURCE, RESULT> {
protected var isWorking: Boolean = true
protected var completeListener : MvpFeedContract.MvpCompleteListener<RESULT>? = null
override fun start(source: SOURCE) {
super.execute(source)
}
override fun stop() {
isWorking = false
}
override fun addCompleteListener(completeListener: MvpFeedContract.MvpCompleteListener<RESULT>?) {
this.completeListener = completeListener
}
override fun onPostExecute(result: RESULT?) {
completeListener?.apply {
if (result != null) onComplete(result) else onFailure("Result is null")
}
super.onPostExecute(result)
}
}
Here's MvpFeedContract
interface MvpFeedContract {
interface MvpFeedInteraction<SOURCE, RESULT> {
fun start(source : SOURCE)
fun stop()
fun addCompleteListener(completeListener: MvpCompleteListener<RESULT>?)
}
interface MvpCompleteListener<RESULT> {
fun onComplete(result : RESULT)
fun onFailure(message : String?)
}
}
Here's one of the sub classes that returns that error
class MvpDownloader : MvpBackgroundInteraction<String, InputStream>() {
override fun doInBackground(vararg p0: String): InputStream? {
return try {
if (isWorking) downloadUrl(p0[0]) else null
} catch (e : Exception) {
Log.e("MvpParser", e.message ?: "error")
null
}
}
companion object {
#Throws(IOException::class)
private fun downloadUrl(urlString: String): InputStream? {
val url = URL(urlString)
return (url.openConnection() as? HttpURLConnection)?.run {
readTimeout = 10000
connectTimeout = 15000
requestMethod = "GET"
doInput = true
connect()
inputStream
}
}
}
}
Here's how I'm initializing it and some similar classes
private val downloader : MvpDownloader by lazy { MvpDownloader() }
private val parser : MvpParser<OBJECT> by lazy { MvpParser<OBJECT>() }
private val cashier : MvpCashier<OBJECT> by lazy { MvpCashier<OBJECT>(baseDao) }
Here's the returned logcat
2019-07-10 14:55:11.962 1708-1729/com.tamimattafi.degitalnomads E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
Process: com.tamimattafi.degitalnomads, PID: 1708
java.lang.RuntimeException: An error occurred while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:330)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
at java.util.concurrent.FutureTask.run(FutureTask.java:242)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:255)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:776)
Caused by: java.lang.ClassCastException: java.lang.Object[] cannot be cast to java.lang.String[]
at com.tamimattafi.degitalnomads.repository.feed.MvpDownloader.doInBackground(MvpDownloader.kt:10)
at android.os.AsyncTask$2.call(AsyncTask.java:316)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:255)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:776)
EDIT
Here's my feed manager
class FeedManager<OBJECT>(baseDao: BaseDao<OBJECT>) : MvpFeedContract.MvpFeedInteraction<String, Boolean> {
private var isWorking = true
private var completeListener: MvpFeedContract.MvpCompleteListener<Boolean>? = null
private val downloader : MvpDownloader by lazy { MvpDownloader() }
private val parser : MvpParser<OBJECT> by lazy { MvpParser<OBJECT>() }
private val cashier : MvpCashier<OBJECT> by lazy { MvpCashier<OBJECT>(baseDao) }
override fun start(source: String) {
downloadFeed(source)
}
private fun downloadFeed(link : String) {
downloader.apply {
addCompleteListener(object :
MvpFeedContract.MvpCompleteListener<InputStream> {
override fun onComplete(result: InputStream) {
parseFeed(result)
}
override fun onFailure(message: String?) {
completeListener?.onFailure(message)
}
})
start(link)
}
}
private fun parseFeed(result: InputStream) {
parser.apply {
addCompleteListener(object :
MvpFeedContract.MvpCompleteListener<List<OBJECT>> {
override fun onComplete(result: List<OBJECT>) {
cashFeed(result)
}
override fun onFailure(message: String?) {
completeListener?.onFailure(message)
}
})
start(result)
}
}
private fun cashFeed(result: List<OBJECT>) {
cashier.apply {
addCompleteListener(object :
MvpFeedContract.MvpCompleteListener<Boolean> {
override fun onComplete(result: Boolean) {
completeListener?.onComplete(result)
}
override fun onFailure(message: String?) {
completeListener?.onFailure(message)
}
})
start(result)
}
}
override fun stop() {
isWorking = false
downloader.stop()
parser.stop()
cashier.stop()
}
override fun addCompleteListener(completeListener: MvpFeedContract.MvpCompleteListener<Boolean>?) {
this.completeListener = completeListener
}
}
I moved from sql db to room. I can't figure out how to check if item exist in database.
How to write this code using Room?
fun existsCheck(place: Places): Boolean {
val db= this.readableDatabase
val query = "SELECT * FROM $TABLE_PLACES WHERE $COLUMN_LAT = ${place.lat} AND $COLUMN_LNG = ${place.lng}"
val cursor = db.rawQuery(query, null)
if(cursor.count > 0){
cursor.close()
db.close()
return true
}
cursor.close()
db.close()
return false
}
I have tried to achieve this with this code, but always getting FALSE back
#Query("SELECT name FROM place WHERE lat = :lat AND lng = :lng")
fun exist(lat: Double?, lng: Double?): Flowable<String>
override fun save() {
Observable.just(dao)
.subscribeOn(Schedulers.io())
.subscribe { dao.insert(place) }
}
override fun itemExists(lat: Double?, lng: Double?): Single<Boolean> =
dao.exist(lat, lng)
.flatMapSingle { Single.just(it.isNotEmpty()) }
.onErrorReturn { false }
.first(false)
override fun delete() {
dao.deleteByLat(place.lat)
}
override fun saveClicked() {
var boolean = false
itemExists(place.lat, place.lng)
.subscribeOn(Schedulers.io())
.map { it -> boolean = it }
if (boolean){
delete()
v.setImageNotSaved()
} else {
save()
v.setImageSaved()
}
}
This works fine
override fun itemExists() {
Observable.just(dao)
.subscribeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.map { it -> it.exist(place.lat, place.lng) }
.subscribe( { it -> saveClicked(true) },
{error -> saveClicked(false)})
}