Updating AtomicBoolean from within lambda expression

Viewed 256

I'm passing a lambda expression as an argument to a method, and I want to modify a variable that is defined outside of the lambda expression.

I've tried a few things. Currently, I have an AtomicBoolean called success. Within the lambda expression, I'm calling success.set(true). However, when I log the value of the success variable, it's still false and doesn't seem to be updating.

Basically, I'm calling a function with a void return type and need to know if the function was successful.

What am I doing wrong?

Here's the code:

public void createUser(String email, String password) {
    AtomicBoolean success = new AtomicBoolean(false);
    Amplify.Auth.signUp(
        email,
        password,
        AuthSignUpOptions.builder()
            .userAttribute(AuthUserAttributeKey.email(), email)
            .build(),
        result -> success.set(true),
        error -> Log.e("AuthQuickStart", "Sign up failed", error)
    );
    Log.d("success", String.valueOf(success.get()));
}
3 Answers

If the signUp method is blocking (i.e. it returns once the signup is finished), your code should work as expected. So I suspect it is asynchronous.

In that case, on top of using a boolean, you could also use a CountDownLatch to wait until the async method completes:

AtomicBoolean success = new AtomicBoolean(false);
CountDownLatch done = new CountDownLatch(1);
Amplify.Auth.signUp(
        email,
        password,
        AuthSignUpOptions.builder().userAttribute(AuthUserAttributeKey.email(), email).build(),
        result -> { success.set(true); done.countDown(); },
        error -> { Log.e("AuthQuickStart", "Sign up failed", error); done.countDown(); }
);
done.await(); //you may want to add a timeout here
Log.d("success", String.valueOf(success.get()));

An AtomicBoolean variable (success) is used correctly. Most probably,

Log.d("success", String.valueOf(success.get()));

is called before

result -> success.set(true)

because of asynchronous execution of callback. Alternatively result callback is not called at all.

You can test it by changing callback body to:

 result -> {
     Log.d("callback test", "callback start");
     result -> success.set(true);
     Log.d("callback test", "callback end");
}

If "callback test" doesn't appear in the logs then callback is not called.
If "callback test" appear after "success" then callback body is called after Log.d("success", String.valueOf(success.get())); due to asynchronous execution.

@assylias's and @jakub-bialy's answers are totally valid. However, neither considers the error callback. Additionally, using a CountDownLatch directly is a little low-level.

Amplify provides an rxbindings module, which can help simplify asynchronous programming.

In your case, you could use the Rx version of the signUp(...) method, like so:

// Change your initialization to:
RxAmplify.addPlugin(new AWSCognitoAuthPlugin())
RxAmplify.configure(getApplicationContext())

// Sign-up, in a blocking way:
AuthSignUpOptions options = AuthSignUpOptions.builder()
    .userAttribute(AuthUserAttributeKey.email(), email)
    .build();
AuthSignUpResult result =
    RxAmplify.Auth.signUp(email, password, options)
        .timeout(10, TimeUnit.SECONDS)
        .blockingGet();

The signUp(...) call in this case will throw the error in a blocking way, in the thread that makes the call. In the other answers, this error is ignored.

Related