fun getClient(token: String, userId: Long, language: String = "en", maxTry: Int = 2): Retrofit {
val okHttpClient = OkHttpClient.Builder()
okHttpClient.readTimeout(30, TimeUnit.SECONDS)
okHttpClient.connectTimeout(30, TimeUnit.SECONDS)
okHttpClient.writeTimeout(90, TimeUnit.SECONDS)
var tryCount = 0
okHttpClient.addInterceptor { chain ->
val original = chain.request()
val request = original.newBuilder()
.addHeader("Content-Type", "application/json")
.addHeader("secToken", token)
.addHeader("userId", userId.toString()).build()
var response = chain.proceed(request)
while (!response.isSuccessful && tryCount < maxTry) {
Log.d("intercept", "Request is not successful - $tryCount")
tryCount++
response = chain.proceed(request)
}
response
}
val builder = GsonBuilder()
builder.registerTypeAdapter(TransModel::class.java, NotificationTypeAdapter(language))
val gson = builder.create()
if (retrofit == null) {
retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient.build())
.build()
} else {
}
// .client(getHttpClientForFile())
return retrofit!!
}
Above code is to get singleton retrofit client for every request in App.
What I need to do is what to do in else part of retrofit == null.
Here language is initialized only once. while initializing retrofit, but for second request I don't have idea to change language and maxTry count for request.
I want to change language, and maxTry at runTime. For every request there must different maxTry count and may language also.
Edit:
As per suggestion of #EarlOfEgo my else part is
retrofit!!.newBuilder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient.build())
.build()
but It is not changing language.
Edit 2:
TransModel
class TransModel {
var en: String = ""
var gu: String = ""
var hi: String = ""
constructor()
fun get(language: String): String? {
return when (language) {
LanguageUtil.languageEn -> {
en
}
LanguageUtil.languageGu -> {
gu
}
LanguageUtil.languageHi -> {
hi
}
else -> {
null
}
}
}
constructor(language: String, value: String) {
when (language) {
LanguageUtil.languageEn -> {
en = value
}
LanguageUtil.languageGu -> {
gu = value
}
LanguageUtil.languageHi -> {
hi = value
}
}
}
fun getValueByLanguage(language: String): String? {
return when (language) {
LanguageUtil.languageEn -> {
en
}
LanguageUtil.languageGu -> {
gu
}
LanguageUtil.languageHi -> {
hi
}
else -> {
null
}
}
}
fun updateIt(title: TransModel, currentLanguage: String) {
when (currentLanguage) {
LanguageUtil.languageEn -> {
gu = title.gu
hi = title.hi
}
LanguageUtil.languageGu -> {
en = title.en
hi = title.hi
}
LanguageUtil.languageHi -> {
gu = title.gu
en = title.en
}
}
}
}
and my NotificationTypeAdapter
class NotificationTypeAdapter(val language: String) : TypeAdapter<TransModel>() {
override fun write(out: JsonWriter?, value: TransModel?) {
if (out == null || value == null) return
out.beginObject()
out.name("title")
out.value(value.getValueByLanguage(language))
out.endObject()
}
override fun read(reader: JsonReader?): TransModel? {
if (reader == null) return null
val jsonParser = JsonParser()
val je = jsonParser.parse(reader)
val trans = TransModel(language, (je.asString))
return trans
}
}
You can use the Retrofit method newBuilder to get a new builder and there set a different OkHttpClient with different attributes. Put something like this into your else case:
retrofit.newBuilder()
.client(anotherOkHttpClientWithOtherAttributes.build())
.build()
You can add Credentials manual as Header To your request.
Example:
#GET("user")
Call<UserDetails> getUserDetails(#Header("Authorization") String credentials)
Credentials.basic("ausername","apassword");
You can change BASE_URL at runtime by passing it as a parameter with the use of #Url annotation.
#FormUrlEncoded
#POST()
fun testService(#Url baseUrl: String,
#Field("exampleParam") exampleParam: String): Observable<String>
Please try this and let me know for any query.
Since I didn't found proper answer I am using new retrofit client for every call.
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient.build())
.build()
If anyone have more proper answer, Please suggest me some.
Related
In this app, I used Blogger API, I have this method that gets posts
#GET
suspend fun getPostList(#Url URL: String): Response<PostList>
This is the base URL that I used with it
const val BASE_URL = "https://www.googleapis.com/blogger/v3/blogs/$BLOG_ID/posts/"
when at first request it become like this
https://www.googleapis.com/blogger/v3/blogs/12345678910/posts/maxResults=20&key=API_KEY
when the second request it should be getting a token parameter pageToken=ABCDE in result that I needed to do pagination, so I edit the BASE URL in viewModel and add the pageToken parameter to it then I pass it to the getPostList method again
and it looks like this
https://www.googleapis.com/blogger/v3/blogs/12345678910/posts/maxResults=20&pageToken=ABCDEFG&key=API_KEY
This my network layer
private val interceptor : HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY
}
#Singleton
#Provides
fun postAPIService(): PostAPIService {
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.client(
OkHttpClient.Builder().readTimeout(
15, TimeUnit.SECONDS
).connectTimeout(15, TimeUnit.SECONDS)
.addNetworkInterceptor(interceptor)
.addInterceptor { chain ->
val url = chain
.request()
.url
.newBuilder()
.addQueryParameter("maxResults", Constants.MAX_RESULT)
.addQueryParameter("key", Constants.API_KEY)
.build()
chain.proceed(chain.request().newBuilder().url(url)
.build())
}.build()
)
.addConverterFactory(GsonConverterFactory.create())
.build().create(PostAPIService::class.java)
}
}
In view model layer:
/** RETROFIT **/
var postsResponse: MutableLiveData<NetworkResult<PostList>> = MutableLiveData()
var postListResponse: PostList? = null
val label = MutableLiveData<String>()
var finalURL: MutableLiveData<String> = MutableLiveData()
private val token = MutableLiveData<String?>()
private suspend fun getPostsSafeCall() {
postsResponse.postValue(NetworkResult.Loading())
Log.e(TAG, "getPostsSafeCall finalURL is ${finalURL.value!!}")
if (hasInternetConnection()) {
try {
if (finalURL.value.isNullOrEmpty()) {
finalURL.postValue(BASE_URL)
}
val response = mainRepository.remoteDataSource.getPostList(finalURL.value!!)
postsResponse.value = handlePostsResponse(response)
} catch (exception: Exception) {
postsResponse.postValue(NetworkResult.Error(exception.message.toString()))
// Log.e(TAG, e.message + e.cause)
if (exception is HttpException) {
errorCode.postValue(exception.code())
Log.e(TAG, "getPostsSafeCall: errorCode $errorCode")
Log.e(TAG, "getPostsSafeCall: ${exception.message.toString()}")
}
}
} else {
postsResponse.postValue(NetworkResult.Error("No Internet Connection."))
}
}
private fun handlePostsResponse(response: Response<PostList>): NetworkResult<PostList> {
if (response.isSuccessful) {
token.value = response.body()?.nextPageToken
Log.d(TAG, "handlePostsResponse: token = ${response.body()?.nextPageToken.toString()}")
response.body()?.let { resultResponse ->
Log.d(
TAG, "handlePostsResponse: old token is: ${token.value} " +
"new token is: ${resultResponse.nextPageToken}"
)
finalURL.postValue(
BASE_URL + "&pageToken=${token.value}"
)
if (postListResponse == null) {
postListResponse = resultResponse
} else {
val oldPosts = postListResponse?.items
val newPosts = resultResponse.items
oldPosts?.addAll(newPosts)
}
Log.d(TAG, "handlePostsResponse: ${token.value}")
Log.e(TAG, "handlePostsResponse finalURL is ${finalURL.value!!}")
for (item in resultResponse.items) {
insertItem(item)
}
return NetworkResult.Success(postListResponse ?: resultResponse)
}
} else {
if (token.value == null) {
errorCode.postValue(400)
} else {
errorCode.postValue(response.code())
}
Log.d(TAG, "handlePostsResponse: ${response.code().toString()}")
Log.d(TAG, "handlePostsResponse: ${response.headers().toString()}")
Log.d(TAG, "handlePostsResponse: ${response.headers().toString()}")
// Log.d(TAG, "handlePostsResponse: final ${finalURL.value.toString()}")
return NetworkResult.Error(
"network results of handlePostsResponse ${response.body().toString()}"
)
}
return NetworkResult.Error(
"network results of handlePostsResponse ${response.body().toString()}"
)
}
WHAT I NEED
A way to inject pageToken parameter dynamically only when there's a value (NOT NULL OR EMPTY) to avoid the following in final/base URL
https://www.googleapis.com/blogger/v3/blogs/4294497614198718393/posts/?maxResults=20&pageToken=null&key=API_KEY
this cause error 400 badRequest
{
"error": {
"code": 400,
"message": "We're sorry, but the value for field pageToken was not valid.",
"errors": [
{
"message": "We're sorry, but the value for field pageToken was not valid.",
"domain": "global",
"reason": "invalid"
}
]
}
}
I tried edit the PostAPIService method to look like that
#GET("posts")
suspend fun getPostList(#Query("pageToken") pageToken: String=""): Response<PostList>
and use it in viewModel like this
val response = if (token.value.isNullOrEmpty()) {
mainRepository.remoteDataSource.getPostList()
} else {
mainRepository.remoteDataSource.getPostList(token.value!!)
}
but unfortunately this causes badRequest also because of pageToken=null in BASE_URL
As docs said Retrofit ignores null query values, it means that you just need to remove pageToken from your base url and make corresponding parameter nullable in your function:
#GET("posts")
suspend fun getPostList(#Query("pageToken") pageToken: String? = null): Response<PostList
How can I refresh my token using authenticator? I need the refresh token method to return the token or null when I get 401 in my api call.
class SupportInterceptor() : Interceptor, Authenticator {
/**
* Interceptor class for setting of the headers for every request
*/
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
request = request?.newBuilder()
?.addHeader("Content-Type", "application/json")
?.addHeader("app-id", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
?.build()
return chain.proceed(request)
}
/**
* Returns a request that includes a credential to satisfy an authentication challenge in
* [response]. Returns null if the challenge cannot be satisfied.
*
* The route is best effort, it currently may not always be provided even when logically
* available. It may also not be provided when an authenticator is re-used manually in an
* application interceptor, such as when implementing client-specific retries.
*/
override fun authenticate(route: Route?, response: Response): Request? {
var requestAvailable: Request? = null
try {
return runBlocking {
when (val tokenResponse = refreshToken()) {
is Success -> {
// userPreferences.saveAccessTokens(
// tokenResponse.value.access_token!!,
// tokenResponse.value.refresh_token!!
// )
response.request.newBuilder()
.header("Authorization", "Bearer ${tokenResponse.value.access_token}")
.build()
}
else -> null
}
}
// requestAvailable = response?.request?.newBuilder()
//// ?.addHeader("Authorization", "Bearer $token")
// ?.build()
// return requestAvailable
} catch (ex: Exception) {
}
return requestAvailable
}
suspend fun refreshToken(): Either<Failure, String?> {
return withContext(Dispatchers.IO) {
try {
val PREFS_NAME = "userPref"
val sharedPref: SharedPreferences =
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
val refreshToken = sharedPref.getString(MyConstants.KEY_REFRESH_TOKEN, "")
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(baseURL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val api: TokenRefreshApi = retrofit.create(TokenRefreshApi::class.java)
val response = api.refreshAccessToken(refreshToken).execute()
// val call: Call<LogIn> = api.refreshAccessToken(refreshToken)
when (response.isSuccessful) {
false -> Either.Left(response.errorResponse())
true -> {
val editor: SharedPreferences.Editor = sharedPref.edit()
editor.putString(
MyConstants.KEY_ACCESS_TOKEN,
response.body()!!.access_token
)
editor.putString(
MyConstants.KEY_REFRESH_TOKEN,
response.body()!!.refresh_token
)
editor!!.apply()
response.body()!!.access_token
}
}
} catch (e: Exception) {
Timber.e("searchTasks: $e")
Either.Left(Failure.UnknownError)
}
}
}
}
I would first clarify the industry standard behavior for reliable clients:
Client tries an API request with an access token
If client receives a 401 it attempts to silently refresh the access token and retry the API request with the new token
If there are technical problems avoid redirecting the user to re-authenticate
Here is some plain Kotlin code of mine that does this.
Looks to me like Retrofit's Authenticator interface makes this easier, and will do the retry for you. Your code looks mostly correct and similar to mine but without the manual checks for 401s:
You need to test 401s though, and one way to do this is to add arbitrary characters to an access token during development, to simulate expiry
In case someone is having difficulty same like me.
override fun authenticate(route: Route?, response: Response): Request? {
var requestAvailable: Request? = null
try {
return runBlocking {
val tokenResponse = getNewToken()
if (!tokenResponse.isNullOrEmpty()) {
response.request.newBuilder()
.header("Authorization", "Bearer ${tokenResponse}")
.build()
} else {
null
}
}
} catch (ex: Exception) {
}
return requestAvailable
}
private fun getNewToken(): String? {
val PREFS_NAME = "userPref"
val sharedPref: SharedPreferences =
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
val refreshToken: String = sharedPref.getString("refresh_token", "").orEmpty()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_API_URL_CONSUMER)
.addConverterFactory(GsonConverterFactory.create())
.build()
val hashMap = HashMap<String, String>()
hashMap.put("refresh_token", refreshToken)
val call = retrofit.create(TokenRefreshApi::class.java).refreshAccessToken(hashMap)
val authTokenResponse = call?.execute()?.body()
if (authTokenResponse != null) {
val editor: SharedPreferences.Editor = sharedPref.edit()
editor.putString(
"access_token",
authTokenResponse!!.access_token
)
editor.putString(
"refresh_token",
authTokenResponse!!.refresh_token
)
editor!!.apply()
return authTokenResponse!!.access_token
} else {
return null
}
}
I am trying to figure out what is the best practice for handling a retrofit response.
I provide the retrofit singlton like this:
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(AuthInterceptor())
.addInterceptor(httpLoggingInterceptor)
.build()
val retrofit = Retrofit.Builder()
.client(okHttpClient)
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutinesResponseCallAdapterFactory())
.build()
val service = retrofit.create(ShowsService::class.java)
The service interface is this:
interface ShowsService {
#GET("popular")
suspend fun fetchPopularShows(): Response<PopularShows>
}
I get a list of shows from API and parse it in a repository like this:
override suspend fun getShows(): Result<List<Show>?> {
val shows = service.fetchPopularShows()
val body = shows.body()
val errorBody = shows.errorBody()
return when {
body != null -> {
Result.Success(body.shows)
}
errorBody != null -> {
Result.Error(Exception(errorBody.string()))
}
else -> {
Result.Error(Exception("Unknown error: ${shows.raw().message}"))
}
}
}
However, this feels very non-kotlin and also would probably result in code duplication eventually, can anyone point me to a sample where this is implemented in the best practice?
In principle, you could create an unwrapResponse() generic function that takes a Response<T> and returns a Result<T?> and incorporates your algorithm. By eyeball, something like this:
suspend fun <T> unwrapResponse(response: Response<T>): Result<T> {
val body = response.body()
val errorBody = response.errorBody()
return when {
body != null -> {
Result.Success(body)
}
errorBody != null -> {
Result.Error(Exception(errorBody.string()))
}
else -> {
Result.Error(Exception("Unknown error: ${response.raw().message}"))
}
}
}
You could then call unwrapResponse(service.fetchPopularShows()) to get a Result<PopularShows>.
If you really wanted to allow unwrapResponse() to return a Result<List<Show>?>, you would wind up with something like:
suspend fun <T, R> unwrapResponse(response: Response<T>, unpacker: (T) -> R?): Result<R?> {
val body = response.body()
val errorBody = response.errorBody()
return when {
body != null -> {
Result.Success(unpacker(body))
}
errorBody != null -> {
Result.Error(Exception(errorBody.string()))
}
else -> {
Result.Error(Exception("Unknown error: ${response.raw().message}"))
}
}
}
unwrapResponse(service.fetchPopularShows()) { it.shows } would then give your Result<List<Show>?>.
Again, this is all by eyeball — adjustments are likely to be needed here.
I am using Authenticator instead of Interceptor to refresh the token. I am able to detect the 401 exception and easily refresh the new token. Everything is working perfectly but the issue is following:
I am unable to call the request again, I do not want the user to hit again to call the offer.
So after execution of the code below I get a new token, it gives me a 401 error message.
My Question is: How can I call the request chain again?
Any advice on the implementation is welcome.
class OffersViewModel
val observable = ApiServiceClient.createApiUsingToken(context).getOffers(
Pref.getString(getApplication(), Pref.CUSTOMER_CODE, "")!!,
Pref.getString(getApplication(), Pref.TOKEN, "")!!
)
compositeDisposable.add(observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
responseModel.statusCode = StatusCode.START
offersRegisteredUserResponseLiveData.postValue(responseModel)
}
.subscribe({ success ->
if (success.errors.isNullOrEmpty()) {
success.statusCode = StatusCode.SUCCESS
} else {
success.statusCode = StatusCode.ERROR
}
offersRegisteredUserResponseLiveData.value = success
}, {
//HERE I GOT 401
Log.d("debug",it.message.toString())
responseModel.statusCode = StatusCode.ERROR
offersRegisteredUserResponseLiveData.value = responseModel
}, { })
)
API Service Class
/*.....Offer Screen...........*/
#GET("offers/xyz/{abc}")
fun getOffers(
#Path("abc") customerCode: String,
#Header("Authorization") authorization: String,
#Header("Content-Type") contentType: String = CONTENT_TYPE
):
Observable<OfferRegisteredUserResponseModel>
ApiClient Class
fun createApiUsingToken(context: Context?): ApiService {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder().addInterceptor(interceptor).connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.authenticator(TokenInterceptor(context)).build()
val retrofit = Retrofit.Builder()
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(Constants.BASE_URL)
.build()
var ApiServiceClient=retrofit.create(ApiService::class.java)
return retrofit.create(ApiService::class.java)
}
class TokenInterceptor
var requestAvailable: Request? = null
if (response!!.code() === 401) {
var retrofitResponse = ApiServiceClient.createToken().getTokenWithoutObserver().execute()
if (retrofitResponse != null) {
val refreshTokenResponse = retrofitResponse!!.body()
val newAccessToken = refreshTokenResponse!!.token
if (newAccessToken != null)
{
Pref.setString(MyApplication.mInstance, Pref.TOKEN, "${refreshTokenResponse.tokenType} ${refreshTokenResponse?.token}")
Pref.setString(MyApplication.mInstance, Pref.TOKEN_EXPIRES_IN, refreshTokenResponse.tokenExpirationTime.toString())
Utils.addTokenExpirationTimeToCurrentTime(MyApplication.mInstance, refreshTokenResponse.tokenExpirationTime?.toInt()!!)
try {
requestAvailable = response?.request()?.newBuilder()
?.addHeader("Content-Type", "application/json")
?.addHeader("Authorization", "Bearer " + newAccessToken)
?.build()
return requestAvailable
} catch (ex: Exception) {
}
}
} else
return null
}
return requestAvailable
Couple of things i see wrong with this.
First is that even if you "restart" the request with the new token, if you happen to make another request while the "new token" is not saved, that request is also going to fail.
Second is that i don't see that you save the new token anywhere (in SharedPrefs for example for later use).
This is how i would have do it: (preferenceHelper is SharedPrefs)
override fun authenticate(route: Route?, response: Response): Request? {
val HEADER_AUTHORIZATION = "Authorization"
// We need to have a token in order to refresh it.
val token = preferenceHelper.getAccessToken() ?: return null
synchronized(this) {
val newToken = preferenceHelper.getAccessToken() ?: return null
// Check if the request made was previously made as an authenticated request.
if (response.request().header(HEADER_AUTHORIZATION) != null) {
// If the token has changed since the request was made, use the new token.
if (newToken != token) {
return response.request()
.newBuilder()
.removeHeader(HEADER_AUTHORIZATION)
.addHeader(HEADER_AUTHORIZATION, "Bearer " + newToken)
.build()
}
val tokenResponse = ApiServiceClient.createToken().getTokenWithoutObserver().execute()
if (tokenResponse.isSuccessful) {
val userToken = tokenResponse.body() ?: return null
preferenceHelper.saveAccessToken(userToken.token)
preferenceHelper.saveRefreshToken(userToken.refreshToken)
// Retry the request with the new token.
return response.request()
.newBuilder()
.removeHeader(HEADER_AUTHORIZATION)
.addHeader(HEADER_AUTHORIZATION, "Bearer " + userToken.token)
.build()
} else {
logoutUser()
}
}
}
return null
}
I develop a RSS reader Android app. I want to API call more than one but I can't. My code is here, What am I doing wrong ?
Note;
Response will be XML as String dasd
Request size may increase (maybe 10)
ArticleServisGenerator;
object ArticleServiceGenerator {
private const val FAKE_URL = "https://api.github.com"
private val interceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
}
private val client: OkHttpClient = OkHttpClient.Builder().apply {
addInterceptor(interceptor)
}.build()
private val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(FAKE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build()
val service: ArticleService = retrofit.create(ArticleService::class.java)}
ArticleService;
interface ArticleService {
#GET
fun getArticlesFromRss(#Url url: String): Observable<ResponseBody>}
And Repository
class Repository {
fun getArticlesFromRss() {
val request: ArrayList<Observable<*>> = ArrayList()
// The number may increase here.
request.add(ArticleServiceGenerator.service.getArticlesFromRss("https://commonsware.com/blog/feed.atom"))
request.add(ArticleServiceGenerator.service.getArticlesFromRss("https://jfenn.me/blog/feeds/android.xml"))
request.add(ArticleServiceGenerator.service.getArticlesFromRss("https://arunkumar.dev/feed.xml"))
Observable.zip(request) { Any() }
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.subscribe({
print(it.toString())
}) {
print(it.toString())
}
}}
You can try something like below:
fun getArticlesFromRss() {
val request: ArrayList<Observable<ResponseBody>> = ArrayList()
request.add(ArticleServiceGenerator.service.getArticlesFromRss("https://commonsware.com/blog/feed.atom").subscribeOn(Schedulers.io()))
request.add(ArticleServiceGenerator.service.getArticlesFromRss("https://jfenn.me/blog/feeds/android.xml").subscribeOn(Schedulers.io()))
request.add(ArticleServiceGenerator.service.getArticlesFromRss("https://arunkumar.dev/feed.xml").subscribeOn(Schedulers.io()))
Observable.zip(request){ args -> Arrays.asList(args) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
print(it.toString())
}) {
print(it.toString())
}
}}
Each Observable inside zip is executed sequentially. Add subscribeOn(Schedulers.io()) to each getArticles Observable and it will execute them concurrently.
Observable<List<String>> result = Observable.zip(
observable1.subscribeOn(Schedulers.io()),
observable2.subscribeOn(Schedulers.io()),
observable3.subscribeOn(Schedulers.io()),
new Function3<ResponseType1, ResponseType2, ResponseType3, List<String>>() {
#Override
public List<String> apply(ResponseType1 type1, ResponseType2 type2, ResponseType3 type3) {
List<String> list = new ArrayList();
list.add(type1.data);
list.add(type2.data);
list.add(type3.data);
return list;
}
}
);
You just need to specify default scheduler.
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io))