Reputation: 21391
I'm trying to implement "live" input validation. The problem I have been facing for a while now is how to live check if the username already exists in the database.
For all database requests I am using Volley POSTs to an PHP API which resulted in:
private boolean checkTaken(String username, Context context){
boolean taken;
Volley.getInstance(context).addToRequestQueue(new StringRequest(Request.Method.POST, url, output -> taken = output.contains("taken"), error -> {}) {
@Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("username", username);
return params;
}
@Override
public Map<String, String> getHeaders() {
Map<String, String> params = new HashMap<>();
params.put("Content-Type", "application/x-www-form-urlencoded");
return params;
}
});
return taken;
}
Which would have been great but doesn't work because the return statement is reached long before the API response has arrived. That's when I discovered RequestFutures and tried to implement them as I saw here:
private boolean checkTaken(String username, Context context){
RequestFuture<String> taken = RequestFuture.newFuture();
Volley.getInstance(context).addToRequestQueue(new StringRequest(Request.Method.POST, url, taken, error -> {}) {
@Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("username", username);
return params;
}
@Override
public Map<String, String> getHeaders() {
Map<String, String> params = new HashMap<>();
params.put("Content-Type", "application/x-www-form-urlencoded");
return params;
}
});
try {
return taken.get(500, TimeUnit.MILLISECONDS).contains("taken");
} catch (InterruptedException | ExecutionException | TimeoutException e) {
return false;
}
}
Unfortunately it doesn't work even if I increase the timeout to an unbearable amount, the taken.get()
never gets resolved. I think it's because the taken
used in the request is interpreted as a new variable for the output and not as the actual FutureRequest.
I would be very thankful for a hint why this doesn't work or another solution for my problem.
Method call:
if (!email.matches(regex)){
return "Username has an invalid format";
} else if (checkTaken(username, context)){
return "Username already taken";
} else{
return null;
}
Upvotes: 1
Views: 105
Reputation: 5498
private void checkTaken(String username, Context context, Callback callback){
boolean taken;
Volley.getInstance(context).addToRequestQueue(new StringRequest(Request.Method.POST, url,
output -> callback.done(output.contains("taken")),
error -> {
// TODO handle this error
}) {
@Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("username", username);
return params;
}
@Override
public Map<String, String> getHeaders() {
Map<String, String> params = new HashMap<>();
params.put("Content-Type", "application/x-www-form-urlencoded");
return params;
}
});
}
Define your Callback interface and pass in a lambda expression from wherever you're calling checkTaken
:
// This method wherever this is defined also needs to accept a callback so it can be asynchronous
public void whatever(StringCallback callback) {
// ...
if (!email.matches(regex)){
callback.done("Username has an invalid format");
} else {
checkTaken(username, context, taken ->
callback.done(taken ? "Username already taken" : null));
}
And then wherever you call that:
whatever(error -> {
if(error == null) {
handleSuccess();
}
else {
handleError(error);
}
);
This may seem like an annoying and complicated chain of changes you have to make, but when you move from synchronous to asynchronous, there are often a lot of ripple effects, and since you cannot do network operations on the main thread in Android, you will often find yourself writing asynchronous methods.
Upvotes: 1