1

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;
}
leonheess
  • 16,068
  • 14
  • 77
  • 112

1 Answers1

1
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.

nasch
  • 5,330
  • 6
  • 31
  • 52
  • I have a call like `if (checkTaken(username, context)) {setError..}`. Could you elaborate on how to implement a callback interface? I'm fairly new to Android development. – leonheess Dec 10 '18 at 22:12
  • 1
    This tutorial is oriented more toward events, but hopefully it will get the idea across: https://guides.codepath.com/android/Creating-Custom-Listeners – nasch Dec 10 '18 at 22:16
  • I read the tutorial and tried to implement it but I don't really have different objects. I only have this single Activity with a method that checking the input and the on you see in my question. Could you give me a hint how I would implement a listener there and how the `if (...)` would look like? – leonheess Dec 10 '18 at 22:40
  • 1
    Just declare the interface as a nested interface (and look up "java nested static class" or "java nested interface" if you don't know what that is). Once you have it defined, you pass in an instance; generally this is done using a lambda expression, just like you're doing in the code you already have: `result -> doStuff(result)`. – nasch Dec 10 '18 at 22:43
  • I'm really sorry to bother you again but I'm having a hard time understanding your suggestion. Would you mind editing your answer to include the call of `checkTaken()`? I also edited my question so with my call in case that is of any help. – leonheess Dec 10 '18 at 22:51
  • A little bit. Where do you define `done()` and how do you call `whatever()` when it needs a callback itself? Where does it originate? – leonheess Dec 10 '18 at 23:11
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/185030/discussion-between-nasch-and-mixt4pe). – nasch Dec 10 '18 at 23:12