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
-
Navigate to
. -
Right-click layout and select .
-
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
-
-
Navigate to
and openfragment_username_password.xml
. -
Select and delete the existing "Hello blank fragment"
TextView
element. -
Right-click the "FrameLayout" component, select Convert FrameLayout to ConstraintLayout, and then click OK.
-
From the Palette pane, from drag a
Plain Text
input element to the canvas:-
id:
inputUsername
-
text:
Username
-
-
From the Palette pane, drag a
Password
element to the canvas:-
id:
inputPassword
-
hint:
Password
-
-
From the Palette pane, drag a
Button
element to the canvas:-
id:
buttonCancel
-
text:
Cancel
-
-
From the Palette pane, drag a second
Button
element to the canvas:-
id:
buttonContinue
-
text:
Continue
-
-
Layout the elements on the canvas to your liking.
The following screenshot shows one possibility:
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
-
Open
usernamePasswordFragment.java
For example,
. -
Update the class to extend
DialogFragment
rather thanFragment
, which makes opening and closing the fragment easier:public class usernamePassword extends DialogFragment {
-
Add import statements for
androidx.fragment.app.DialogFragment
:import androidx.fragment.app.DialogFragment;
-
Within the
usernamePasswordFragment
class, initialize required variables:private MainActivity listener; private Node node;
-
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; }
-
Insert an
onResume()
method below thenewInstance()
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); }
-
Delete the
onCreate()
function. -
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; }
-
Add an
onAttach()
method after theonCreateView()
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; } }
-
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
-
Open the project’s
MainActivity
class file.For example,
. -
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.