RetroFit Failing to send POST request - android

I've been trying to send a POST request to a php script in order to update the value of a table in a MySQL database. I've been using RetroFit of late and have used it to send quite a few POST request for POST based operations. I don't know if my problem occurs because I am violating HTTP best practices which would recommend to use PUT for update operations but I have also tried sending a PUT request from the Client although it had the same outcome. The database values were not changed when I checked them. Here is the code where I send the HTTP Post Request from RetroFit.
final JsonDataAPI API = RetroFitClient.getInstance().getAPI();
Map<String, String> voteParams = new HashMap<String, String>();
voteParams.put("pollID", String.valueOf(pollID));
voteParams.put("optText", rb1.getTag().toString());
Call <ArrayList<PollOpt>> voteCall = API.makeVote(voteParams);
voteCall.enqueue(new Callback<ArrayList<PollOpt>>() {
#Override
public void onResponse(Call<ArrayList<PollOpt>> call, Response<ArrayList<PollOpt>> response) {
if(!response.isSuccessful()){
Toast.makeText(PollPage.this, "Vote Unsuccessful for vote 2",
Toast.LENGTH_SHORT).show();
} else{
Toast.makeText(PollPage.this, "Vote Successful for vote 2",
Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<ArrayList<PollOpt>> call, Throwable t) {
Log.d("TAG", "Vote Failure: " + t.getMessage());
Log.d("TAG", "FailureURL rb2: " + call.request().url().toString());
}
});
The URL of the requests is just fine it even prints the request in the onFailure function which fires as there is no response, although I don't expect a response.
This is the part of the PHP API code the request is getting sent too Vote.php:
'
method = $_SERVER['REQUEST_METHOD'];
switch($method){
case "POST":
if(isset($_POST["optText"]) && isset($_POST["pollID"])){
$optText = $_POST["optText"];
$pollID = (int) $_POST["pollID"];
$query = "UPDATE polloption SET frequency = frequency + 1 WHERE pollID = " . $pollID . " AND optText = '". $optText ."'";
$conn = new mysqli($host, $user, $pass, $database);
if ($conn->connect_error)
die($conn->connect_error);
if(mysqli_query($conn, $query)){
echo "Updated Poll Frequency";
} else{
echo "Failed to Update Poll Frequency";
}
}
break;'
I've tested the PHP code out using AJAX scripts seeing as you can see phps echo data in the AJAX Requests response whereas in Android you are basically working in the dark when sending HTTP Requests with methods other than GET and determining errors without having to individually test each file with an AJAX Request. After testing the server with AJAX scripts multiple times with different parameters, I can't see why the frequency value doesn't update when sending POST requests through RetroFit.
Any advice on this would be much appreciated!

Dont use isset($_POST["optText"]) && isset($_POST["pollID"]) to get Values from Json based body requests just use
$post_body = file_get_contents('php://input');
for php

Related

PendingResult.setResultCallback() always returns the statusCode success

if i connect my google watch with a mobile device successfully, and then disable the bluetooth connection (for test reasons) and make a google api client call to my mobile device, the pending result always returns the status code success, even if its not successfull because there is no more connection
async task for the request
class DataTask extends AsyncTask<Node, Void, Void> {
#Override
protected Void doInBackground(Node... nodes) {
Gson gson = new Gson();
Request requestObject = new Request();
requestObject.setType(Constants.REQUEST_TYPE);
String jsonString = gson.toJson(requestObject);
PutDataMapRequest dataMap = PutDataMapRequest.create(Constants.PATH_REQUEST);
dataMap.setUrgent();
dataMap.getDataMap().putString(Constants.KEY_REQUEST, jsonString);
PutDataRequest request = dataMap.asPutDataRequest();
DataApi.DataItemResult dataItemResult = Wearable.DataApi
.putDataItem(googleApiClient, request).await();
boolean connected = googleApiClient.isConnected();
PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(googleApiClient, request);
pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
#Override
public void onResult(#NonNull DataApi.DataItemResult dataItemResult) {
com.google.android.gms.common.api.Status status = dataItemResult.getStatus();
DataItem dataItem = dataItemResult.getDataItem();
boolean dataValid = dataItemResult.getDataItem().isDataValid();
boolean canceled = status.isCanceled();
boolean interrupted = status.isInterrupted();
float statusCode = status.getStatusCode();
if(status.isSuccess()){ // expected to be false because there is no bluetooth connection anymore
Log.d(TAG, "Success");
}else{
Log.d(TAG, "Failure");
}
}
});
return null;
}
}
why do i not get a false for status.isSuccess?
the only solution i found is to write following code inside the AsyncTask:
Wearable.NodeApi.getConnectedNodes(googleApiClient).await().getNodes();
if(connectedNodes.size() == 0){
// no connection
}
is it not possible to check if the request was successfully inside the ResultCallback?
I believe that the getStatus() call for DataItemResult is only indicating whether the call was successfully passed off to the Data API, not whether it was successfully relayed to another node. The Data API is asynchronous - it's a "store and forward" architecture - so it's not reasonable to expect it to notify you immediately of successful delivery.
In fact, I don't think that there is a way to determine from the Data API when your DataItem has been delivered at all. Your getConnectedNodes technique is only telling you that the watch is connected, not that the data has been delivered. If you need proof of delivery, you'll probably have to implement that yourself, perhaps using the Message API.
One other note: given you've wrapped your code in an AsyncTask, there's no need to use PendingResult.setResultCallback. You can simply await the result inline: http://developer.android.com/training/wearables/data-layer/events.html#sync-waiting

GCM - Device not registrered despite returned token

In my Xamarin Android app I call
var instanceID = InstanceID.GetInstance(this);
string token = instanceID.GetToken("xxx", GoogleCloudMessaging.InstanceIdScope, null);
and I get a token in return in the format e63498f:oijafa89fjaasi...
In my c# program I call
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=xxxx");
//Get current connection
string url = "https://gcm-http.googleapis.com/gcm/send";
var message = new JObject();
var data = new JObject();
data.Add("message", "hello from csharp");
message.Add("to", "e63498f:oijafa89fjaasi...");
message.Add("data", data);
HttpResponseMessage response = null;
try
{
response = await client.PostAsync(url, new StringContent(message.ToString(), Encoding.Default, "application/json"));
}
catch (Exception exp)
{
MessageBox.Show(exp.Message);
return;
}
//Handle errors
if (!response.IsSuccessStatusCode)
MessageBox.Show("Error: " + response.ToString());
string responseBody = await response.Content.ReadAsStringAsync();
textBox2.Text = responseBody;
I get the response:
{Text = "{\"multicast_id\":xxxx,\"success\":0,\"failure\":1,\"canonical_ids\":0,\"results\":[{\"error\":\"NotRegistered\"}]}"}
I have tryed alot of things but I cannot get it working. If I use the old GCM (gcm.register) there is no error message, but I don't want to use deprecated functionality. Why does GCM say that the token is not registrered when I just got a token returned from GCM? (The app is of course open while I do the test). Do I need to call some sort of method to actually register the token?
Found this similar thread regarding GCM in the Xamarin forums that discusses the same issue. Mentioned in the thread "The problem seems to be that deployments to the VM may be triggering the uninstall scenario.". A workaround/solution is also included:
"The solution I came up with was to track tokens where I receive a Not Registered response on the server, if a device indicates they want to use that token I respond with a send-a-new-token response. The way to accomplish this is to delete the InstanceId and then trigger the registration service
Google cloud message 'Not Registered' failure and unsubscribe best practices?"
Care to try it out. Let me know if it works.

Android Studio Login/Register System ideas

I want to make a diet helper app for android devices, using android studio and
I need ideas on what to use to implement the login/register system, I followed a tutorial on youtube but it was outdated and I ended up wasting my time, then I've read on google, that android studio has a library called volley that I can use with PHP and MySql to make the login system.
Do you have other ideas, or is that the best one to go with?
I'm open to suggestions so shoot!
Update:
I've created a post about how to do this using a PHP backend for your Android application. https://keithweaver.ca/posts/4/android-php-custom-login
Additionally to the link above, this is how you can setup a server.
https://github.com/kweaver00/tutorials/blob/master/setup-server.md
https://keithweaver.ca/posts/9/setup-ubuntu-server-quickly
Original Post:
This is one solution and isn't guaranteed to be the best.
You can really use anything to communicate with a server. Async Tasks or Retrofit are both popular.
Assuming you have set up a server with a LAMP stack. Make sure you have an SSL so you don't pass user information that isn't encrypted.
Create a user table in mysql
Ex.
id int default->NULL AI primary-key
user varchar 250 default->null
pass varchar 250 default->null
signupdate date default-> null
Create a log in sessions table of some sort
Ex.
id int default->NULL AI primary-key
user varchar 250 default->null
token varchar 250 default->null
addedDate date default->null
Create a log in php script (I know this probably isnt the best way to right php code)
$connection = mysqli_connect("localhost", "phpmysqluser", "password", "dbname") or die("Error 404: unable to connect");
$username = $_POST['user'];
$pass = $_POST['pass'];
//add code to remove slashes and etc.
$result = mysqli_query($connection, "SELECT * FROM userTable WHERE user='$username' AND pass='$pass'") or die("Error: this line has error");
class response{
public $loggedin =0;
public $message = "";
}
$response = new response();
if(mysqli_num_rows($result) == 1){
$logInToken = generateLogInToken();
//have a function that creates a unique token and stores it for X days or minutes
$response->loggedin = 1;
$response->message = $logInToken;
}else{
$response->message = "wrong info";
}
echo json_decode($response);
This should output a json file like this depending on your user and pass variables.
{
"loggedin" : 1,
"message" : "asdnlansdkansd"
}
Right another script that passes in the log in token and user name to check if it's valid.
$connection .... //same as above
//well it really should be a include_once cause if you change credentials
$token = $_POST['token'];
$user = $_POST['user'];
$registeredDate = "";
$today = date('Y-m-d');
$result = mysqli_query($connection, "SELECT * FROM tokenTable WHERE user='$user' AND token='$token'") or die("Error...");
class response{
public $status = 0;
}
$response = new response();
if(mysqli_num_rows($result) == 1){
//check token has been register today and if not sign them out
while($row = mysqli_fetch_array($result)){
$registeredDate = $row['addedDate'];
}
if($registeredDate == $today){
//token is valid
$response->status = 3;
}else{
//expired
$response->status = 2;
}
}else{
//user and token are not valid
$response->status = 1;
}
echo json_decode($response);
Giving a json object like:
{
"status" : 3
}
In your Android app on open, run the code to check if the account is valid if there is anything stored locally. Or just go to log in screen.
On splash screen in the onCreate (you dont need a splash screen, its actually not recommended but its the easiest way to explain the process):
if(userNameAndTokenStoredInSharedPref()){
String token = getTokenFromSharedPref();
String userName = getUserNameFromSharedPref();
checkAgainstServer(token, userName);
}else{
Intent openLogInWindow = new Intent(this, LogInActivity.class);
startActivity(openLogInWindow);
}
still in the slash activity but out of the oncreate:
protected void checkAgainstServer(String token, String user){
//using retrofit
ThisAppRestClient.get().postCheckTokenAndUser(token, user, new Callback<UserStatusCallBack>() {
#Override
public void success(UserStatusCallBack userStatusCallback, retrofit.client.Response response) {
if(userStatusCallback.getStatus() == 1){
//Invalid token
}else if(userStatusCallback.getStatus() == 2){
//Expired token
}else if(userStatusCallback.getStatus() == 3){
//Success
Intent openMainWindow = new Intent(this, MainActivity.class);
startActivity(openMainWindow);
}
}
#Override
public void failure(RetrofitError error) {
//Retrofit errors like timeouts, etc.
}
}
}
The log in activity would be something like:
logBtn.setOnClickListener(new View.onClick...
String userName = userNameEditText.getText().toString().toLowerCase().trim();
String password = passwordEditText.getText().toString().trim();
if(!TextUtils.isEmpty(userName) && !TextUtils.isEmpty(password)){
callServerLogInScript(userName, password);
}
userNameEditText.setText("");
logBtn.setVisibility(View.GONE);
}
lower down the file:
protected void callServerLogInScript(String user, String pass){
//using retrofit
ThisAppRestClient.get().postCheckTokenAndUser(user, pass, new Callback<LogInCallBack>() {
#Override
public void success(LogInCallBack logInCallback, retrofit.client.Response response) {
if(logInCallback.getLoggedIn() == 1){
//succssful
storeUserNameInSharedPref(user);
storeTokenInSharedPref(logInCallback.getMessage());
Intent openMainActivity = new Intent(this, MainActivity.class);
startActivity(openMainActivity);
}else{
//incorrect log in
logBtn.setVisibility(View.VISIBLE);
}
}
#Override
public void failure(RetrofitError error) {
//Retrofit errors like timeouts, etc.
}
}
}
The reason for not storing the user name and password directly is if the device is rooted they can manipulate the data locally but not on your server.
It depends which you want to use. If you have your own server to host, then use php,mysql. If not, you can also use other third party which provides you to add if you know php,mysql to create.
Another option is if you don't want to use php mysql to store datas, then you can proceed with parse.com
So if you want to use parse.com, just register it. It's free to use.
Hope it will match your requirement, say for eg: if you want to create registration(everything saving in datas will be handled),you need to give exact object name that matches what you given in parse.com
Even you can also create in code itself without object name. I will show you a piece of example how to create and insert for registration..
ParseUser user = new ParseUser();
user.setEmail((txtEmail));//create an edittext and get the values in strings and store..
user.setPassword(txtPassword);//same for password
user.setUsername(txtUsername);//username
user.signUpInBackground(new SignUpCallback() {
public void done(ParseException e) {
if (e == null) {
//completed..it has been registered
Toast.makeText(getApplicationContext(),
"Successfully Signed up, please log in.",
Toast.LENGTH_LONG).show();
finish();
} else {
Toast.makeText(getApplicationContext(),
"Sign up Error", Toast.LENGTH_LONG)
.show();
}
}
});
Simple one if you don't want to use php,mysql. Well documentation and easy to integrate and use it. Happy coding.
FYI: Android studio is IDE for development. And volley is HTTP library that makes networking for Android.

Android volley to handle redirect

I recently started to use Volley lib from Google for my network requests. One of my requests get error 301 for redirect, so my question is that can volley handle redirect somehow automatically or do I have to handle it manually in parseNetworkError or use some kind of RetryPolicyhere?
Thanks.
Replace your url like that url.replace("http", "https");
for example:
if your url looking like that : "http://graph.facebook......." than
it should be like : "https://graph.facebook......."
it works for me
I fixed it catching the http status 301 or 302, reading redirect url and setting it to request then throwing expection which triggers retry.
Edit: Here are the main keys in volley lib which i modified:
Added method public void setUrl(final String url) for class Request
In class BasicNetwork is added check for redirection after // Handle cache validation, if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY) || statusCode == HttpStatus.SC_MOVED_TEMPORARILY), there I read the redirect url with responseHeaders.get("location"), call setUrl with request object and throw error
Error get's catched and it calls attemptRetryOnException
You also need to have RetryPolicy set for the Request (see DefaultRetryPolicy for this)
If you dont want to modify the Volley lib you can catch the 301 and manually re-send the request.
In your GsonRequest class implement deliverError and create a new Request object with the new Location url from the header and insert that to the request queue.
Something like this:
#Override
public void deliverError(final VolleyError error) {
Log.d(TAG, "deliverError");
final int status = error.networkResponse.statusCode;
// Handle 30x
if(HttpURLConnection.HTTP_MOVED_PERM == status || status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_SEE_OTHER) {
final String location = error.networkResponse.headers.get("Location");
Log.d(TAG, "Location: " + location);
final GsonRequest<T> request = new GsonRequest<T>(method, location, jsonRequest, this.requestContentType, this.clazz, this.ttl, this.listener, this.errorListener);
// Construct a request clone and change the url to redirect location.
RequestManager.getRequestQueue().add(request);
}
}
This way you can keep updating Volley and not have to worry about things breaking.
Like many others, I was simply confused about why Volley wasn't following redirects automatically. By looking at the source code I found that while Volley will set the redirect URL correctly on its own, it won't actually follow it unless the request's retry policy specifies to "retry" at least once. Inexplicably, the default retry policy sets maxNumRetries to 0. So the fix is to set a retry policy with 1 retry (10s timeout and 1x back-off copied from default):
request.setRetryPolicy(new DefaultRetryPolicy(10000, 1, 1.0f))
For reference, here is the source code:
/**
* Constructs a new retry policy.
* #param initialTimeoutMs The initial timeout for the policy.
* #param maxNumRetries The maximum number of retries.
* #param backoffMultiplier Backoff multiplier for the policy.
*/
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
Alternatively, you can create a custom implementation of RetryPolicy that only "retries" in the event of a 301 or 302.
Hope this helps someone!
End up doing a merge of what most #niko and #slott answered:
// Request impl class
// ...
#Override
public void deliverError(VolleyError error) {
super.deliverError(error);
Log.e(TAG, error.getMessage(), error);
final int status = error.networkResponse.statusCode;
// Handle 30x
if (status == HttpURLConnection.HTTP_MOVED_PERM ||
status == HttpURLConnection.HTTP_MOVED_TEMP ||
status == HttpURLConnection.HTTP_SEE_OTHER) {
final String location = error.networkResponse.headers.get("Location");
if (BuildConfig.DEBUG) {
Log.d(TAG, "Location: " + location);
}
// TODO: create new request with new location
// TODO: enqueue new request
}
}
#Override
public String getUrl() {
String url = super.getUrl();
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url; // use http by default
}
return url;
}
It worked well overriding StringRequest methods.
Hope it can help someone.
Volley supports redirection without any patches, no need for a separate fork
Explanation:
Volley internally uses HttpClient which by default follows 301/302 unless specified otherwise
From: http://hc.apache.org/httpcomponents-client-4.2.x/tutorial/html/httpagent.html
ClientPNames.HANDLE_REDIRECTS='http.protocol.handle-redirects': defines whether redirects should be handled automatically. This parameter expects a value of type java.lang.Boolean. If this parameter is not set HttpClient will handle redirects automatically.
ok, im a bit late to the game here, but i've recently been trying to achieve this same aspect, so https://stackoverflow.com/a/17483037/2423312 is the best one, given that you are willing to fork volley and maintain it and the answer here : https://stackoverflow.com/a/27566737/2423312 - I'm not sure how this even worked.This one is spot on though : https://stackoverflow.com/a/28454312/2423312. But its actually adding a new request object to the NetworkDipatcher's queue, so you'll have to notify the caller as well somehow, there is one dirty way where you can do this by not modifying the request object + changing the field "mURL", PLEASE NOTE THAT THIS IS DEPENDENT ON YOUR IMPLEMENTATION OF VOLLEY'S RetryPolicy.java INTERFACE AND HOW YOUR CLASSES EXTENDING Request.java CLASS ARE, here you go : welcome REFLECTION
Class volleyRequestClass = request.getClass().getSuperclass();
Field urlField = volleyRequestClass.getDeclaredField("mUrl");
urlField.setAccessible(true);
urlField.set(request, newRedirectURL);
Personally I'd prefer cloning volley though. Plus looks like volley's example BasicNetwork class was designed to fail at redirects : https://github.com/google/volley/blob/ddfb86659df59e7293df9277da216d73c34aa800/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java#L156 so i guess they arent leaning too much on redirects, feel free to suggest/edit. Always looking for good way..
I am using volley:1.1.1 with https url though the request was having some issue. On digging deeper i found that my request method was getting changed from POST to GET due to redirect (permanent redirect 301). I am using using nginx and in server block i was having a rewrite rule that was causing the issue.
So in short everything seems good with latest version of volley. My utility function here-
public void makePostRequest(String url, JSONObject body, final AjaxCallback ajaxCallback) {
try {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST,
url, body, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
Log.d(LOG, response.toString());
ajaxCallback.onSuccess(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e(LOG, error.toString());
ajaxCallback.onError(error);
}
});
singleton.getRequestQueue().add(jsonObjectRequest);
} catch(Exception e) {
Log.d(LOG, "Exception makePostRequest");
e.printStackTrace();
}
}
// separate file
public interface AjaxCallback {
void onSuccess(JSONObject response);
void onError(VolleyError error);
}

SIP Stack registration packet with authorization

we want to create SIP application on Android 2.3.3 and have some issues with android.sip stack (default sip stack). Our mobile app sends register sip packet, but
1.) by default OpenIMS core responds 400 Bad request P-Visited-Network-ID Header missing
2.) in the case that we set port number to 4060 -PCSCF /builder.setPort(4060)/ OpenIMS core sends this request from 4060 to 4060 (same port, same IP, same CSCF, same packet) and this is cykling until OpenIMS core send respond to mobile app - 504 Server Time-out.
We also tried SipDemo, CSipSimple and we had same problems.
When we tried Monster Communicator or IMSDroid, then it works!
There is one difference between working and problematic applications - working apps send register packet also with Authorization field.
Part of the code:
public SipManager mSipManager = null;
public SipProfile mSipProfile = null;
SipProfile.Builder builder = new SipProfile.Builder(username, domain);
builder.setPassword(password);
builder.setDisplayName(username);
builder.setProfileName(username + "#" + domain);
port = Integer.parseInt(4060);
builder.setProtocol(protocol);
mSipProfile = builder.build();
...
try { mSipManager.open(mSipProfile);} catch (SipException e) { ...}
try {
mSipManager.register(mSipProfile, 30, new SipRegistrationListener(){
public void onRegistering(String localProfileUri) {
}
public void onRegistrationDone(String localProfileUri, long expiryTime) {
}
public void onRegistrationFailed(String localProfileUri, int errorCode, String errorMessage) {
}
});
} catch (SipException e) {
....
}
How to give authorization field to register packet in classic SIP stack?
We also tried J-SIP but it display error: Conversion to dalvik format failed with error 1.
Every answer would be very appreciated.
Your problem is not related to missing Authorization header.
Registration is done in the following matter:
the client send Register request without "Authorization" header.
server response with 401 response code which includes an header named "WWW-Authnticate", that header hold parameters as realm, opaque, qop and hashing algorithm type.
using these parameters with the username and passord an Authorication header is generated automatically by SIP stacks. and a second Register request is sent which includes the "Authorication" header.
the if the request is send in the correct manner the server return 200 OK response code which means that you are now registered.
Your problem is something else, you don't even get to step 3 (Authorization step), you fail in step 1, for your initial Register request you receive 400 Bad Request response code - which almost always mean that you have a syntax error in your request.

Categories

Resources