Good day,
I am using Parse Push Notification, and below are my difficulties:
In brief, I would like to "merge" these two conditions:
query.whereEqualTo("Gender", userLookingGender);
pushQuery.whereEqualTo("the gender column of the ParseQuery", the user gender of the ParseQuery column);
In other words, I would like to send out a message to the user that falls within that gender. The gender column along with the genders are found in the parse query called "User".
Update:
userLookingGender is the following:
String userLookingGender = ParseUser.getCurrentUser().getString(
"Looking_Gender");
If you need any clarification let me know.
Update 2:
I use one condition, gender, to make easier to understand. Now Imagine if I had multiple condition, and is trying to send a push message only the recipient who fulfill all of the below criteria, and where upon button click it would take them to particular activity page.
ParseQuery<ParseObject> query = ParseQuery.getQuery("User");
query.whereNotEqualTo("objectId", ParseUser.getCurrentUser()
.getObjectId());
// users with Gender = currentUser.Looking_Gender
query.whereEqualTo("Gender", userLookingGender);
// users with Looking_Gender = currentUser.Gender
query.whereEqualTo("Looking_Gender", userGender);
query.setLimit(1) ;
ParseGeoPoint point = ParseUser.getCurrentUser().getParseGeoPoint("location");
query.whereWithinKilometers("location", point, mMax_Distance.doubleValue());
query.whereEqualTo("ActivityName", activityName);
query.whereGreaterThanOrEqualTo("UserAge", minimumAge);
query.whereLessThanOrEqualTo("UserAge", maximumAge);
Update 3:
Android Code
ParseCloud.callFunctionInBackground("sendPushToNearbyAndMatching", new HashMap<String, Object>(), new FunctionCallback<String>() {
public void done(String result, ParseException e) {
if (e == null) {
// success
}
}
});
Parse Cloud JavaScript code (found in cloud/main.js)
The owner column in this case is users
// Use Parse.Cloud.define to define as many cloud functions as you want.
// For example:
Parse.Cloud.define("hello", function(request, response) {
response.success("Hello world!");
});
Parse.Cloud.define("sendPushToNearbyAndMatching", function(request, response) {
Parse.Cloud.useMasterKey();
// the authenticated user on the device calling this function
var user = request.user;
// the complex query matching users
var query = new Parse.Query(Parse.User);
query.whereNotEqualTo("objectId", user.id);
// users with Gender = currentUser.Looking_Gender
query.equalTo("Gender", user.get("Gender"));
// users with Looking_Gender = currentUser.Gender
query.equalTo("Looking_Gender", user.get("Looking_Gender"));
query.equalTo("ActivityName", user.get("ActivityName"));
query.greaterThanOrEqualTo("UserAge", user.get("Minimum_Age"));
query.lessThanOrEqualTo("UserAge", user.get("Maximum_Age"));
query.limit(1);
query.each(function(user) {
// sendPushNotification is added in next code section
return sendPushNotification(user);
}).then(function() {
response.success("success!");
}, function(err) {
response.error(err);
});
});
var sendPushNotification = function(user) {
var query = new Parse.Query(Parse.Installation);
query.equalTo('users', user);
return Parse.Push.send({
where : query, // send to installations matching query
expiration_interval : 600, // optional - expires after 10 minutes
data : {
alert: "App says hello!",
}
})
}
Now that I have a bit more insight I think I'm ready with an answer:
I do Push from Cloud Code, and there the query matches against Installation objects, which is why having the values there as well would be useful.
It looks like you are sending directly from the app, so I would suggest creating a channel for each gender: https://parse.com/docs/push_guide#sending-channels/Android
Then you just need to:
String userLookingGender = ParseUser.getCurrentUser().getString(
"Looking_Gender");
ParsePush push = new ParsePush();
push.setChannel(userLookingGender);
push.setMessage("Your message");
push.sendInBackground();
Update:
Ok. the multiple queries indeed make matters more complicated.
I think you would have to move on to Cloud Code to perform such an advanced query push (which is by the way recommended for security reasons).
Cloud Code guide: https://parse.com/docs/cloud_code_guide
Embrasing the fact that users can have multiple devices, you need to be able to fetch all the installations associated with a user. To do this I would suggest saving a pointer to User on each installation. You can do this as part of the first login of your app.
Assuming you have a, say, owner column in your installation pointing to the respective User owning the device, then you can do something like this in Cloud Code:
Parse.Cloud.define("sendPushToNearbyAndMatching", function(request, response) {
Parse.Cloud.useMasterKey();
// the authenticated user on the device calling this function
var user = request.user;
// the complex query matching users
var query = new Parse.Query(Parse.User);
query.whereNotEqualTo("objectId", user.id);
// users with Gender = currentUser.Looking_Gender
query.equalTo("Gender", user.get("Gender"));
// users with Looking_Gender = currentUser.Gender
query.equalTo("Looking_Gender", user.get("Looking_Gender"));
query.limit(1);
... etc
// execute the query
// i am using each just to show an convenient way to iterate the results
// instead of setting limit(1) consider executing the query using first() instead
// android SDK has a getFirstInBackground() as well
query.each(function(user) {
// sendPushNotification is added in next code section
return sendPushNotification(user);
}).then(function() {
response.success("success!");
}, function(err) {
response.error(err);
});
});
About querying User in javascript: https://parse.com/docs/js_guide#users-querying
How to call this Cloud function from Android: https://parse.com/docs/android_guide#cloudfunctions
Now it is time to send out the notifications to the devices owned by the user:
var sendPushNotification = function(user) {
var promise = new Parse.Promise();
var query = new Parse.Query(Parse.Installation);
query.equalTo('owner', user);
query.count().then(function(count) {
console.log("sending push to " + count + " devices");
Parse.Push.send({
where : query, // send to installations matching query
expiration_interval : 600, // optional - expires after 10 minutes
data : {
alert: "App says hello!",
}
}).then(function() {
// success
console.log("push success");
promise.resolve();
}, function(error) {
console.error(error.message);
promise.reject(error);
});
});
return promise;
}
For more advanced pushes (if you for instance want to receive a broadcast to handle some data) see: https://parse.com/docs/push_guide#sending-queries/JavaScript
Also, if you decide to fiddle with Cloud Code and thereby javascript, I would highly recommend having a look at how promises work. This makes your life so much easier when handling asynchronous calls, for instance when issuing queries: https://parse.com/docs/js_guide#promises
This is all a lot of information and probably a lot to take in all at once if it is new to you, but I think it will be all worth it, I know it was for me.
Related
Here in the Firestore databases is a collection called "Users", In that collection, there are 29 documents(States) and in each of that documents there are many collections(Districts). This collection includes many documents these documents include the user's data along with numbers).
so how do we check the phone number that has already been added to the database?
I write something like this
FirebaseFirestore firestore = FirebaseFirestore.getInstance();
CollectionReference usersRef = firestore.collection("Users");
String phoneNumber = "1234567890";
usersRef.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Map<String, Object> data = document.getData();
for (String key : data.keySet()) {
Object value = data.get(key);
if (value instanceof String && ((String) value).equals(phoneNumber)) {
isRealAvailable = true;
Toast.makeText(OtpActivity.this, "Number found", Toast.LENGTH_SHORT).show();
// Phone number found
break;
}
}
}
} else {
isRealAvailable = false;
Toast.makeText(OtpActivity.this, "No number found, create an account!", Toast.LENGTH_LONG).show();
}
// Handle error
}
});
(the number is not a String value)
I don't know how to retrieve these details from different document names.
How do we get the Phone Number from the document? if it exists.
What I wanted is, I'm building a blood donors app, where people can register using their phone numbers. From different countries. So when a user sign-up with proper details, he can go to the main screen. Whenever he logout and login, he doesn't need to update the details again. As we know phone number login and signup are the same. Also, I want to separate the states and places in different documents and collections. I added the location permission and perspective codes so the country and states are chosen automatically. (will get the users location)
When you're calling get() on the following collection reference:
CollectionReference usersRef = firestore.collection("Users");
It means that you're trying to get all user documents that exist in the Users collection. As soon as you get all users, you then check the phoneNumber against the one that you find in the database, which is bad since you'll need to pay for a document read even for the users that do not have that particular phone number. What you have to do instead is to create a query that should only return the documents that you are interested in.
On the other hand, your actual database schema isn't quite helping you to achieve that. In the NoSQL world, we are usually structuring a database according to the queries that we want to perform. So if you need to get all users from Brooklyn / New York that have a particular phone number, I would recommend you have a schema that looks like this:
db
|
--- users (collection)
|
--- $uid (document)
|
--- city: "New York"
|
--- borough: "Brooklyn"
|
--- mobNumber: 720555
And in code, it should look like this:
FirebaseFirestore db = FirebaseFirestore.getInstance();
CollectionReference usersRef = db.collection("users");
Query queryByMobNumber = usersRef.whereEqualTo("city", "New York")
.whereEqualTo("borough", "Brooklyn")
.whereEqualTo("mobNumber", 720555);
queryByMobNumber.get().addOnCompleteListener(/* ... /*);
So in Firestore, chaining multiple whereEqualTo calls will work perfectly fine.
Besides that, I see that you are changing a boolean value inside the callback, which will not be seen outside the callback because Firebase API is asynchronous. If you want to learn more about that, I recommend you check this resource. Here is the corresponding repo.
We have an application that loads all contacts stored in an account using the Microsoft Graph API. The initial call we issue is https://graph.microsoft.com/v1.0/users/{userPrincipalName}/contacts$count=true&$orderBy=displayName%20ASC&$top=100, but we use the Java JDK to do that. Then we iterate over all pages and store all loaded contacts in a Set (local cache).
We do this every 5 minutes using an account with over 3000 contacts and sometimes, the count of contacts we received due to using $count does not match the number of contacts we loaded and stored in the local cache.
Verifying the numbers manually we can say, that the count was always correct, but there are contacts missing.
We use the following code to achieve this.
public List<Contact> loadContacts() {
Set<Contact> contacts = new TreeSet<>((contact1, contact2) -> StringUtils.compare(contact1.id, contact2.id));
List<QueryOption> requestOptions = List.of(
new QueryOption("$count", true),
new QueryOption("$orderBy", "displayName ASC"),
new QueryOption("$top", 100)
);
ContactCollectionRequestBuilder pageRequestBuilder = null;
ContactCollectionRequest pageRequest;
boolean hasNextPage = true;
while (hasNextPage) {
// initialize page request
if (pageRequestBuilder == null) {
pageRequestBuilder = graphClient.users(userId).contacts();
pageRequest = pageRequestBuilder.buildRequest(requestOptions);
} else {
pageRequest = pageRequestBuilder.buildRequest();
}
// load
ContactCollectionPage contactsPage = pageRequest.get();
if (contactsPage == null) {
throw new IllegalStateException("request returned a null page");
} else {
contacts.addAll(contactsPage.getCurrentPage());
}
// handle next page
hasNextPage = contactsPage.getNextPage() != null;
if (hasNextPage) {
pageRequestBuilder = contactsPage.getNextPage();
} else if (contactsPage.getCount() != null && !Objects.equals(contactsPage.getCount(), (long) contacts.size())) {
throw new IllegalStateException(String.format("loaded %d contacts but response indicated %d contacts", contacts.size(), contactsPage.getCount()));
} else {
// done
}
}
log.info("{} contacts loaded using graph API", contacts.size());
return new ArrayList<>(contacts);
}
Initially, we did not put the loaded contacts in a Set by ID but just in a List. With the List we very often got more contacts than $count. My idea was, that there is some caching going on and some pages get fetched multiple times. Using the Set we can make sure, that we only have unique contacts in our local cache.
But using the Set, we sometimes have less contacts than $count, meaning some pages got skipped and we end up in the condition that throws the IllegalStateException.
Currently, we use microsoft-graph 5.8.0 and azure-identiy 1.4.2.
Have you experienced similar issues and can help us solve this problem?
Or do you have any idea what could be causing these inconsistent results?
Your help is very much appreciated!
I have been bonking my head everywhere on this problem , I would really need some help please !! I am pretty new to Android.
My problem is the following , I have completed the User Class with some columns , for example "Former Friends" which are a list of Strings .
I do a first query , then I find the Parseuser objects matching the query (which are not the logged in user) and then I try to fill those columns.
I also update the info for the logged in user
It properly works for the logged in user ,however I can't see the filled info for the other Parse object user
I tried modifying the write access for the first user (objects.get(0)) ,but it doesn't work
ParseQuery<ParseUser> query = ParseUser.getQuery();
query.whereNotEqualTo("username", getCurrentUser().getUsername());
query.whereNotContainedIn("username",getCurrentUser().getList("Former_friends"));
query.findInBackground(new FindCallback<ParseUser>() {
#Override
public void done(List<ParseUser> objects, ParseException e) {
if (e == null) {
if (objects.size() > 0) {
// Here I just add the first object to a list and I update the current user data ,that works fine
List<String> aList = ParseUser.getCurrentUser().getList("Former_friends");
aList.add(objects.get(0).getString("username"));
ParseUser.getCurrentUser().put("Former_friends", aList);
ParseUser.getCurrentUser().saveInBackground();
ParseUser userfound =objects.get(0);
// The two following Lines doesn't work. I don't see "Any String" in the ParseDashboard "Name" columns..
userfound.put("Name","Any String");
userfound.saveInBackground();
There are no bugs , but no update for the non-logged-in user
Big thx,
Serge
For security reasons, in Parse Server, one user cannot change another user object directly from client side. If it were possible, a bad user could erase all other users data, for example.
So this operation requires you to write a cloud code function. You should have a cloud function similar to this one here:
Parse.cloud.define('formerFriends', async request => {
const query = new Parse.Query(Parse.User);
query.notEqualTo('username', request.user.getUsername());
query.notContainedIn('username', request.user.get('Former_friends'));
const objects = await query.find({ useMasterKey: true });
if (object.length > 0) {
const aList = request.user.get('Former_friends');
aList.add(objects[0].getUsername());
request.user.set('Former_friends', aList);
await request.user.save();
const userfound = objects[0];
userfound.set('Name', 'Any String');
await userfound.save(null, { useMasterKey: true });
}
});
And then call the cloud function from Android like this:
HashMap<String, Object> params = new HashMap<String, Object>();
ParseCloud.callFunctionInBackground("formerFriends", params, new FunctionCallback<Float>() {
void done(Object result, ParseException e) {
if (e == null) {
// success
}
}
});
The docs for cognito user pools can be found here:
http://docs.aws.amazon.com/cognito/latest/developerguide/how-to-manage-user-accounts.html
In this they do not say whether you can query users by the automatically generated sub attribute, which is a uuid. It explicitly says you can't search for users by custom attributes, but sub/uuid is not a custom attribute. Weirdly though, in the list of searchable attributes sub/uuid is not one of them. Surely though you can look up users by their UUID, how would this be done though??
You know, I have used COgnito but never needed to look up via sub (or other params other than the username). I looked into it because surely you can, but it is not very clear (like a lot of their documentation). Here is what I saw that you could try... hope it helps man.
// the imported ListUsersResult is...
import com.amazonaws.services.cognitoidp.model.ListUsersRequest;
import com.amazonaws.services.cognitoidp.model.ListUsersResult;
// class var
protected final AWSCognitoIdentityProviderClient identityUserPoolProviderClient;
// omitted stuff...
// initialize the Cognito Provider client. This is used to talk to the user pool
identityUserPoolProviderClient = new AWSCognitoIdentityProviderClient(new BasicAWSCredentials(AWS_ACCESS_KEY, AWS_SECRET_KEY)); // creds are loaded via variables that are supplied to my program dynamically
identityUserPoolProviderClient.setRegion(RegionUtils.getRegion(USER_POOL_REGION)); // var loaded
// ...some code omitted
ListUsersRequest listUsersRequest = new ListUsersRequest();
listUsersRequest.withUserPoolId(USER_POOL_ID); // id of the userpool, look this up in Cognito console
listUsersRequest.withFilter("sub=xyz"); // i THINK this is how the Filter works... the documentation is terribad
// get the results
ListUsersResult result = identityUserPoolProviderClient.listUsers(listUsersRequest);
List<UserType> userTypeList = result.getUsers();
// loop through them
for (UserType userType : userTypeList) {
List<AttributeType> attributeList = userType.getAttributes();
for (AttributeType attribute : attributeList) {
String attName = attribute.getName();
String attValue = attribute.getValue();
System.out.println(attName + ": " + attValue);
}
}
If you have the username you could get the user like this
// build the request
AdminGetUserRequest idRequest = new AdminGetUserRequest();
idRequest.withUserPoolId(USER_POOL_ID);
idRequest.withUsername(username);
// call cognito for the result
AdminGetUserResult result = identityUserPoolProviderClient.adminGetUser(idRequest);
// loop through results
I'm to get a list of the users photos (one's they've been tagged in) using FQL.
Basically I've go an array object like so: _imageAddressArray.
I can retrieve the users' photos using graphApi so I know it works, problem with graphAPi is that it's too slow (+15 seconds min for 100 photos).
So far I've got:
//New Stuff
FQL fql = new FQL(facebook);
String FQLResult = null;
try
{
_userGallery = graphApi.getPhotosMy(_noOfPhotos);
FQLResult = fql.fqlQuery("SELECT object_id, src_small FROM photo");
}
catch (EasyFacebookError e)
{
e.printStackTrace();
}
System.out.println("FQL Result" + FQLResult);
This returns the error: 601, any ideas anyone?
Of course ideally FQLResult will be a string[] (string array)
You're getting an error because you don't have a WHERE clause in your FQL statement that references one of the indexed columns -- shown with a "*" here
To get the photos using FQL that your user has been tagged in, try this:
SELECT object_id, src_small FROM photo WHERE object_id IN
(SELECT object_id FROM photo_tag WHERE subject = me())