Kotlin Generic List Parsing - android

I was looking other questions but this one is using a different method.
I’m using MVVM in my Android app.
Actually this is the way that I’m getting data from my server:
Inject dataManager into viewModel.
viewModel calls dataManager->fetchUsers
fetchUsers make request to server and return an Observable of Several which in this case should be Several but it’s generic.
viewModel subscribe to this request and expect a Several.
At this point everything works besides Several doesn’t have a list of User. This several have a list of LinkedTreeMap
I tried to change my dataManager to return a string then map the response in my viewModel but the thing with this is that I will have to do that in every request.
Also I tried to map the request in my dataManager but I got the same link tree map array.
The thing with TypeToken approach is that I have to map in my viewModel.
UPDATED
UsersViewModel
private fun fetchUsers() {
isLoading.set(false)
compositeDisposable += dataManager.GET<User>(classJava = User::class)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( { response ->
isLoading.set(false)
// Here the response should be a several with a list of users.
}, { err ->
isLoading.set(false)
})
}
GET function:
override fun <T> GET(classJava: KClass<*>): Single<Several<T>> {
return Rx2AndroidNetworking.get("EndPoint")
.addHeaders("Authorization", apiHeader.protectedAPIHeader.accessToken!!)
.setOkHttpClient(APIClient.getUnsafeClient())
.build()
// Also I tested with
// .getObjectSingle(Several::class.java) // The thing is that I can't assign the type <T> with this approach.
.stringSingle
.map {
val fromJSON = fromJson<Several<T>>(it)
fromJSON
}
}
fromJson function:
inline fun <reified T> fromJson(json: String): T = Gson().fromJson(json, object: TypeToken<T>() {}.type)
Several.kt
data class Several<T>(val items: MutableList<T>)
If I change my GET function to return a String then in UsersViewModel I add a .map like this
private fun fetchUsers() {
isLoading.set(false)
compositeDisposable += dataManager.GET<User>(classJava = User::class)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(
val severalUsers = fromJson<Several<User>>(it)
severalUsers
)
.subscribe( { response ->
isLoading.set(false)
// usersLiveData.value = response
}, { err ->
Log.e("tag", "Ocurred some error")
isLoading.set(false)
})
}
Then I would have a list of Users as expected but What I dont want to do is map the response in UsersViewModel because I would have to do in every single request that expect a list of items.
Is there any way that I could get a several object without map in my viewmodel?

Related

Change Data type in the RxJava2 chain

I am new to RxJava2 and its methods.
I need to change the data type which is emitted from an Observable.
Say, I have a data class like below.
data class SomeClass (val type: String)
An API returns ArrayList<SomeClass>, this works fine on the current implementation using RxJava2 and RxAndroid.
apiService.getPrice(code)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(singleObserver)
I have to change/transform the data from ArrayList<SomeClass> to HashMap<someClassObject.type, ArrayList<SomeClass>>. I am trying operators to make this happen, but no operator will allow changing the datatype being observed.
Transform based on:
Consumer<ArrayList<SomeClass>> { response ->
val mapped = HashMap<String, ArrayList<SomeClass>>()
response.forEach { someClassObj ->
val type = someClassObj.type!!
if (mapped.containsKey(type)) {
mapped[district]?.add(someClassObj)
} else {
val list = ArrayList<SomeClass>()
list.add(someClassObj)
mapped[type] = list
}
}
}
I am thinking to use two different observables, in which Observable data #2 is based on the response of Observable data #1 (ArrayList<SomeClass>). But, I am not sure if this works. Any better or efficient way to achieve this?
Try map:
apiService.getPrice(code)
.map { response ->
val mapped = HashMap<String, ArrayList<SomeClass>>()
response.forEach { someClassObj ->
val type = someClassObj.type!!
if (mapped.containsKey(type)) {
mapped[district]?.add(someClassObj)
} else {
val list = ArrayList<SomeClass>()
list.add(someClassObj)
mapped[type] = list
}
}
mapped
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(singleObserver);

android -MutableLiveData doesn't observe on new data

I'm using mvvm and android architecture component , i'm new in this architecture .
in my application , I get some data from web service and show them in recycleView , it works fine .
then I've a button for adding new data , when the user input the data , it goes into web service , then I have to get the data and update my adapter again.
this is my code in activity:
private fun getUserCats() {
vm.getCats().observe(this, Observer {
if(it!=null) {
rc_cats.visibility= View.VISIBLE
pb.visibility=View.GONE
catAdapter.reloadData(it)
}
})
}
this is view model :
class CategoryViewModel(private val model:CategoryModel): ViewModel() {
private lateinit var catsLiveData:MutableLiveData<MutableList<Cat>>
fun getCats():MutableLiveData<MutableList<Cat>>{
if(!::catsLiveData.isInitialized){
catsLiveData=model.getCats()
}
return catsLiveData;
}
fun addCat(catName:String){
model.addCat(catName)
}
}
and this is my model class:
class CategoryModel(
private val netManager: NetManager,
private val sharedPrefManager: SharedPrefManager) {
private lateinit var categoryDao: CategoryDao
private lateinit var dbConnection: DbConnection
private lateinit var lastUpdate: LastUpdate
fun getCats(): MutableLiveData<MutableList<Cat>> {
dbConnection = DbConnection.getInstance(MyApp.INSTANCE)!!
categoryDao = dbConnection.CategoryDao()
lastUpdate = LastUpdate(MyApp.INSTANCE)
if (netManager.isConnected!!) {
return getCatsOnline();
} else {
return getCatsOffline();
}
}
fun addCat(catName: String) {
val Category = ApiConnection.client.create(Category::class.java)
Category.newCategory(catName, sharedPrefManager.getUid())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ success ->
getCatsOnline()
}, { error ->
Log.v("this", "ErrorNewCat " + error.localizedMessage)
}
)
}
private fun getCatsOnline(): MutableLiveData<MutableList<Cat>> {
Log.v("this", "online ");
var list: MutableLiveData<MutableList<Cat>> = MutableLiveData()
list = getCatsOffline()
val getCats = ApiConnection.client.create(Category::class.java)
getCats.getCats(sharedPrefManager.getUid(), lastUpdate.getLastCatDate())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ success ->
list += success.cats
lastUpdate.setLastCatDate()
Observable.just(DbConnection)
.subscribeOn(Schedulers.io())
.subscribe({ db ->
categoryDao.insert(success.cats)
})
}, { error ->
Log.v("this", "ErrorGetCats " + error.localizedMessage);
}
)
return list;
}
I call getCat from activity and it goes into model and send it to my web service , after it was successful I call getCatsOnline method to get the data again from webservice .
as I debugged , it gets the data but it doesn't notify my activity , I mean the observer is not triggered in my activity .
how can I fix this ? what is wrong with my code?
You have made several different mistakes of varying importance in LiveData and RxJava usage, as well as MVVM design itself.
LiveData and RxJava
Note that LiveData and RxJava are streams. They are not one time use, so you need to observe the same LiveData object, and more importantly that same LiveData object needs to get updated.
If you look at getCatsOnline() method, every time the method gets called it's creating a whole new LiveData instance. That instance is different from the previous LiveData object, so whatever that is listening to the previous LiveData object won't get notified to the new change.
And few additional tips:
In getCatsOnline() you are subscribing to an Observable inside of another subscriber. That is common mistake from beginners who treat RxJava as a call back. It is not a call back, and you need to chain these calls.
Do not subscribe in Model layer, because it breaks the stream and you cannot tell when to unsubscribe.
It does not make sense to ever use AndroidSchedulers.mainThread(). There is no need to switch to main thread in Model layer especially since LiveData observers only run on main thread.
Do not expose MutableLiveData to other layer. Just return as LiveData.
One last thing I want to point out is that you are using RxJava and LiveData together. Since you are new to both, I recommend you to stick with just one of them. If you must need to use both, use LiveDataReactiveStreams to bridge these two correctly.
Design
How to fix all this? I am guessing that what you are trying to do is to:
(1) view needs category -> (2) get categories from the server -> (3) create/update an observable list object with the new cats, and independently keep the result in DB -> (4) list instance should notify activity automatically.
It is difficult to pull this off correctly because you have this list instance that you have to manually create and update. You also need to worry about where and how long to keep this list instance.
A better design would be:
(1) view needs category -> (2) get a LiveData from DB and observe -> (3) get new categories from the server and update DB with the server response -> (4) view is notified automatically because it's been observing DB!
This is much easier to implement because it has this one way dependency: View -> DB -> Server
Example CategoryModel:
class CategoryModel(
private val netManager: NetManager,
private val sharedPrefManager: SharedPrefManager) {
private val categoryDao: CategoryDao
private val dbConnection: DbConnection
private var lastUpdate: LastUpdate // Maybe store this value in more persistent place..
fun getInstance(netManager: NetManager, sharedPrefManager: SharedPrefManager) {
// ... singleton
}
fun getCats(): Observable<List<Cat>> {
return getCatsOffline();
}
// Notice this method returns just Completable. Any new data should be observed through `getCats()` method.
fun refreshCats(): Completable {
val getCats = ApiConnection.client.create(Category::class.java)
// getCats method may return a Single
return getCats.getCats(sharedPrefManager.getUid(), lastUpdate.getLastCatDate())
.flatMap { success -> categoryDao.insert(success.cats) } // insert to db
.doOnSuccess { lastUpdate.setLastCatDate() }
.ignoreElement()
.subscribeOn(Schedulers.io())
}
fun addCat(catName: String): Completable {
val Category = ApiConnection.client.create(Category::class.java)
// newCategory may return a Single
return Category.newCategory(catName, sharedPrefManager.getUid())
.ignoreElement()
.andThen(refreshCats())
.subscribeOn(Schedulers.io())
)
}
}
I recommend you to read through Guide to App Architecture and one of these livedata-mvvm example app from Google.

How to zip two Observables in Android?

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.

How to gey body() response using RxJava in Android

I am new to Kotlin and I am making a method that makes a call to an interface of Endpoints and uses one of the methods present there. I am using Observable<> instead of Call<> into the response. I wanted to know how to obtain the response body() in the "result" above. This is my method
private fun refreshUser(userLogin: String) {
executor.execute {
// Check if user was fetched recently
val userExists = userDao.hasUser(userLogin, getMaxRefreshTime(Date())) != null
// If user have to be updated
if (!userExists) {
disposable = endpoints.getUser(userLogin)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> /*Get the response body() HERE*/},
{ error -> Log.e("ERROR", error.message) }
)
}
}
}
It all depends on how you have defined the Retrofit interface. In order to get the Response you need to return something from the interface that looks like:
fun getUsers() : Observable<Response<User>>
Then inside { result -> /*Get the response body() HERE*/}, you will get something of the form Response<User>, which has the response's body.
Also to note, you do not need to enclosing executor if you leverage Room for the dao interactions; it has RxJava support. You can use RxJava operators to combine the dao lookup with the server call.
See this tutorial
https://medium.freecodecamp.org/rxandroid-and-kotlin-part-1-f0382dc26ed8
//Kotlin
Observable.just("Hello World")
.subscribeOn(Schedulers.newThread())
//each subscription is going to be on a new thread.
.observeOn(AndroidSchedulers.mainThread()))
//observation on the main thread
//Now our subscriber!
.subscribe(object:Subscriber<String>(){
override fun onCompleted() {
//Completed
}
override fun onError(e: Throwable?) {
//TODO : Handle error here
}
override fun onNext(t: String?) {
Log.e("Output",t);
}
})
if you wanna use retrofit 2 and rxjava 2
https://medium.com/#elye.project/kotlin-and-retrofit-2-tutorial-with-working-codes-333a4422a890
interface WikiApiService {
#GET("api.php")
fun hitCountCheck(#Query("action") action: String,
#Query("format") format: String,
#Query("list") list: String,
#Query("srsearch") srsearch: String):
Observable<Model.Result>
}
Observable is the class response.
private fun beginSearch(srsearch: String) {
disposable =
wikiApiServe.hitCountCheck("query", "json", "search", srsearch)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> showResult(result.query.searchinfo.totalhits) },
{ error -> showError(error.message) }
)
}
If, as you mentioned to #Emmanuel, the return type of your getUser() method is Observable<Response<User>> then calling result.body() will yield the resulting User.
{ result ->
val user: User = result.body()
}
If however, you are looking for the the raw response, you can instead call result.raw().body(); which will return an okhttp3.ResponseBody type.
{ result ->
val body: ResponseBody = result.raw().body()
val text: String = body.string()
}

Chain a request in retrofit using rxJava2 and populate the result in recyclerview

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)
})
}

Categories

Resources