In my app I have two services and both of them have a method that makes a requests and then returns an Observable of different type.
I want to display in a RecyclerView a list composed of the result of combining these two Observables. I googled about this and found the zip() method that seems to do exactly what I want. I'm trying to implement it but I don't know how to do it correctly.
While I was googling, I came up with this this article which seems to explain it clearly. Even though the author is using Singles while I am using Observables.
As far as I understand how zip() works, I know I have to pass every Observable I want to "zip" and then I must specify a function that will compose my final Observable, right?
This is my code so far:
interface InvitationService {
#GET("foo/{userId}")
fun getFooByUser(#Path("userId") userId: String): Observable<Response<ArrayList<Foo>>>
}
interface InvitationService {
#GET("bar/{userId}")
fun getBarByUser(#Path("userId") userId: String): Observable<Response<ArrayList<Bar>>>
}
class FooRemoteDataSource : FooDataSource {
private var apiService: FooService
fun getFooByUser(userId:String) {
return apiService.getFooByUser(userId)
}
}
class BarRemoteDataSource : BarDataSource {
private var apiService: BarService
fun getBarByUser(userId:String) {
return apiService.getBarByUser(userId)
}
}
class FooRepository(private val remoteDataSource: InvitationRemoteDataSource) : FooDataSource {
override fun getFooByUser(userId: String): Observable<Response<ArrayList<Foo>>> {
return remoteDataSource.getFooByUser(userId)
}
}
class BarRepository(private val remoteDataSource: BarRemoteDataSource) : BarDataSource {
override fun getBarByUser(userId: String): Observable<Response<ArrayList<Bar>>> {
return remoteDataSource.getBarByUser(userId)
}
}
And here is where I'm actually stuck:
class ListPresenter(var listFragment: ListContract.View?,
val fooRepository: FooRepository,
val barRepository: BarRepository) : ListContract.Presenter {
fun start() {
loadList()
}
private fun loadLists() {
//HERE IS WHERE IM STUCK
Observable.zip(fooRepository.getFooByUser(userId).subscribeOn(Schedulers.io()),
barRepository.getBarByUser(userId).subscribeOn(Schedulers.io()),
)
// AFTER 'ZIPPING' THE OBSERVABLES
// I NEED TO UPDATE THE VIEW ACCORDINGLY
}
}
I don't know how to call zip() properly, I know that I must pass a function but I don't get it because in the article linked above the author is using a Function3 because he has 3 Observables.
As I only have 2, I don't know how to do it. If open curly braces after a comma inside the method args, it requires me to return a BiFunction<ArrayList<Foo>, ArrayList<Bar>> which is what I don't know how to specify.
Would someone explain it to me?
For Kotlin you should use RxKotlin rather than RxJava. BiFunction, Function3 come from RxJava. With RxKotlin you can use lambdas instead.
As far as I understand how zip() works, I know I have to pass every Observable I want to "zip" and then I must specify a function that will compose my final Observable, right?
Correct, and here is a minimal example, which demonstrates how to do it.
Example 1
val observable1 = listOf(1, 2, 3).toObservable()
val observable2 = listOf(4, 5, 6).toObservable()
val zipped = Observables.zip(observable1, observable2) { o1, o2 -> o1 * o2}
In this example you have two observables, each emitting integers. You pass them to zip and as third argument a lambda which defines a way to "cobmine them". In this case it multiplies them.
The resulting observable zipped will emit: 4, 10 and 18.
Example 2
Here another example zipping three observables which are not all of the same type:
val obs1 = listOf("on", "tw", "thre").toObservable()
val obs2 = listOf("n", "o", "e").toObservable()
val obs3 = listOf(1, 2, 3).toObservable()
val zipped = Observables.zip(obs1, obs2, obs3) { o1, o2, o3 ->
"$o1$o2 = $o3"
}
Here, each element of the resulting observable will be a string: "one = 1", "two = 2", "three = 3"
Zipping two Observables of different types using BiFunction
override fun getCommoditiesAndAddresses() {
view.showProgress()
view.hideViews()
Observable.zip(Commo24Retrofit.createAuthService(RateAPIService::class.java)
.getCommodities(),
Commo24Retrofit.createAuthService(RateAPIService::class.java)
.getLocations(GetLocationsRequest(getOrgId())),
BiFunction { commodityResponse: GetCommoditiesResponse, locationsResponse: GetLocationsResponse ->
handleCommoditiesAndAddresses(commodityResponse, locationsResponse)
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
view.hideProgress()
view.showViews()
view.handleCommodities(it?.commodities)
view.handleLocations(it?.locations)
}, { throwable ->
view.hideProgress()
view.handleFailure(throwable.getErrorMessage(context))
})
}
Look, how I'm handling the response:
private fun handleCommoditiesAndAddresses(commodityResponse: GetCommoditiesResponse, locationsResponse: GetLocationsResponse): CommoditiesAddresses {
return CommoditiesAddresses(commodityResponse.commodityList, locationsResponse.addressList)
}
Here, check the API Service:
interface RateAPIService {
#POST("get-org-address")
fun getLocations(#Body getLocationsRequest: GetLocationsRequest): Observable<GetLocationsResponse>
#POST("get-commodity-list")
fun getCommodities(): Observable<GetCommoditiesResponse>
}
If you have any doubt you can comment it out.
Related
I'm creating offline first app as my side project using rxKotlin, MVVM + Clean Architecture and yesterday I decided to get ride off boilerplate subscribeOn and observeOn by using transformers. I quickly realized that apply function of transformers are ignored.
Here is code of my base completable use case (interactor):
abstract class CompletableUseCase(private val transformer: CompletableTransformer) {
abstract fun createCompletable(data: Map<String, Any>? = null) : Completable
fun completable(data: Map<String, Any>? = null) : Completable {
return createCompletable(data).compose(transformer)
}
}
And here is implementation of specific interactor:
class SaveRouteInteractor(
transformer: CompletableTransformer,
private val routeRepository: RouteRepository
) : CompletableUseCase(transformer) {
companion object {
private const val PARAM_ROUTE = "param_route"
}
fun saveRoute(route: Route) : Completable {
val data = HashMap<String, Route>()
data[PARAM_ROUTE] = route
return completable(data)
}
override fun createCompletable(data: Map<String, Any>?): Completable {
val routeEntity = data?.get(PARAM_ROUTE)
routeEntity?.let {
return routeRepository.saveRoute(routeEntity as Route)
} ?: return Completable.error(IllegalArgumentException("Argument #route must be provided."))
}
}
My custom transformer that is passed to the constructor of SaveRouteInteractor:
class IOCompletableTransformer(private val mainThreadScheduler: Scheduler) : CompletableTransformer {
override fun apply(upstream: Completable): CompletableSource {
return upstream.subscribeOn(Schedulers.io()).observeOn(mainThreadScheduler)
}
}
And implementation of RouteRepository method:
override fun saveRoute(route: Route): Completable {
return localRouteSource.saveRoute(route)
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
.flatMapCompletable { localRouteSource.updateRouteID(route.routeId, it) }
}
I'm using Room as my local source so after calling save interactor in my ViewModel I'm getting IlligalStateException telling me that I'm not allowed to access database on the main thread.
Maybe I'm missing something but it seems that transform function is ignored. I debugged this method and it is applying subscribeOn and observeOn to the upstream.
Thanks for help in advance,
Pace!
It's hard to tell you where the issue is because the code is partial.
For example here:
return localRouteSource.saveRoute(route)
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
.flatMapCompletable { localRouteSource.updateRouteID(route.routeId, it) }
I suppose the localRouteSource.saveRoute() is using the interactor you show us but it is not clear how remoteRouteSource.saveRoute() or localRouteSource.updateRouteID() are implemented.
they also need to be subscribed on the IO thread.
As a rule of thumb you should switch thread when you KNOW that you need it.
In other words, you should use subscribeOn() in places where you know you are doing IO as close as possible to the actual job. ObserveOn instead is to be used when you know you need to obtain those results in the UI thread and that you might get in some other thread.
in your example there's absolutely no need to keep using observeOn(MAIN_THREAD), the only time you do need it (I suppose) is when you want to show the result.
A couple of other things:
This code
override fun createCompletable(data: Map<String, Any>?): Completable {
val routeEntity = data?.get(PARAM_ROUTE)
routeEntity?.let {
return routeRepository.saveRoute(routeEntity as Route)
} ?: return Completable.error(IllegalArgumentException("Argument #route must be provided."))
}
it is evaluated at the time when the method is called rather then when the completable is subscribed.
In other words it break the Rx contract and compute data?.get(PARAM_ROUTE) when you call the method. If it is immutable there's no much difference, but if it can change value during execution it should be wrapped in a Completable.defer { }
Finally, here
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
you are modyfing something outside the chain (route.routeId = localID), this is called a side effect.
be careful with those kind of stuff, Rx is build in a way that is safer to be used with immutable objects.
I personally wouldn't mind too much as long as you understand what's going on and when it could create issues.
I'm trying to read a list of objects from the database and mapping it to another type of list.
// Returns either a Failure or the expected result
suspend fun getCountries(): Either<Failure, List<CountryItem>> {
// Get the result from the database
val result = countryLocalDataSource.getCountries()
// Left means Failure
if (result.isLeft) {
// Retrieve the error from the database
lateinit var error: Failure
result.either({
error = it
}, {})
// Return the result
return Either.Left(error)
}
// The database returns a List of Country objects, we need to map it to another object (CountryItem)
val countryItems: MutableList<CountryItem> = mutableListOf()
// Iterate the Country List and construct a new List of CountryItems
result.map { countries -> {
countries.forEach {
// Assign some values from an Enum (localized string resources)
val countryEnumValue = Countries.fromId(it.id)
countryEnumValue?.let { countryIt ->
val countryStringNameRes = countryIt.nameStringRes;
// Create the new CountryItem object (#StringRes value: Int, isSelected: Bool)
countryItems.add(CountryItem(countryStringNameRes, false))
}
}
} }
// Because this is a success, return as Right with the newly created List of CountryItems
return Either.Right(countryItems)
}
For the sake of readability I didn't included the whole Repository or the DAO classes and I have left comments in the code snippet above.
In a nutshell: I'm using Kotlin's Coroutines for accessing the database in a separated thread and I'm handling the response on the UI Thread. Using the Either class in order to return two different results (failure or success).
The above code works, however It's too ugly. Is this the right approach to deliver the result?
What I'm trying to do is to refactor the code above.
The whole problem is caused by the two different object types. The Database Data Source API is returning an Either<Failure, List<Country>>, meanwhile the function is expected to return an Either<Failure, List<CountryItem>>.
I can't deliver a List<CountryItem> directly from the Database Data Source API, because Android Studio doesn't let me compile the project (entities implementing interfaces, compile error, etc.). What I'm trying to achieve is to map the Either result in a nicer way.
Try using Kotlin's Result
So in your case you can write something like:
return result.mapCatching { list: List<Country> -> /*return here List<CountryItem>>*/ }
And for checking result call:
result.fold(
onSuccess = {...},
onFailure = {...}
)
In order to invoke a constructor you should call Result.success(T) or Result.failure(Throwable)
Unfortunately, you'll also need to suppress use-as-return-type warnings How to
You can simplify by checking the type of Either and accessing the value directly. In your case:
access Left via result.a -> Failure
access Right via result.b -> List<Country>
ex:
when (result) {
is Either.Left -> {
val failure: Failure = result.b
...
}
is Either.Right -> {
val countries: List<Country> = result.b
...
}
}
An alternative is to use the either() function (normally this is called fold()):
result.either(
{ /** called when Left; it == Failure */ },
{ /** called when Right; it == List<Country> */ }
)
Assume your Country class is defined as follow:
data class Country(val name: String) {}
and your CountryItem class is defined as follow:
data class CountryItem(private val name: String, private val population: Int) {}
and your CountryLocalDataSource class with a method getCountries() like this:
class DataSource {
suspend fun getCountries(): Either<Exception, List<Country>> {
return Either.Right(listOf(Country("USA"), Country("France")))
//return Either.Left(Exception("Error!!!"))
}
}
then the answer to your question would be:
suspend fun getCountryItems(): Either<Exception, List<CountryItem>> {
when (val countriesOrFail = DataSource().getCountries()) {
is Either.Left -> {
return Either.Left(countriesOrFail.a)
}
is Either.Right -> {
val countryItems = countriesOrFail.b.map {
CountryItem(it.name, 1000)
}
return Either.Right(countryItems)
}
}
}
To call your getCountryItems(), here is an example:
suspend fun main() {
when (val countriesOrFail = getCountryItems()) {
is Either.Left -> {
println(countriesOrFail.a.message)
}
is Either.Right -> {
println(countriesOrFail.b)
}
}
}
Here's the sample code in the playground: https://pl.kotl.in/iiSrkv3QJ
A note about your map function:
I'm guessing you don't actually need the result to be a MutableList<CountryItem> but you had to define so because you want to add an element as you iterate through the input list List<Country>.
Perhaps the following is the case: If you have a List<Country> with 2 elements like in the example, and you want to map so that the result becomes a List<CountryItem> with also 2 corresponding elements, then you don't need to call forEach inside a fun that gets passed to the higher-order function map. But this may be an entirely new question.
I've inherited this codebase which uses RxJava2 and kotlin with a rather peculiar Result pattern for API calls. i.e. all API calls return Singles with a Result object (which is a sealed class of Success and Error types as shown below) i.e.
sealed class Result<T, E> {
data class Success<T, E>(
val data: T
): Result<T, E>()
data class Error<T, E>(
val error: E
): Result<T, E>()
}
Now I'm trying to chain together a bunch of API calls of these but need to terminate the chain on the first Result.Error in it and continue if not.
The only way I can think of is to zip all of the Singles and then have a zipper function that checks the type of each parameter and returns a Result.Error() with the first error it encounters. i.e. something like,
Singles.zip(
repo1.makeCall1(arg),
repo1.makeCall2(arg2),
repo2.makeCall1(arg3)
) { result1, result2, result3 ->
val data1 = when (result1) {
is Result.Error -> return#zip Result.Error(result1.error)
is Result.Success -> result1.data
}
val data2 = when (result2) {
is Result.Error -> return#zip Result.Error(result2.error)
is Result.Success -> result2.data
}
val data3 = when (result3) {
is Result.Error -> return#zip Result.Error(result3.error)
is Result.Success -> result3.data
}
return#zip Result.Success(MergedData(data1, data2, data3))
}
which works but looks really weird (and feels like a code smell with this huge ass zipper method). Also does not allow me to chain anything more after the last method (that checks if the Result is a Success / Error).
I feel it would be a lot more readable to be able to chain these calls and terminate on the first error but I don't know enough Rx to do this. Is there an operator or an approach that could help make this better?
You can get original Single behavior by reversing what your codebase already does.
Create transformer which will extract data from api call or throw error on error. First error will terminate zip.
public <T, E extends Throwable> SingleTransformer<Result<T, E>, T> transform() {
return source -> source.flatMap(result -> {
if (result instanceof Result.Success)
return Single.just(((Success<T, E>) result).getData());
else
return Single.error(((Error<T, E>) result).getError());
});
}
Use it with repo.makeCall(arg).compose(transform())
Hope it helps.
Out of the box, RxJava would "abort on the first error" because Observable and Single (which is akin to Task/Future/Promise) has "monadic qualities". But as Result<*, *> explicitly makes errors be handled on the "success" path to avoid aborting the stream, we might want to consider a different route than letting Rx go to terminal events - because the existing code expects it to be on the success path. Terminal events should be for "the world is ending" exceptions, not ones we actually expect and can handle.
I had some ideas but I think the only thing you can do is reduce the number of lines it takes to do this instead of flat-out removing it.
Technically we are trying to re-implement the Either<E, T> monad here from Arrow, but knowing that, we can reduce the number of lines with some tricks:
sealed class Result<T, E>(
open val error: E? = null,
open val data: T? = null
) {
data class Success<T>(
override val data: T
): Result<T, Nothing?>()
data class Error<E>(
override val error: E
): Result<Nothing?, E>()
}
fun <E> E.wrapWithError(): Result.Error<E> = Result.Error(this) // similar to `Either.asLeft()`
fun <T> T.wrapWithSuccess(): Result.Success<T> = Result.Success(this) // similar to `Either.asRight()`
fun blah() {
Singles.zip(
repo1.makeCall1(arg),
repo1.makeCall2(arg2),
repo2.makeCall1(arg3)
) { result1, result2, result3 ->
val data1 = result1.data ?: return#zip result1.error.wrapWithError()
val data2 = result2.data ?: return#zip result2.error.wrapWithError()
val data3 = result3.data ?: return#zip result3.error.wrapWithError()
Result.Success(MergedData(data1, data2, data3))
}
}
What do you think about this code block:
Single.zip(
Single.just(Result.Error(error = 9)),
Single.just(Result.Success(data = 10)),
Single.just(Result.Success(data = 11)),
Function3<Result<Int, Int>, Result<Int, Int>, Result<Int, Int>, List<Result<Int, Int>>> { t1, t2, t3 ->
mutableListOf(t1, t2, t3)
})
.map { list ->
list.forEach {
if (it is Result.Error){
return#map it
}
}
return#map Result
} // or do more chain here.
.subscribe()
I combine the results into a list, then map it to your expected result. It's much easier to read.
We already know that Observable can only have one argument as its generic type. Observable
I assume that I have 2 network calls which return 2 data type: UserResponse and WorkResponse.
And I want to call 2 these APIs step by step, getUser then getWork.
Finally I subscribe to them and got only one data type, obviously that's WorkResponse because getWork is the last API call in the upper stream Observable<WorkResponse>.
But in the subscribe code block, I want to get both UserResponse and WorkResponse. So how can I achieve that?
1 - Some people say that I should create a container class to contain both UserResponse and WorkResponse then I can get these data types from that container in subcribe code block.
2 - Create a temporary variable to hold userResponse then access to it from subscibe code block, like the following:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var tempUserResponse: UserResponse? = null
Observable.just("Prepare call API")
.flatMap {
apiGetUser()
}.flatMap { userResponse ->
tempUserResponse = userResponse // Save temporarily userResponse to pass it to subscribe code block
apiGetWork()
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { workResponse ->
Log.e("TAG", "userResponse = " + tempUserResponse)
Log.e("TAG", "workResponse = " + workResponse)
}
}
private fun apiGetUser(): Observable<UserResponse> {
// Fake API to get User
return Observable.just(UserResponse())
}
private fun apiGetWork(): Observable<Work> {
// Fake API to get Work
return Observable.just(Work())
}
class Work
class UserResponse
}
3 - Is there another way?
Please give me answer, thanks so much!
EDIT: Thanks for all your answers, guys! All your answers, may be different at the ways to implement (using nested flatMap or using zip) but we all need to use a 3rd class as a container class for all objects we need.
Built-in container classes, we have: Pair<A, B> and Triple<A, B, C>
. If we need more arguments, we must create our own ones
You can use zip to get one object from your 2 results:
public class MergedObject{
private Work workResponse;
private UserResponse userResponse;
MergedObject(final Work workResponse, final UserResponse userResponse){
this.workResponse= workResponse;
this.userResponse= userResponse;
}
// getter / setter
}
and then
Observable.just("Prepare call API")
.flatMap {
apiGetUser()
}.zipWith(apiGetWork(), ( userResponse, workResponse ) ->
return new MergedObject(workResponse, userResponse)
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { mergedObject->
Log.e("TAG", "userResponse = " + mergedObject.userResponse)
Log.e("TAG", "workResponse = " + mergedObject.workResponse)
}
(Written without any testing you may need to adapt a bit)
You could overload flatmap and do the following:
fun test() {
Observable.just("Prepare call API")
.flatMap {
apiGetUser()
}.flatMap(
Function<UserResponse, Observable<Work>> {
return#Function apiGetWork()
},
BiFunction<UserResponse, Work, Pair<UserResponse, Work>> { userResponse, work ->
Pair(userResponse, work)
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { response ->
Log.e("TAG", "userResponse = " + response.first.name)
Log.e("TAG", "workResponse = " + response.second.name)
}
}
private fun apiGetUser(): Observable<UserResponse> {
// Fake API to get User
return Observable.just(UserResponse())
}
private fun apiGetWork(): Observable<Work> {
// Fake API to get Work
return Observable.just(Work())
}
class Work {
val name = "world"
}
class UserResponse {
val name = "hello"
}
Basically returning a Pair<UserResponse, Work> :)
I am trying to consume an API from thesportsdb to display lastmatch from specific league. in my recyclerview I want to show the team badge for every teams but when I request the lastmatch API it didn't include the team badge, only the id for each team and if I want to show the badge it require me to request the team profile which includes the url for the team badge.
Since I am new to rxJava so I am still familiarize myself with it. some posts suggest using flatmap but it kind a difficult for beginner like me to implement it.
this is the retrofitService:
interface FootballRest {
#GET("eventspastleague.php")
fun getLastmatch(#Query("id") id:String) : Flowable<FootballMatch>
#GET("lookupteam.php")
fun getTeam(#Query("id") id:String) : Flowable<Teams>
}
I used repository pattern
class MatchRepositoryImpl(private val footballRest: FootballRest) : MatchRepository {
override fun getFootballMatch(id: String): Flowable<FootballMatch> = footballRest.getLastmatch(id)
override fun getTeams(id: String): Flowable<Teams> =
footballRest.getTeam(id)
}
and this is the presenter who make the call and send the data to the view:
class MainPresenter(val mView : MainContract.View, val matchRepositoryImpl: MatchRepositoryImpl) : MainContract.Presenter{
val compositeDisposable = CompositeDisposable()
val requestMatch = matchRepositoryImpl.getFootballMatch("4328")
val requestTeam = matchRepositoryImpl.getTeams()
override fun getFootballMatchData() {
compositeDisposable.add(matchRepositoryImpl.getFootballMatch("4328")
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe{
mView.displayFootballMatch(it.events)
})
}
so far I only show the last match result, but I want also to show the badge team on the list.
You could use a map operator combined with lastElement().blockingGet() for the second Observable for this and then return a Pair of results. A simple example could be as follows:
#Test
public fun test1() {
Observable.just(1)
.map {
// here 'it' variable is calculated already so it can be passed to the second observable
val result = Observable.just(2).lastElement().blockingGet()
Pair<Int, Int>(it, result)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { t -> System.out.println("First : " + t?.first + ", second : " + t?.second) }
Thread.sleep(1000)
}
Output
1 2
If your second Observable depends on the result of the first one then just use the it variable inside the map operator and pass it to whatever place it's needed. So, if using the previous example your code could be converted to this:
override fun getFootballMatchData() {
compositeDisposable.add(matchRepositoryImpl.getFootballMatch("4328").toObservable(
.map {
// here 'it' variable is calculated already so it can be passed to the second observable
val next = matchRepositoryImpl.getTeams(it).toObservable().lastElement().blockingGet()
Pair<Int, Int>(it, next)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe{ t ->
mView.displayFootballMatch(t.first)
mView.displayBadgeTeam(t.second)
})
}
Rather than use a blockingGet operator its probably easier for you to use a flatmap and return all of this data as a single stream.
You could achieve this by combining the flatmap and zip operator. This would look something like the following, where MatchData holds both the FootballMatch data along with the homeTeam and awayTeam data.
data class MatchData(val footballMatch: FootballMatch, val homeTeam: Teams, val awayTeam: Teams)
Your flatmap operation would then need to invoke the getTeams method for both home and away team which can then be combined with the footballMatch data through the zip oprator.
override fun getFootballMatchData() {
compositeDisposable.add(matchRepositoryImpl.getFootballMatch("4328")
.subscribeOn(Schedulers.io())
.flatMap { footballMatch ->
Flowable.zip(
matchRepositoryImpl.getTeams(footballMatch.idHomeTeam),
matchRepositoryImpl.getTeams(footballMatch.idAwayTeam),
BiFunction { homeTeam: Teams, awayTeam: Teams ->
MatchData(
footballMatch = footballMatch,
homeTeam = homeTeam,
awayTeam = awayTeam)
}
)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
mView.displayFootballMatch(it)
})
}