Retrofit + RxJava chain multiple requests and aggregate the result - android

I am looking for the way how to chain multiple but same API requests with different parameters. So far my method looks like this:
#Override
public Observable<List<Entity>> getResult(Integer from, Integer to, Integer limit) {
MyService myService = restClient.getMyService();
if (null != from && null != to) {
Observable<List<Response>> responseObservable = myService.get(from, limit);
for (int i = from + 1; i <= to; i++) {
responseObservable = Observable.concat(responseObservable, myService.get(i, limit));
}
return responseObservable.map(mapResponseToEntity);
} else {
int fromParameter = null == from ? DEFAULT_FROM : from;
return myService.get(fromParameter, limit).map(mapResponseToEntity);
}
}
I expected that concat method combines Oservables data into one stream and returns combined Observable but I am getting only the last one calls result. However, in logcat I can see that correct number of calls to API was made.

Try using Observable.merge() and Observable.toList() as follows:
List<Observable<Response>> observables = new ArrayList();
// add observables to the list here...
Subscription subscription = Observable.merge(observables)
.toList()
.single()
.subscribe(...); // subscribe to List<Response>

Related

Android mediator live data combine 2 live data into one stream and one trigger

I have 2 live data, I add them as sources to a mediator live data, I expose this from a view model for a fragment to observe.
When either of the live data changes it triggers the onChanged method of the mediator live data, which means my observer gets triggered twice, what I want is to combine the two and observe just one stream that only triggers once, is there something I'm missing or do I need to roll my own method for this? currently I do this, which triggers twice
SOME CODE REMOVED FOR BREVITY
public CardViewModel(#NonNull Application application , int clicks, String[] cardArgs){
sentenceRepository = new SentenceRepository(application);
cards = Transformations.switchMap(search, mySearch -> sentenceRepository.searchLiveCardListByWordTypeAndWordDescriptionAndSearchWord(cardArgs[0],cardArgs[1],mySearch));
groupRepository = new GroupRepository(application);
groups = groupRepository.getGroupsByWordDescriptionAndWordType(cardArgs[0],cardArgs[1]);
sentencesAndGroups = new MediatorLiveData<>();
sentencesAndGroups.addSource(cards, sentences -> {
Log.d(TAG,"addSource cards");
sentencesAndGroups.setValue(combineLatest(sentences, groups.getValue()));
});
sentencesAndGroups.addSource(groups, groupsWithSentences -> {
Log.d(TAG,"addSource groups");
sentencesAndGroups.setValue(combineLatest(cards.getValue(), groupsWithSentences));
});
}
private List<Visitable> combineLatest(List<Sentence> sentenceList, List<GroupsWithSentences> groupsWithSentences) {
List<Visitable> visitableList = new ArrayList<>();
if (sentenceList != null){
visitableList.addAll(sentenceList);
}
if (groupsWithSentences != null){
visitableList.addAll(groupsWithSentences);
}
return visitableList;
}
any help?
You can use a BiFunction. In my project I have a class which contains this:
public class MultipleLiveDataTransformation {
#MainThread
public static <X, Y, O> LiveData<O> biMap(LiveData<X> data1, LiveData<Y> data2, final BiFunction<X, Y, O> biFun) {
final MediatorLiveData<O> result = new MediatorLiveData<>();
result.addSource(data1, x -> result.setValue(biFun.apply(x, data2.getValue())));
result.addSource(data2, y -> result.setValue(biFun.apply(data1.getValue(), y)));
return result;
}
}
You can use it like this:
public LiveData<Visitable> getVisitables() {
return MultipleLiveDataTransformation.biMap(groups, sentences, (gro, sen) -> {
List<Visitable> visitableList = new ArrayList<>();
if (gro != null) {
visitableList.addAll(gro);
}
if (sen != null) {
visitableList.addAll(sen);
}
return visitableList;
});
}
I do not fully understand what lists you have and what you want to observe. The example I gave here can observe two LiveData objects and triggers when one of them needs to update. It will always give you the most up to date version of the Visitable objects.
If I don't understand the problem, can you explain what LiveData objects need to be combined and what the result needs to be?
In the case, that an extra LiveData object needs to be observed in the MultipleDataTransformation, it is possible to make a triMap.
Edit:
You can try this:
public LiveData<Visitable> getVisitables() {
return MultipleLiveDataTransformation.biMap(groups, sentences, (gro, sen) -> {
List<Visitable> visitableList = new ArrayList<>();
if (gro != null && !gro.isEmpty() && sen != null && !sen.isEmpty()) {
visitableList.addAll(gro);
visitableList.addAll(sen);
}
return visitableList;
});
}

dreamfactory : Querying records with multiple filters

I am working on an ecommerce android app. I am trying to fetch records using dreamfactory API for android, based on multiple filters.
Using an AsyncTask named as GetProductsBySubCatIdTask
public class GetProductsBySubCatIdTask extends BaseAsyncRequest {
Context context;
public Products productsRec;
int subCatId;
String sort_str,fltr_str;
public GetProductsBySubCatIdTask(Context context, int subCataId, String sort_str, String fltr_str){
this.context = context;
this.subCatId = subCataId;
this.sort_str = sort_str;
this.fltr_str = fltr_str;
}
#Override
protected void doSetup() throws ApiException, JSONException {
callerName = "getProductsBySubCatId";
serviceName = AppConstants.DB_SVC;
endPoint = "product";
verb = "GET";
// filter to only select the contacts in this group
if(!TextUtils.isEmpty(fltr_str)){
fltr_str = "&&" + fltr_str;
}
else{
fltr_str = "";
}
queryParams = new HashMap<>();
queryParams.put("filter", "sub_category_id=" + subCatId + fltr_str);
queryParams.put("order", sort_str);
applicationApiKey = AppConstants.API_KEY;
sessionToken = PrefUtil.getString(context, AppConstants.SESSION_TOKEN);
}
#Override
protected void processResponse(String response) throws ApiException, JSONException {
//Log.d("Tang Ho"," >>>>> " + response);
productsRec =
(Products) ApiInvoker.deserialize(response, "", Products.class);
}
#Override
protected void onCompletion(boolean success) {
if(success && productsRec != null && productsRec.products.size() > 0){
Log.d("Tang Ho"," >>>>> Success");
}
}
}
I have used filters which is constructed outside the class and provided as parameter, the possible filters are
unit_offerprice < unit_mrp<br>
unit_offerprice = unit_mrp<br>
(unit_offerprice > 200) && (unit_offerprice > 500)<br>
unit_offerprice > 100<br>
unit_offerprice < 600<br>
All the above filters can be used either individually or in combination of 2 or 3 like
unit_offerprice < unit_mrp && unit_offerprice > 100
After escaping the symbols in the string like
unit_offerprice%3Cunit_mrp
Not able to get desired result,
Searched in documentations but din't found exact thing.
what can be the possible solution for this ?
If your filter has multiple conditions, each condition needs to be placed inside parentheses (). Additionally, the proper syntax for joining filters with and is AND, not &&.
Supported logical operators are AND, OR, and NOT.
In your example, this:
unit_offerprice < unit_mrp && unit_offerprice > 100
should be this:
(unit_offerprice < unit_mrp) AND (unit_offerprice > 100)
See these portions of the documentation:
http://wiki.dreamfactory.com/DreamFactory/Features/Database/Records#Filtering_Records
http://wiki.dreamfactory.com/DreamFactory/Tutorials/Querying_records_with_logical_filters
DreamFactory also offers a number of official support avenues, including user forums. http://www.dreamfactory.com/support
Got it resolved by adding parenthesis for multiple filters.
But there was issue using the special characters (like <,>) and
this needed to be
encoded into %3C (for <) and %3E (for >),
the string used for the filtering is :
"(unit_offerprice>100)AND(unit_offerprice<500)"
the encoded form is :
"(unit_offerprice%3E100)AND(unit_offerprice%3C500)"

How to remove duplicates from ArrayList of type Object? [duplicate]

This question already has answers here:
How to remove duplicates from a list?
(15 answers)
Closed 8 years ago.
I want to remove duplicates from ArrayList of type Alerts where Alerts is a class.
Class Alerts -
public class Alerts implements Parcelable {
String date = null;
String alertType = null;
String discription = null;
public Alerts() {
}
public Alerts(String date, String alertType, String discription) {
super();
this.date = date;
this.alertType = alertType;
this.discription = discription;
}
}
Here is how I added the elements -
ArrayList<Alerts> alert = new ArrayList<Alerts>();
Alerts obAlerts = new Alerts();
obAlerts = new Alerts();
obAlerts.date = Date1.toString();
obAlerts.alertType = "Alert Type 1";
obAlerts.discription = "Some Text";
alert.add(obAlerts);
obAlerts = new Alerts();
obAlerts.date = Date2.toString();
obAlerts.alertType = "Alert Type 1";
obAlerts.discription = "Some Text";
alert.add(obAlerts);
What I want to remove from them-
I want all alerts which have unique obAlerts.date and obAlerts.alertType. In other words, remove duplicate obAlerts.date and obAlerts.alertType alerts.
I tried this -
Alerts temp1, temp2;
String macTemp1, macTemp2, macDate1, macDate2;
for(int i=0;i<alert.size();i++)
{
temp1 = alert.get(i);
macTemp1=temp1.alertType.trim();
macDate1 = temp1.date.trim();
for(int j=i+1;j<alert.size();j++)
{
temp2 = alert.get(j);
macTemp2=temp2.alertType.trim();
macDate2 = temp2.date.trim();
if (macTemp2.equals(macTemp1) && macDate1.equals(macDate2))
{
alert.remove(temp2);
}
}
}
I also tried-
HashSet<Alerts> hs = new HashSet<Alerts>();
hs.addAll(obAlerts);
obAlerts.clear();
obAlerts.addAll(hs);
You need to specify yourself how the class decides equality by overriding a pair of methods:
public class Alert {
String date;
String alertType;
#Override
public boolean equals(Object o) {
if (this == 0) {
return true;
}
if ((o == null) || (!(o instanceof Alert)))
return false;
}
Alert alert = (Alert) o;
return this.date.equals(alert.date)
&& this.alertType.equals(alert.alertType);
}
#Override
public int hashCode() {
int dateHash;
int typeHash;
if (date == null) {
dateHash = super.hashCode();
} else {
dateHash = this.date.hashCode();
}
if (alertType == null) {
typeHash = super.hashCode();
} else {
typeHash = this.alertType.hashCode();
}
return dateHash + typeHash;
}
}
You can then loop through your ArrayList and add elements if they aren't already there as Collections.contains() makes use of these methods.
public List<Alert> getUniqueList(List<Alert> alertList) {
List<Alert> uniqueAlerts = new ArrayList<Alert>();
for (Alert alert : alertList) {
if (!uniqueAlerts.contains(alert)) {
uniqueAlerts.add(alert);
}
}
return uniqueAlerts;
}
However, after saying all that, you may want to revisit your design to use a Set or one of its family that doesn't allow duplicate elements. Depends on your project. Here's a comparison of Collections types
You could use a Set<>. By nature, Sets do no include duplicates. You just need to make sure that you have a proper hashCode() and equals() methods.
In your Alerts class, override the hashCode and equals methods to be dependent on the values of the fields you want to be primary keys. Afterwards, you can use a HashSet to store already seen instances while iterating over the ArrayList. When you find an instance which is not in the HashSet, add it to the HashSet, else remove it from the ArrayList. To make your life easier, you could switch to a HashSet altogether and be done with duplicates per se.
Beware that for overriding hashCode and equals, some constraints apply.
This thread has some helpful pointers on how to write good hashCode functions. An important lesson is that simply adding together all dependent fields' hashcodes is not sufficient because then swapping values between fields will lead to identical hashCodes which might not be desirable (compare swapping first name and last name). Instead, some sort of shifting-operation is usually done before adding the next atomic hash, eg. multiplying with a prime.
First store your datas in array then split at as one by one string,, till the length of that data execute arry and compare with acyual data by if condition and retun it,,
HashSet<String> hs = new HashSet<String>();
for(int i=0;i<alert.size();i++)
{
hs.add(alert.get(i).date + ","+ alert.get(i).alertType;
}
alert.clear();
String alertAll[] = null;
for (String s : hs) {
alertAll = s.split(",");
obAlerts = new Alerts();
obAlerts.date = alertAll[0];
obAlerts.alertType = alertAll[1];
alert.add(obAlerts);
}

#Nullable #Named in GAE / Android Sample

I'm trying to work on the sample GAE / Android app. There is Place Entity.
In generated PlaceEndpoint class there is a method:
#ApiMethod(name = "listGame")
public CollectionResponse<Place> listPlace(
#Nullable #Named("cursor") String cursorString,
#Nullable #Named("limit") Integer limit) {
EntityManager mgr = null;
Cursor cursor = null;
List<Game> execute = null;
try {
mgr = getEntityManager();
Query query = mgr.createQuery("select from Place as Place");
if (cursorString != null && cursorString != "") {
cursor = Cursor.fromWebSafeString(cursorString);
query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
}
if (limit != null) {
query.setFirstResult(0);
query.setMaxResults(limit);
}
execute = (List<Game>) query.getResultList();
cursor = JPACursorHelper.getCursor(execute);
if (cursor != null)
cursorString = cursor.toWebSafeString();
// Tight loop for fetching all entities from datastore and accomodate
// for lazy fetch.
for (Game obj : execute)
;
} finally {
mgr.close();
}
return CollectionResponse.<Game> builder().setItems(execute)
.setNextPageToken(cursorString).build();
}
As I understand cursor and limit all optional params.
However I can't figure out how to pass them using Placeednpoint class on the client side:
Placeendpoint.Builder builder = new Placeendpoint.Builder(AndroidHttp.newCompatibleTransport(), new JacksonFactory(), null);
builder = CloudEndpointUtils.updateBuilder(builder);
Placeendpoint endpoint = builder.build();
try {
CollectionResponsePlace placesResponse = endpoint.listPlace().execute();
} catch (Exception e) {
e.printStackTrace();
Normally, when params are not nullable I would pass them in endpoint.listPlace() method. But when params are nullable, client side app doesn't see alternative constructor, that would accept params.
How am I supposed to pass them then?
For passing parameters from client side while sending a query request through cloud endpoints, you need to add provision for setting parameters. To send the required parameter from android , the class where you would define the REST Path and method type, should include an option to the set cursor and limit. For example for the String cursorstring :
#com.google.api.client.util.Key
private String cursorstring;
public String getCursorstring() {
return cursorstring;
}
public ListPlace setCursorstring(String cursorstring) {
this.cursorstring = cursorstring;
return this;
}
Finally while calling the endpoint method from your android code, you should pass a value using the setCursorstring, which will be something like:
CollectionResponsePlace placesResponse = endpoint.listPlace().setCursorstring("yourcursorstring").execute();
There is an additional way. You can simply set your nullable value 'cursor' it like this:
CollectionResponsePlace placesResponse =
endpoint.listPlace().set("cursor", "Your Cursorstring").execute();
Where you can
Faced the same issue but backend developed using Java.
The solution provided by Tony m worked like a charm.
Simply had to adapt how the method is called on Android and it worked.

How to retrieve more than 1000 rows from Parse.com?

I have been using Parse to retrieve a data for a list view. Unfortunately they limit requests to 100 by default to a 1000 max. I have well over that 1000 max in my class. I found a link on the web which shows a way to do it on iOS but how would you do it on Android? Web Link
I am currently adding all the data into a arraylist in a loop until all items are complete (100) then adding them to the list
I have figured out how to achieve my goal:
Declare Global Variable
private static List<ParseObject>allObjects = new ArrayList<ParseObject>();
Create Query
final ParseQuery parseQuery = new ParseQuery("Objects");
parseQuery.setLimit(1000);
parseQuery.findInBackground(getAllObjects());
Callback for Query
int skip=0;
FindCallback getAllObjects(){
return new FindCallback(){
public void done(List<ParseObject> objects, ParseException e) {
if (e == null) {
allObjects.addAll(objects);
int limit =1000;
if (objects.size() == limit){
skip = skip + limit;
ParseQuery query = new ParseQuery("Objects");
query.setSkip(skip);
query.setLimit(limit);
query.findInBackground(getAllObjects());
}
//We have a full PokeDex
else {
//USE FULL DATA AS INTENDED
}
}
};
}
Here is a JavaScript version without promises..
These are the global variables (collections are not required, just a bad habit of mine)..
///create a collection of cool things and instantiate it (globally)
var CoolCollection = Parse.Collection.extend({
model: CoolThing
}), coolCollection = new CoolCollection();
This is the "looping" function that gets your results..
//recursive call, initial loopCount is 0 (we haven't looped yet)
function getAllRecords(loopCount){
///set your record limit
var limit = 1000;
///create your eggstra-special query
new Parse.Query(CoolThings)
.limit(limit)
.skip(limit * loopCount) //<-important
.find({
success: function (results) {
if(results.length > 0){
//we do stuff in here like "add items to a collection of cool things"
for(var j=0; j < results.length; j++){
coolCollection.add(results[j]);
}
loopCount++; //<--increment our loop because we are not done
getAllRecords(loopCount); //<--recurse
}
else
{
//our query has run out of steam, this else{} will be called one time only
coolCollection.each(function(coolThing){
//do something awesome with each of your cool things
});
}
},
error: function (error) {
//badness with the find
}
});
}
This is how you call it (or you could do it other ways):
getAllRecords(0);
IMPORTANT None of the answers here are useful if you are using open
source parse server then it does limit 100 rows by default but you can
put any value in query,limit(100000) //WORKS
No need for recursive
calls just put the limit to number of rows you want.
https://github.com/parse-community/parse-server/issues/5383
JAVA
So after 5 years, 4 months the above answer of #SquiresSquire needed some changes to make it work for me, and I would like to share it with you.
private static List<ParseObject>allObjects = new ArrayList<ParseObject>();
ParseQuery<ParseObject> parseQuery = new ParseQuery<ParseObject>("CLASSNAME");
parseQuery.setLimit(1000);
parseQuery.findInBackground(getAllObjects());
FindCallback <ParseObject> getAllObjects() {
return new FindCallback <ParseObject>() {
#Override
public void done(List<ParseObject> objects, ParseException e) {
if (e == null) {
allObjects.addAll(objects);
int limit = 1000;
if (objects.size() == limit) {
skip = skip + limit;
ParseQuery query = new ParseQuery("CLASSNAME");
query.setSkip(skip);
query.setLimit(limit);
query.findInBackground(getAllObjects());
}
//We have a full PokeDex
else {
//USE FULL DATA AS INTENDED
}
}
}
};
In C# I use this recursion:
private static async Task GetAll(int count = 0, int limit = 1000)
{
if (count * limit != list.Count) return;
var res = await ParseObject.GetQuery("Row").Limit(limit).Skip(list.Count).FindAsync();
res.ToList().ForEach(x => list.Add(x));
await GetAll(++count);
}
JS version:
function getAll(list) {
new Parse.Query(Row).limit(1000).skip(list.length).find().then(function (result) {
list = list.concat(result);
if (result.length != 1000) {
//do here something with the list...
return;
}
getAll(list);
});
}
Usage: GetAll() in C#, and getAll([]) in JS.
I store all rows from the class Rowin the list. In each request I get 1000 rows and skip the current size of the list. Recursion stops when the current number of exported rows is different from the expected.
**EDIT : Below answer is redundant because open source parse server doesn't put any limit on max rows to be fetched
//instead of var result = await query.find();
query.limit(99999999999);//Any value greater then max rows you want
var result = await query.find();**
Original answer:
Javascript / Cloud Code
Here's a clean way working for all queries
async function fetchAllIgnoringLimit(query,result) {
const limit = 1000;
query.limit(limit);
query.skip(result.length);
const results = await query.find();
result = result.concat(results)
if(results.length === limit) {
return await fetchAllIgnoringLimit(query,result );
} else {
return result;
}
}
And here's how to use it
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
//instead of var result = await query.find();
var result = await fetchAllIgnoringLimit(query,new Array());
console.log("got "+result.length+" rows")
YAS (Yet Another Solution!) Using async() and await() in javascript.
async parseFetchAll(collected = []) {
let query = new Parse.Query(GameScore);
const limit = 1000;
query.limit(limit);
query.skip(collected.length);
const results = await query.find();
if(results.length === limit) {
return await parseFetchAll([ ...collected, ...results ]);
} else {
return collected.concat(results);
}
}
A Swift 3 Example:
var users = [String] ()
var payments = [String] ()
///set your record limit
let limit = 29
//recursive call, initial loopCount is 0 (we haven't looped yet)
func loadAllPaymentDetails(_ loopCount: Int){
///create your NEW eggstra-special query
let paymentsQuery = Payments.query()
paymentsQuery?.limit = limit
paymentsQuery?.skip = limit*loopCount
paymentsQuery?.findObjectsInBackground(block: { (objects, error) in
if let objects = objects {
//print(#file.getClass()," ",#function," loopcount: ",loopCount," #ReturnedObjects: ", objects.count)
if objects.count > 0 {
//print(#function, " no. of objects :", objects.count)
for paymentsObject in objects {
let user = paymentsObject[Utils.name] as! String
let amount = paymentsObject[Utils.amount] as! String
self.users.append(user)
self.payments.append(amount)
}
//recurse our loop with increment because we are not done
self.loadAllPaymentDetails(loopCount + 1); //<--recurse
}else {
//our query has run out of steam, this else{} will be called one time only
//if the Table had been initially empty, lets inform the user:
if self.users.count == 1 {
Utils.createAlert(self, title: "No Payment has been made yet", message: "Please Encourage Users to make some Payments", buttonTitle: "Ok")
}else {
self.tableView.reloadData()
}
}
}else if error != nil {
print(error!)
}else {
print("Unknown Error")
}
})
}
adapted from #deLux_247's example above.
You could achieve this using CloudCode... Make a custom function you can call that will enumerate the entire collection and build a response from that but a wiser choice would be to paginate your requests, and fetch the records 1000 (or even less) at a time, adding them into your list dynamically as required.
GENERIC VERSION For SWIFT 4:
Warning: this is not tested!
An attempt to adapt nyxee's answer to be usable for any query:
func getAllRecords(for query: PFQuery<PFObject>, then doThis: #escaping (_ objects: [PFObject]?, _ error: Error?)->Void) {
let limit = 1000
var objectArray : [PFObject] = []
query.limit = limit
func recursiveQuery(_ loopCount: Int = 0){
query.skip = limit * loopCount
query.findObjectsInBackground(block: { (objects, error) in
if let objects = objects {
objectArray.append(contentsOf: objects)
if objects.count == limit {
recursiveQuery(loopCount + 1)
} else {
doThis(objectArray, error)
}
} else {
doThis(objects, error)
}
})
}
recursiveQuery()
}
Here's my solution for C# .NET
List<ParseObject> allObjects = new List<ParseObject>();
ParseQuery<ParseObject> query1 = ParseObject.GetQuery("Class");
int totalctr = await query1.CountAsync()
for (int i = 0; i <= totalctr / 1000; i++)
{
ParseQuery<ParseObject> query2 = ParseObject.GetQuery("Class").Skip(i * 1000).Limit(1000);
IEnumerable<ParseObject> ibatch = await query2.FindAsync();
allObjects.AddRange(ibatch);
}

Categories

Resources