Skip to main content

Use a PIN

All seems to work like a charm now, however there is room for another improvement. Did you try to connect to a conference that is protected by a PIN? Try it out!

You will see that the conference will fail without the opportunity to write your PIN.

Cannot Connect

In this lesson you will see how to detect that a PIN is required and display a new fragment to let the user introduce it.

As always you can download the initial code from Step.02-Exercise-Use-a-pin.

Modify the Navigation Graph

Now is the turn of defining how to access the PinFragment . The first step is to modify the fragment section for the ConferenceFragment :

  • Add an argument that will receive the PIN introduced in the PinFragment . This parameter could be null.
  • Add an action with some animation to navigate from the ConferenceFragment to the PinFragment .
<fragment
android:id="@+id/conferenceFragment"
android:name="com.example.pexipconference.screens.conference.ConferenceFragment"
android:label=""
tools:layout="@layout/fragment_conference">
...
<argument
android:name="pin"
app:argType="string"
app:nullable="true"/>
<action
android:id="@+id/action_conferenceFragment_to_pinFragment"
app:destination="@id/pinFragment"
app:enterAnim="@anim/slide_in_top"
app:exitAnim="@anim/slide_out_top"
app:popUpTo="@id/formFragment" />
</fragment>

Now you have to define the PinFragment in the navigation. In this case, you will need three arguments that the PinFragment will received and that, in a later step, they will be resend to the ConferenceFragment . Also you have to define a new action for coming back to the ConferenceFragment .

<fragment
android:id="@+id/pinFragment"
android:name="com.example.pexipconference.screens.pin.PinFragment"
android:label="Introduce PIN"
tools:layout="@layout/fragment_pin">
<argument
android:name="node"
app:argType="string" />
<argument
android:name="vmr"
app:argType="string" />
<argument
android:name="display_name"
app:argType="string" />
<action
android:id="@+id/action_pinFragment_to_conferenceFragment"
app:destination="@id/conferenceFragment"
app:exitAnim="@anim/slide_out_top"
app:popUpTo="@id/formFragment" />
</fragment>

Modify the PIN package

This package has only a fragment and its associated layout. It will display a panel for introducing the conference PIN when needed.

Modify the PIN layout

It's time to go to the layout. In this file we should remove the TextView and add two main elements:

  • EditText : For introducing the PIN.
  • Button : Element that will launch a new ConferenceFragment with the PIN.

You can see an extra LinearLayout that is used for vertical centering of the interface.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".screens.pin.PinFragment">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">

<LinearLayout
android:layout_width="200dp"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">

<EditText
android:id="@+id/pin_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:ems="10"
android:hint="@string/pin"
android:inputType="number" />

<Button
android:id="@+id/enter_pin_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="60dp"
android:text="@string/enter" />
</LinearLayout>

</LinearLayout>


</layout>

Modify the PIN fragment

Now we need to configure here the Data Binding and define the onClickListener . In this case, the only think that the button does, is to retrieve all the SafeArgs that this fragment receives, and launch again the ConferenceFragment . Although, this time it will include the PIN.

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {

// Inflate the layout for this fragment
val binding = FragmentPinBinding.inflate(
inflater,
container,
false
)

val args by navArgs<PinFragmentArgs>()
binding.enterPinButton.setOnClickListener {
val action = PinFragmentDirections.actionPinFragmentToConferenceFragment(
args.node, args.vmr, args.displayName, binding.pinText.text.toString()
)
findNavController().navigate(action)
}
return binding.root
}

Modify the form package

Although almost all the modifications are done in the pin and conference packages, we need to make a small modification in this package.

Modify the form fragment

Due to the changes that you have made recently, it's mandatory to include a PIN when you navigate to the ConferenceFragment . Even if you don't have one, you should include a value of null . You need to do this modification in the FormFragment .

val action = FormFragmentDirections.actionFormFragmentToConferenceFragment(
node, vmr, displayName, null
)

Modify the conference package

Due to the previous changes, now the conference will receive a new value with the PIN. We will have to use this value when the user creates a new conference. Also, the app needs to redirect the user to the PIN fragment, when the it detects that a PIN is required.

Modify the conference ViewModel

Define the new PIN parameter in startConference() and pass it to createConference().

fun startConference(node: String, vmr: String, displayName: String, pin: String?) {
...
conference = createConference(node, vmr, displayName, pin)

Now you will need to check if the pin is null or not. Depending on this value you will use requestToken() in one way or another.

private suspend fun createConference(
node: String,
vmr: String,
displayName: String,
pin: String?
): InfinityConference {
...
val response = if (pin != null) {
infinityService.newRequest(nodeUrl)
.conference(vmr)
.requestToken(request, pin)
.await()
} else {
infinityService.newRequest(nodeUrl)
.conference(vmr)
.requestToken(request)
.await()
}

Modify conference fragment

Pass the PIN value to startConference() :

viewModel.startConference(args.node, args.vmr, args.displayName, args.pin)

Until now, in the ConferenceFragment we detected an exception and displayed a custom snackbar for NoSuchConferenceException , however, for any other exception, we displayed a generic error message.

You need to add two more exceptions:

  • RequiredPinException : In this case, the app navigates to the PinFragment with the same arguments that the ConferenceFragment received. The final "" is to set the error variable to an empty string.

  • InvalidPinException : Instead of displaying the standard error message, you will display a more compressive message indicating the error.

Notice that you only display the snackbar if the error string is not empty. We don't want to display the an error message if a PIN is required.

viewModel.onError.observe(viewLifecycleOwner, Observer { exception ->
val error: String = when (exception) {
is RequiredPinException -> {
val action = ConferenceFragmentDirections.actionConferenceFragmentToPinFragment(
node, vmr, displayName
)
findNavController().navigate(action)
""
}
is InvalidPinException -> {
resources.getString(R.string.wrong_pin)
}
is NoSuchConferenceException -> {
resources.getString(R.string.conference_not_found, vmr)
}
else -> {
resources.getString(R.string.cannot_connect, node)
}
}
if (error.isNotEmpty()) {
val parentView = requireActivity().findViewById<View>(android.R.id.content)
Snackbar.make(parentView, error, Snackbar.LENGTH_LONG).show()
findNavController().popBackStack()
}
})

Run the app

And this is all. You can access your conference even if it's protected by a PIN. Take into account that if you want to enter as a guest, you can leave the PIN field black and click on join.

You can compare your code with the solution in Step.02-Exercise-Use-a-pin. You can also check the differences with the previous tutorial in the git diff.

Enter PIN