Parse Android: understanding cloud code and deploy - android

My app is simple as it requires four steps
Step 1:
UserA can create a questionnaire and send it to userB.
Step 2:
UserB receives the questionnaire with three options represented in radio button
Step 3:
UserB chooses the preferable answer that presented in radio button and click submit button
Step 4:
Upon clicking submit button userA receives an email with given answer from userB
However, I used parse for this app and I just started recently learning about cloud code and how to use it since I am trying to fetch sender's (UserA) email from User table but still I am not getting the desired results as I feel I am missing something
My main.js
Parse.Cloud.define("getEmail", function(request, response) {
Parse.Cloud.useMasterKey();
var userQuery = new Parse.Query(Parse.User);
var senderName = userEmail.get("senderId");
userQuery.equalTo("email", request.params.email);
userQuery.get("email", {
success: function(userEmail) {
// the object was retrieved
if ( email == senderName) {
status.message(email + "found");
return getEmail.save;
}
else {
status.message("Invalid email address");
}
},
error: function(object, error) {
status.error("something went wrong")
}
});
});
My android, cloud code function
private void callCodeCloud() {
HashMap<String, String> params = new HashMap<String, String>();
params.put("objectId", objectId);
params.put("email", email);
ParseCloud.callFunctionInBackground("getEmail", params, new FunctionCallback<String>() {
public void done(String email, ParseException e) {
if (e == null) {
Toast.makeText(getApplication(), "Vote Sent", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplication(), "Error", Toast.LENGTH_SHORT).show();
}
}
});
}
private String returnVoteAnswer() {
int nIdRadio = radioVoteGroup.getCheckedRadioButtonId();
if (nIdRadio == R.id.optionone) {
optionONE.setText(mO);
callCodeCloud();
}
else if (nIdRadio == R.id.optiontwo) {
optionTWO.setText(mT);
callCodeCloud();
}
else if (nIdRadio == R.id.optionthree) {
optionTHREE.setText(mH);
callCodeCloud();
}
else{
}
return null;
}
I have been really struggling with this for a while now and I need guidance please. It will be really great achievement for me and fresher by finishing this app as this problem has become an obstacle for me in finishing this app.
Thanks in advance

Related

Using cached Cognito identity from Xamarin

When I first log into my app, I go through the following code:
auth = new Xamarin.Auth.OAuth2Authenticator(
"my-google-client-id.apps.googleusercontent.com",
string.Empty,
"openid",
new System.Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new System.Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new System.Uri("https://www.googleapis.com/oauth2/v4/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
Which triggers this activity:
[Activity(Label = "GoodleAuthInterceptor")]
[IntentFilter(actions: new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "com.enigmadream.storyvoque" }, DataPaths = new[] { "/oauth2redirect" })]
public class GoodleAuthInterceptor : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
MainActivity.auth?.OnPageLoading(uri_netfx);
Finish();
}
}
And finally this code to link the account to Cognito:
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = "us-east-2:79ebf8e1-97de-4d1c-959a-xxxxxxxxxxxx";
cli.GetIdAsync(req).ContinueWith((task) =>
{
if ((task.Status == TaskStatus.RanToCompletion) && (task.Result != null))
{
ShowMessage(string.Format("Identity {0} retrieved", task.Result.IdentityId));
}
else
ShowMessage(task.Exception.InnerException != null ? task.Exception.InnerException.Message : task.Exception.Message);
});
}
else
ShowMessage("Login cancelled");
}
This all works great, and after the login, I am able to use my identity/credentials to retrieve data from DynamoDB. With this object:
Amazon.DynamoDBv2.AmazonDynamoDBClient ddbc = new Amazon.DynamoDBv2.AmazonDynamoDBClient(credentials, RegionEndpoint.USEast2);
The second time I run my app, this code runs:
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
ShowMessage(string.Format("I still remember you're {0} ", idToken));
And if I try to use the credentials with DynamoDB (or anything, I assume) at this point, I get errors that I don't have access to the identity. I have to logout (credentials.Clear()) and login again to obtain proper credentials.
I could require that a user go through the whole login process every time my app runs, but that's a real pain because the Google login process requires the user to know how to manually close the web browser to get back to the application after authenticating. Is there something I'm missing about the purpose and usage of cached credentials? When I use most apps, they aren't requiring me to log into my Google account every time and close a web browser just to access their server resources.
It looks like the refresh token needs to be submitted back to the OAuth2 provider to get an updated id token to add to the credentials object. First I added some code to save and load the refresh_token in a config.json file:
private Dictionary<string, string> config;
const string CONFIG_FILE = "config.json";
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
if (e.Account.Properties.ContainsKey("refresh_token"))
{
if (config == null)
config = new Dictionary<string, string>();
config["refresh_token"] = e.Account.Properties["refresh_token"];
WriteConfig();
}
credentials.AddLogin("accounts.google.com", idToken);
CognitoLogin(idToken).ContinueWith((t) =>
{
try
{
t.Wait();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
ShowMessage("Login cancelled");
}
void WriteConfig()
{
using (var configWriter = new System.IO.StreamWriter(
Application.OpenFileOutput(CONFIG_FILE, Android.Content.FileCreationMode.Private)))
{
configWriter.Write(ThirdParty.Json.LitJson.JsonMapper.ToJson(config));
configWriter.Close();
}
}
public void Login()
{
try
{
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
if (ReadConfig())
{
LoginRefreshAsync().ContinueWith((t) =>
{
try
{
t.Wait();
if (!t.Result)
FullLogin();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
{
credentials.Clear();
FullLogin();
}
}
}
else
FullLogin();
bDidLogin = true;
}
catch(Exception ex)
{
ShowMessage(string.Format("Error logging in: {0}", ex.Message));
}
}
private bool ReadConfig()
{
bool bFound = false;
foreach (string filename in Application.FileList())
if (string.Compare(filename, CONFIG_FILE, true) == 0)
{
bFound = true;
break;
}
if (!bFound)
return false;
using (var configReader = new System.IO.StreamReader(Application.OpenFileInput(CONFIG_FILE)))
{
config = ThirdParty.Json.LitJson.JsonMapper.ToObject<Dictionary<string, string>>(configReader.ReadToEnd());
return true;
}
}
Then refactored the code that initiates the interactive login into a separate function:
public void FullLogin()
{
auth = new Xamarin.Auth.OAuth2Authenticator(CLIENTID_GOOGLE, string.Empty, "openid",
new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new Uri("https://accounts.google.com/o/oauth2/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
}
Refactored the code that retrieves a Cognito identity into its own function:
private async Task CognitoLogin(string idToken)
{
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = ID_POOL;
try
{
var result = await cli.GetIdAsync(req);
ShowMessage(string.Format("Identity {0} retrieved", result.IdentityId));
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
}
And finally implemented a function that can retrieve a new token based on the refresh token, insert it into the current Cognito credentials, and get an updated Cognito identity.
private async Task<bool> LoginRefreshAsync()
{
string tokenUrl = "https://accounts.google.com/o/oauth2/token";
try
{
using (System.Net.Http.HttpClient client = new System.Net.Http.HttpClient())
{
string contentString = string.Format(
"client_id={0}&grant_type=refresh_token&refresh_token={1}&",
Uri.EscapeDataString(CLIENTID_GOOGLE),
Uri.EscapeDataString(config["refresh_token"]));
System.Net.Http.HttpContent content = new System.Net.Http.ByteArrayContent(
System.Text.Encoding.UTF8.GetBytes(contentString));
content.Headers.Add("content-type", "application/x-www-form-urlencoded");
System.Net.Http.HttpResponseMessage msg = await client.PostAsync(tokenUrl, content);
string result = await msg.Content.ReadAsStringAsync();
string idToken = System.Json.JsonValue.Parse(result)["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
/* EDIT -- discovered this is not necessary! */
// await CognitoLogin(idToken);
return true;
}
}
catch (Exception ex)
{
ShowMessage(ex.Message);
return false;
}
}
I'm not sure if this is optimal or even correct, but it seems to work. I can use the resulting credentials to access DynamoDB without having to prompt the user for permission/credentials again.
There's a very different solution I'm trying to fit with the other answer. But it's so different, I'm adding it as a separate answer.
It appears the problem was not so much related to needing to explicitly use a refresh token to get an updated access token (I think this is done implicitly), but rather needing to remember the identity token. So rather than include all the complexity of manually applying a refresh token, all that's needed is to store the identity token (which can be done in a way similar to how the refresh token was being stored). Then we just need to add that same identity token back to the credentials object when it's missing.
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (config.Read())
{
if (config["id_token"] != null)
credentials.AddLogin(currentProvider.Name, config["id_token"]);
Edit: The problem of needing to use a refresh token does still exist. This code works if the token hasn't expired, but attempting to use these credentials after the token has expired will fail, so there is still some need to use a refresh token somehow in some cases.

Find whether user has subscribed youtube channel using Youtube data api

I am developing an application in which user can subscribe to various channels in my app itself. So to save api request i want to know whether user has already subscribed a youtube channel or not. While researching I have found some code and modified that to my requirements:
static public boolean checkIfUserAlreadySubscribed(String channelyoutubeid) {
SubscriptionListResponse response = null;
try {
HashMap<String, String> parameters = new HashMap<>();
parameters.put("part", "snippet,contentDetails");
parameters.put("forChannelId", channelyoutubeid);
parameters.put("mine", "true");
YouTube.Subscriptions.List subscriptionsListForChannelIdRequest = MainActivity
.mService.subscriptions().list(parameters.get("part").toString());
if (parameters.containsKey("forChannelId") && parameters.get("forChannelId") != "") {
subscriptionsListForChannelIdRequest.setForChannelId(parameters
.get("forChannelId").toString());
}
if (parameters.containsKey("mine") && parameters.get("mine") != "") {
boolean mine = (parameters.get("mine") == "true") ? true : false;
subscriptionsListForChannelIdRequest.setMine(mine);
}
response = subscriptionsListForChannelIdRequest.execute();
} catch (IOException e) {
e.printStackTrace();
}
if (response != null) {
//What should i do here
} else {
//whta should i pass
}
}
On executing my code response value is always not null whether user has subscribed or not .Can anyone suggest me what to do??
To retrieve channels which a user has already subscribed, you may want to try using Activities: list with mine parameter set to true. A successful request will return response body with activity resource:
"contentDetails": {
"subscription": {
"resourceId": {
"kind": string,
"channelId": string,
}
}
}
contentDetails.subscription.resourceId.channelId is the ID that YouTube uses to uniquely identify the channel that the user subscribed to.

Parse cloud function send JSON to specific user

I create an App that a user can send messages(notifications) in other users. Fot these perpose i use parse SDK. So i send the message from device into the parse cloud with below code.
final ParseQuery<ParseUser> query = ParseUser.getQuery();
query.whereEqualTo("email", "user#email.com");
query.getFirstInBackground(new GetCallback<ParseUser>() {
public void done(ParseUser object, ParseException e) {
if (e == null) {
Toast.makeText(getActivity(), "User found", Toast.LENGTH_SHORT).show();
String search_username = object.getString("username");
String id = object.getObjectId();
Log.d("ObjectID:",id);
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("recipientId", id);
params.put("message", username);
ParseCloud.callFunctionInBackground("sendPushToUser", params, new FunctionCallback<String>() {
public void done(String success, ParseException e) {
if (e == null) {
// Push sent successfully
Toast.makeText(getActivity(), "Request send", Toast.LENGTH_SHORT).show();
}
}
});
Then i have the next cloud function for recieve and push the message to the specific user.
Parse.Cloud.define("sendPushToUser", function(request,response){
var senderUser = request.user;
var recipientUserId = request.params.recipientId;
var message = request.params.message;
var title ="Friend Request";
if(message.length > 140){
message = message.substring(0, 137) + "...";
}
var recipientUser = new Parse.User();
recipientUser.id = recipientUserId;
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.equalTo("user", recipientUser);
Parse.Push.send({
where: pushQuery,
data: {
"alert":{"data":{"message":"message",
"title":"title"}}
}
}).then(function(){
response.success("true")
}, function(error) {
response.error("Push failed to send with error: "+error.message);
});
});
But the message never been received. If i sent a push notification from parse dashboard everything works fine. Anyone knows how to solve it? The device expect a JSON to received so may my cloud function didnt send data in json format? Thanks in advance
I had problems while sending notifications because of 2 things
enabling client push "not in your case"
didn't save the user in the installation "try the following"
After the user logs into your app add his id to the installation by
ParseInstallation installation = ParseInstallation.getCurrentInstallation();
installation.addUnique("userId", currentUser.getObjectId());
installation.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
// check the returned exception
if (e == null) {
// everything worked fine
} else {
// error occurred
}
}
});
hope it helps :)
Update
In your code you're sending the recipient userId although you saved the username also in your cloud function you have the same problem, the username is saved but you query the installation based on the id. I've updated the installation above also change the "user" in your cloud function to the "userId"
pushQuery.equalTo("userId", recipientUser);

How to logout/change Twitter account with Parse

I know how to login:
ParseTwitterUtils.logIn(loginView.getCurrentContext(), new LogInCallback() {
#Override
public void done(ParseUser parseUser, ParseException e) {
if (e == null) {
String welcomeMessage = "";
if (parseUser.isNew()) {
welcomeMessage = "Hello new guy!";
} else {
welcomeMessage = "Welcome back!";
}
loginView.showLoginSuccess(parseUser, welcomeMessage);
} else {
String errorMessage = "Seems we have a problem : " + e.getLocalizedMessage();
loginView.showLoginFail(errorMessage);
}
}
});
And to logout :
ParseUser.logOutInBackground(new LogOutCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
homeView.goLogin(true, "See you soon");
} else {
homeView.goLogin(false, "Error detected : " + e.getLocalizedMessage());
}
}
});
But when I want to log in again, I don't have the alert dialog asking me to choose accounts (i use the webview since Twitter app is not installed on the emulator).
How to truly logout from Parse using Twitter login?
In iOS, you can revise the source code of Parse in PFOauth1FlowDialog.m
- (void)loadURL:(NSURL *)url queryParameters:(NSDictionary *)parameters {
NSMutableDictionary *_parameter = [[NSMutableDictionary alloc] init];
[_parameter setObject:#"true" forKey:#"force_login"];
[_parameter addEntriesFromDictionary:parameters];
_loadingURL = [[self class] _urlFromBaseURL:url queryParameters:_parameter];
NSURLRequest *request = [NSURLRequest requestWithURL:_loadingURL];
[_webView loadRequest:request];
}
Then everything should work fine, And this should also work in Android.
Use the unlink functions from ParseTwitterUtils:
https://parse.com/docs/android/api/com/parse/ParseTwitterUtils.html#unlink(com.parse.ParseUser)
This will remove the link between the twitter account and the parse user.
The confusion seems to stem from the fact that the api is so straightforward.
What you're doing in the login is associating a twitter account with a parse user and logging in as that parse user. Then when you are logging out, you are only logging out of the parse user, and the twitter account is still linked to the parse user. Therefore when you go to log in again it automatically uses the twitter account to log in as the parse user.

Android: How to input values to Parse cloud code, eg beforesave

Inside a app, users will upload slot results with period name to the Parse Database. However, before upload, it would be much preferred if beforesave, checked whether the period ref is already there, if the same period ref is existing in the DB, the slot result would not be uploaded.
Cloud.beforesave
Parse.Cloud.beforeSave("check_duplicate", function(request, response)
{
var DB = Parse.Object.extend("Record_db");
var query = new Parse.Query(DB);
query.equalTo("period_ref", request.object.get("period_ref"));
query.first
({
success: function(object)
{
if (object)
{
response.error("A Period with this ref already exists.");
}
else
{
response.success();
}
},
error: function(error)
{
response.error("Could not validate uniqueness for this period ref object.");
}
});
});
Android code:
ParseCloud.callFunctionInBackground("check_duplicate", new HashMap<String, Object>(), new FunctionCallback<String>() {
public void done(String result, ParseException e)
{
if (e == null)
{
Utilities.custom_toast(CurrentResult.this, "cloud success" + result, "gone!", "short");
}
else
{
Utilities.custom_toast(CurrentResult.this, "cloud error" + e, "gone!", "short");
}
}
});
Question:
There is no clear example for such common situation. I would like to ask
for example, now the user would like to upload slot ref 001/2015 results. All info are already available at device, how could I pass this period reference 001/2015 to the cloud code for checking whether it is already existing in the Cloud DB uploading and saving to the Cloud DB?
Thanks a lot!
your first line of Android...
ParseCloud.callFunctionInBackground("check_duplicate", new HashMap(), new FunctionCallback() {
becomes
ParseCloud.callFunctionInBackground("check_duplicate",
new HashMap<String, String>{"period_ref":"001/2015"};,
new FunctionCallback<String>() {

Categories

Resources