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.
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 thePinFragment
. This parameter could be null. - Add an
action
with some animation to navigate from theConferenceFragment
to thePinFragment
.
<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 newConferenceFragment
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 thePinFragment
with the same arguments that theConferenceFragment
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.