Ping SDKs

Step 4b. Implement Android bridge code

In this step you implement the main Android bridging code that provides the callable methods for the Flutter layer.

  1. In Android Studio, open sdk-sample-apps/flutter/flutter_todo, and navigate to the android/app/src/main/java/com/example/flutter_todo/FRAuthSampleBridge.java file.

  2. Update the Configuration class with the details of your server, and the authentication journey and public OAuth 2.0 client you created earlier.

A hypothetical example (your values may vary):

FRAuthSampleBridge.java
class Configuration {
    / The main authentication journey name. */
    static String mainAuthenticationJourney = "sdkUsernamePasswordJourney";
    / The URL of the authentication server. */
    static String amURL = "https://openam-forgerock-sdks.forgeblocks.com/am";
    / The name of the cookie used for authentication. */
    static String cookieName = "ch15fefc5407912";
    / The realm used for authentication. */
    static String realm = "alpha";
    / The OAuth client ID. */
    static String oauthClientId = "sdkPublicClient";
    / The OAuth redirect URI. */
    static String oauthRedirectURI = "https://com.example.flutter.todo/callback";
    / The OAuth scopes. */
    static String oauthScopes = "openid profile email address";
    / The discovery endpoint for OAuth configuration. */
    static String discoveryEndpoint = "https://openam-forgerock-sdks.forgeblocks.com/am/oauth2/realms/root/realms/alpha/.well-known/openid-configuration";
}

Write the start() method

For the SDK to initialize, write the start function as follows:

FRAuthSampleBridge.java
public void start(MethodChannel.Result promise) {
    Logger.set(Logger.Level.DEBUG);
    FROptions options = FROptionsBuilder.build(frOptionsBuilder -> {
        frOptionsBuilder.server(serverBuilder -> {
            serverBuilder.setUrl(Configuration.amURL);
            serverBuilder.setRealm(Configuration.realm);
            serverBuilder.setCookieName(Configuration.cookieName);
            return null;
        });
        frOptionsBuilder.oauth(oAuthBuilder -> {
            oAuthBuilder.setOauthClientId(Configuration.oauthClientId);
            oAuthBuilder.setOauthRedirectUri(Configuration.oauthRedirectURI);
            oAuthBuilder.setOauthScope(Configuration.oauthScopes);
            return null;
        });
        frOptionsBuilder.service(serviceBuilder -> {
            serviceBuilder.setAuthServiceName(Configuration.mainAuthenticationJourney);
            serviceBuilder.setRegistrationServiceName("Registration");
            return null;
        });
        return null;
    });
    FRAuth.start(this.context, options);
    promise.success("SDK Initialized");
    // Clear the session - for debug reasons
    FRUser user = FRUser.getCurrentUser();
    if (user != null) {
        user.logout();
    }
}

The start() function above calls the Ping SDK for Android start() method on the FRAuth class.

Write the login() method

Once the start() method is called, and it has initialized, the SDK is ready to handle user requests.

Let’s start with login().

Just underneath the start() method we wrote above, add the login() method.

FRAuthSampleBridge.java
public void login(MethodChannel.Result promise) {
    try {
        authenticate(promise, true);
    } catch (Exception e) {
        promise.error("error", e.toString(), e);
    }
}

This login() function initializes the journey/tree specified for authentication. You call this method without arguments as it does not log the user in. This initial call to the server will return the first set of callbacks that represents the first node in your journey/tree to collect user data.

Write the next() method

To finalize the functionality needed to complete user authentication, we need a way to iteratively call next() until the authentication journey completes successfully or fails.

FRAuthSampleBridge.java
public void next(String response, MethodChannel.Result promise) throws InterruptedException {
    this.flutterPromise = promise;
    Gson gson= new Gson();
    Response responseObj = gson.fromJson(response,Response.class);
    if (responseObj != null) {
        List<Callback> callbacksList = currentNode.getCallbacks();
        for(int i = 0; i < callbacksList.size(); i++) {
            Object nodeCallback = callbacksList.get(i);

            for(int j = 0; j < responseObj.callbacks.size(); j++) {
                RawCallback callback = responseObj.callbacks.get(j);
                String currentCallbackType = callback.type;
                RawInput input = callback.input.get(0);
                if ((currentCallbackType.equals("NameCallback")) && i==j) {
                    currentNode.getCallback(NameCallback.class).setName((String) input.value);
                }
                if ((currentCallbackType.equals("ValidatedCreateUsernameCallback")) && i==j) {
                    currentNode.getCallback(ValidatedUsernameCallback.class).setUsername((String) input.value);
                }
                if ((currentCallbackType.equals("ValidatedCreatePasswordCallback")) && i==j) {
                    String password = (String) input.value;
                    currentNode.getCallback(ValidatedPasswordCallback.class).setPassword(password.toCharArray());
                }
                if ((currentCallbackType.equals("PasswordCallback")) && i==j) {
                    String password = (String) input.value;
                    currentNode.getCallback(PasswordCallback.class).setPassword(password.toCharArray());
                }
                if ((currentCallbackType.equals("ChoiceCallback")) && i==j) {
                    currentNode.getCallback(ChoiceCallback.class).setSelectedIndex((Integer) input.value);
                }
                if ((currentCallbackType.equals("KbaCreateCallback")) && i==j) {
                    for (RawInput rawInput : callback.input) {
                        if (rawInput.name.contains("question")) {
                            currentNode.getCallback(KbaCreateCallback.class).setSelectedQuestion((String) rawInput.value);
                        } else {
                            currentNode.getCallback(KbaCreateCallback.class).setSelectedAnswer((String) rawInput.value);
                        }
                    }
                }
                if ((currentCallbackType.equals("StringAttributeInputCallback")) && i==j) {
                    StringAttributeInputCallback stringAttributeInputCallback = (StringAttributeInputCallback) nodeCallback;
                    stringAttributeInputCallback.setValue((String) input.value);
                }
                if ((currentCallbackType.equals("BooleanAttributeInputCallback")) && i==j) {
                    BooleanAttributeInputCallback boolAttributeInputCallback = (BooleanAttributeInputCallback) nodeCallback;
                    boolAttributeInputCallback.setValue((Boolean) input.value);
                }
                if ((currentCallbackType.equals("TermsAndConditionsCallback")) && i==j) {
                    TermsAndConditionsCallback tcAttributeInputCallback = (TermsAndConditionsCallback) nodeCallback;
                    tcAttributeInputCallback.setAccept((Boolean) input.value);
                }
                if (currentCallbackType.equals("DeviceProfileCallback") && i==j) {
                    final Semaphore available = new Semaphore(1, true);
                    available.acquire();
                    currentNode.getCallback(DeviceProfileCallback.class).execute(context, new FRListener<Void>() {
                        @Override
                        public void onSuccess(Void result) {
                            Logger.warn("DeviceProfileCallback", "Device Profile Collection Succeeded");
                            available.release();
                        }

                        @Override
                        public void onException(Exception e) {
                            Logger.warn("DeviceProfileCallback", e, "Device Profile Collection Failed");
                            available.release();
                        }
                    });
                }
            }
        }
    } else {
        promise.error("error", "parsing response failed", null);
    }

    currentNode.next(this.context, listener);
}

The above code handles a limited number of callback types. Handling full authentication and registration journeys/trees requires additional callback handling.

To keep this tutorial simple, we’ll focus just on SingleValueCallback type.

Write the logout() bridge method

Finally, add the following lines of code to enable logout for the user:

FRAuthSampleBridge.java
public void logout(MethodChannel.Result promise) {
    FRUser user = FRUser.getCurrentUser();
    if (user != null) {
        user.logout();
        promise.success("User logged out");
    }
}