ForgeRock Developer Experience

Step 6. Create UI to handle the callbacks

In this step, you add a UI fragment to obtain credentials from the user, and code to open that fragment when a callback is received.

The authentication journey in this quick start guide sends the NameCallback and PasswordCallback callbacks.

For demonstration purposes, this application uses a DialogFragment to collect the username and password.

You also add code to populate the callback with the credentials and return it to the server, completing the authentication journey.

Create a UI fragment

  1. Navigate to app  res.

  2. Right-click layout and select New  Fragment  Fragment (Blank).

  3. In the New Android Component dialog, enter the following values, and then click Finish:

    • Fragment Name: usernamePasswordFragment

    • Fragment Layout Name: fragment_username_password

    • Source Language: Java

  4. Navigate to app  res  layout and open fragment_username_password.xml.

  5. Select and delete the existing "Hello blank fragment" TextView element.

  6. Right-click the "FrameLayout" component, select Convert FrameLayout to ConstraintLayout, and then click OK.

  7. From the Palette pane, from drag a Plain Text input element to the canvas:

    • id: inputUsername

    • text: Username

  8. From the Palette pane, drag a Password element to the canvas:

    • id: inputPassword

    • hint: Password

  9. From the Palette pane, drag a Button element to the canvas:

    • id: buttonCancel

    • text: Cancel

  10. From the Palette pane, drag a second Button element to the canvas:

    • id: buttonContinue

    • text: Continue

  11. Layout the elements on the canvas to your liking.

    The following screenshot shows one possibility:

    Possible layout of fragment elements
Show fragment_username_password.xml source
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="+http://schemas.android.com/apk/res/android+"
    xmlns:app="+http://schemas.android.com/apk/res-auto+"
    xmlns:tools="+http://schemas.android.com/tools+"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".usernamePasswordFragment" >

    <EditText
        android:id="@+id/inputUsername"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="Name"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/inputPassword"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:ems="10"
        android:hint="Password"
        android:inputType="textPassword"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/inputUsername" />

    <Button
        android:id="@+id/buttonCancel"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:text="Cancel"
        app:layout_constraintEnd_toStartOf="@+id/buttonContinue"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/inputPassword" />

    <Button
        android:id="@+id/buttonContinue"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="Continue"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/buttonCancel"
        app:layout_constraintTop_toBottomOf="@+id/inputPassword" />
</androidx.constraintlayout.widget.ConstraintLayout>

Configure the fragment code

  1. Open usernamePasswordFragment.java

    For example, app  java  com.example.quickstart  usernamePasswordFragment.

  2. Update the class to extend DialogFragment rather than Fragment, which makes opening and closing the fragment easier:

    public class usernamePassword extends DialogFragment {
  3. Add import statements for androidx.fragment.app.DialogFragment:

    import androidx.fragment.app.DialogFragment;
  4. Within the usernamePasswordFragment class, initialize required variables:

    private MainActivity listener;
    private Node node;
  5. Update the newInstance method to accept a node object as its only parameter:

    public static usernamePasswordFragment newInstance(Node node) {
        usernamePasswordFragment fragment = new usernamePasswordFragment();
        Bundle args = new Bundle();
        args.putSerializable("NODE", node);
        fragment.setArguments(args);
        return fragment;
    }
  6. Insert an onResume() method below the newInstance() method. This correctly sizes the fragment dialog when displayed:

    @Override
    public void onResume() {
        super.onResume();
        ViewGroup.LayoutParams params = getDialog().getWindow().getAttributes();
        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);
    }
  7. Delete the onCreate() function.

  8. Update the onCreateView method to capture the values from the fields in the fragment and populate the callbacks the node returned:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_username_password, container, false);
        node = (Node) getArguments().getSerializable("NODE");
        AppCompatEditText username = view.findViewById(R.id.inputUsername);
        AppCompatEditText password = view.findViewById(R.id.inputPassword);
        Button next = view.findViewById(R.id.buttonContinue);
        next.setOnClickListener(v -> {
            dismiss();
            node.getCallback(NameCallback.class)
                    .setName(username.getText().toString());
            node.getCallback(PasswordCallback.class)
                    .setPassword(password.getText().toString().toCharArray());
            node.next(getContext(), listener);
        });
        Button cancel = view.findViewById(R.id.buttonCancel);
        cancel.setOnClickListener(v -> {
            dismiss();
        });
        return view;
    }
  9. Add an onAttach() method after the onCreateView() method. This ensures the fragment is correctly connected to the main activity:

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if (context instanceof MainActivity) {
            listener = (MainActivity) context;
        }
    }
  10. Add any missing required import statements:

    import android.content.Context;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.Button;
    
    import androidx.annotation.NonNull;
    import androidx.appcompat.widget.AppCompatEditText;
    import androidx.fragment.app.DialogFragment;
    
    import org.forgerock.android.auth.Node;
    import org.forgerock.android.auth.callback.NameCallback;
    import org.forgerock.android.auth.callback.PasswordCallback;
Show completed usernamePasswordFragment.java source
package com.example.quickstart;

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.fragment.app.DialogFragment;

import org.forgerock.android.auth.Node;
import org.forgerock.android.auth.callback.NameCallback;
import org.forgerock.android.auth.callback.PasswordCallback;

/**
 * A simple {@link Fragment} subclass.
 * Use the {@link usernamePasswordFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class usernamePasswordFragment extends DialogFragment {

    private MainActivity listener;
    private Node node;

    public usernamePasswordFragment() {
        // Required empty public constructor
    }

    public static usernamePasswordFragment newInstance(Node node) {
        usernamePasswordFragment fragment = new usernamePasswordFragment();
        Bundle args = new Bundle();
        args.putSerializable("NODE", node);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onResume() {
        super.onResume();
        ViewGroup.LayoutParams params = getDialog().getWindow().getAttributes();
        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_username_password, container, false);
        node = (Node) getArguments().getSerializable("NODE");
        AppCompatEditText username = view.findViewById(R.id.inputUsername);
        AppCompatEditText password = view.findViewById(R.id.inputPassword);
        Button next = view.findViewById(R.id.buttonContinue);
        next.setOnClickListener(v -> {
            dismiss();
            node.getCallback(NameCallback.class)
                    .setName(username.getText().toString());
            node.getCallback(PasswordCallback.class)
                    .setPassword(password.getText().toString().toCharArray());
            node.next(getContext(), listener);
        });
        Button cancel = view.findViewById(R.id.buttonCancel);
        cancel.setOnClickListener(v -> {
            dismiss();
        });
        return view;
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if (context instanceof MainActivity) {
            listener = (MainActivity) context;
        }
    }
}

Open the fragment when receiving callbacks

  1. Open the project’s MainActivity class file.

    For example, app  java  com.example.quickstart  MainActivity.

  2. Update the onCallbackReceived() method to open the fragment to gather the credentials:

    @Override
    public void onCallbackReceived(Node node) {
        usernamePasswordFragment fragment = usernamePasswordFragment.newInstance(node);
        fragment.show(getSupportFragmentManager(), usernamePasswordFragment.class.getName());
    }

Check point

You have now completed the quick start application.

You added a UI fragment to obtain credentials from the user, and code to open that fragment when the callback is received.

You also added code to populate the callback with the credentials and return it to the server, completing the authentication journey.

In the next step, you test the application by authenticating a user, checking the logs, and then logging out.