0% found this document useful (0 votes)
12 views263 pages

Email Authentication Form in Jetpack Compose

The document outlines the process of building an authentication form for applications, focusing on user account creation and access through email authentication. It discusses the necessary UI components, state management, and user interaction handling, including input validation and toggling between sign-in and sign-up modes. Additionally, it highlights the importance of managing loading and error states during the authentication process.

Uploaded by

pritom00019
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views263 pages

Email Authentication Form in Jetpack Compose

The document outlines the process of building an authentication form for applications, focusing on user account creation and access through email authentication. It discusses the necessary UI components, state management, and user interaction handling, including input validation and toggling between sign-in and sign-up modes. Additionally, it highlights the importance of managing loading and error states during the authentication process.

Uploaded by

pritom00019
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Building an A then-

ti tion Form
When buil ing apps, it’s o ten that we’ll be wor ing with some
kind of user a count - mea ing that we’ll need to provide a way
for users to cr ate a new a count or a cess an e is ing one. The
abi ity to be able to achieve this will be provided through an
a the ti tion screen, provi ing a way for users to sign-up or
sign-in u ing one of the met ods that your a pli tion o fers.
While many apps o fer a the ti tion via third-party sites (such
as s cial a counts), a co mon r quir ment is email a the ti a-
tion - not only does this d couple users a counts from third-
party sites, but it also o fers a co mon a the ti tion met od
for co nec ing with your a pli tion.

With this in mind, we’re g ing to build out an email a then-


ti tion screen u ing je pack co pose. While an email form
isn’t vis ally the most e qui ite e pe ence to be buil ing, it of-
fers a co le tion of ways for us to think about state ma age-
ment and is a pe fect e ample of how di fe ent an i pl men a-
tion may be when co pared to the e is ing A droid UI toolkit.

1
















































































When it comes to this screen, we need to co ure and handle
se e al di fe ent things.

- Add the UI for user i te a tion to pe form a the tic-


tion

- Pe sist the state for the entered email and pas word

- Ma age the UI state based on the entered cr den-


tials, such as di abling the bu ton when no cr den-
tials have been entered

- Handle the state change when the a the ti tion


mode toggle bu ton is pressed

2
















fi









- Co rectly su port IME a tions, a lo ing eld f cus to
be changed and the form to be su mi ted via the
sof ware ke board

We can see here that when it comes to the a the ti tion


screen, there is more to it than a mi i al form that is used for
cr de tial entry. The ma ag ment of state and r co po tion
of the UI based on those state changes gives us some good
foun tions to really start to e plore the concept of state wit in
Je pack Co pose.

3




















fi










D ing the A then-
ti tion State
Wit in our a the ti tion form, we’re g ing to be dea ing with
many di fe ent pieces of state that d pict how the UI is g ing to
be di played to the user. This state will be used to co pose our
UI, a lo ing us to co trol how our co po ables look and be-
have to the user. Wit in our UI we’re g ing to need to show the
fo lo ing things:

- A title that will tell the user that they need to sign-in
or sign-up

- An i put eld which will be used to enter an email


a dress

- An i put eld which will be used to enter a pas word

- A pas word va i tion i di a or to n t fy the user


whet er their pas word meets the mi i um r quire-
ments

- A bu ton that will a low the user to tri ger the au-
the ti tion ow. This bu ton will also be di abled
when there is no co tent i put into the email and
pas word elds

- A bu ton that will a low the user to toggle between


sign-in and sign-up

4




















fi

fi

fi
fi

fl



































- A pr gress i di a or which will di play when the
sign-in or sign-up o e tion is b ing pe formed

With the above set out, we can start to see that there are g ing
to be se e al pieces of state that we need to hold. With that in
mind, let’s start to take the above stat ments and build a class
that will re re ent the state of our co po able UI. Let’s cr ate a
new class, A the ti tio State.

// [Link]
data class AuthenticationState(

Mo e ling the user cr den-


tials
We’ll start with the co tent that is g ing to be i put by the user
- the email a dress and pas word. B cause this is som thing
that the user will enter and will be di played wit in our UI, each
of these is g ing to re re ent a piece of our state. For this, we’ll
go ahead and add two string re e ences to our state class.

data class AuthenticationState(


val email: String? = null,
val password: String? = null
)

5









































Mo e ling the pas word re-
quir ments
Alon side the user b ing able to enter their pas word, we’re
also g ing to e force some r quir ments for that pas word.
We’ll re re ent these r quir ments in the form of an enum -
this a lows us to e force the length and co tent of the pass-
word that is entered.

enum class PasswordRequirements {


CAPITAL_LETTER, NUMBER,
EIGHT_CHARACTERS
}

For each of the r quir ments we’re g ing to want to show a


me sage, this will be used to co m ni ate what the r quire-
ment is to the user. For this, we’ll need to start by adding some
new strings to our strin [Link] le.

<string
name="password_requirement_characters">
At least 8 characters
</string>

<string
name="password_requirement_capital">
At least 1 upper-case letter
</string>

<string
name="password_requirement_digit">

6


















fi










At least 1 digit
</string>

We’ll then a just the Pas wor R quir ment to co tain a la-
bel in the form of a string r source i teger, se ting a r source
for each of the r quir ment enums va ues that we pr v ously
de ned.

enum class PasswordRequirement(


@StringRes val label: Int
) {

CAPITAL_LETTER([Link].requirement_capi
tal),
NUMBER([Link].requirement_digit),

EIGHT_CHARACTERS([Link].requirement_ch
aracters)
}

Wit in the state, we’ll need the cu rent Pas wor R quire-
ments that are sa i ed so that we can co m ni ate this to the
user. For this rea on, we’ll add a new pas wor R quir ments
eld to our state that re re ents a list of r quir ments that have
been met.

data class AuthenticationState(


val email: String? = null,
val password: String? = null,
val passwordRequirements:
List<PasswordRequirements> = emptyList()

7
fi
fi






fi




























)

Mo e ling the a the ti tion


mode
The above gives us the key parts of the state that we need to al-
low the user to sign-up or sign-in to our a pli tion. Ho ever,
we’re g ing to be d ing a little more than that! B cause a user
can sign-up and sign-in to our a pli tion, we need to a low the
form to be toggle-able between these two modes. To be able
to re re ent this in our state we’re g ing to start by d ing a
new enum that will be used to si n fy whet er the form is cur-
rently b ing used to sign up or sign in.

enum class AuthenticationMode {


SIGN_UP, SIGN_IN
}

Now we can go ahead and add this to our state - this will then
a low us to ea ily toggle between these two modes and have
our UI r co pose whene er the s le ted mode changes.

data class AuthenticationState(


val authenticationMode:
AuthenticationMode =
AuthenticationMode.SIGN_IN,
val email: String? = null,
val password: String? = null,
val passwordRequirements:
List<PasswordRequirements> = emptyList()

8































fi


)

Mo e ling the loa ing state


Now that our state re re ents both a sign-in and sign-up ow,
we’re g ing to want to be able to handle the state when a user
reaches the point of pe for ing those o e tions. While we
won’t be hi ting an API in our UI e ample, in the real world this
would be an asy chro ous o e tion - mea ing that we would
have to wait a second or two for it to co plete. In this sce ario,
we would want to show a pr gress i di a or to the user so that
they know a r quest is ta ing place. We’ll re re ent this in our
state by adding a loa ing ag to the state class.

data class AuthenticationState(


val authenticationMode:
AuthenticationMode =
AuthenticationMode.SIGN_IN,
val email: String? = null,
val password: String? = null,
val passwordRequirements:
List<PasswordRequirements> =
emptyList(),
val isLoading: Boolean = false
)

9












fl


















fl
Mo e ling the e ror state
Once the loa ing of the r quest has been co pleted, we’ll
want to handle the r sponse that would come back from our
API. In the case of su cess, we would na i ate onto the next
part of our a pli tion, but things might not a ways go as
planned. In these cases, we’ll want to show an e ror to the user,
us ally in the form of the e ror that has come back from the API.
To a low for this, we’ll add a new eld to our state class which
will re re ent if an e ror has o curred and at the same time, the
co tent of that e ror.

data class AuthenticationState(


val authenticationMode:
AuthenticationMode =
AuthenticationMode.SIGN_IN,
val email: String? = null,
val password: String? = null,
val passwordRequirements:
List<PasswordRequirements> =
emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)

Mo e ling the va id state


As well as the co tent that can be entered into the i put elds,
the user can also d lete this co tent. These elds the selves
will also start blank, so in both of these states, we don’t want
the user to be able to su mit the form. We also want to block

10

























fi



fi





fi
the form from b ing su mi ted if the pas word r quir ments
have not been sa i ed. To handle these cases we’ll add a new
fun tion to our state - this will r turn a boolean value that rep-
re ents whet er the form co tent is va id. We’re u ing a func-
tion for this so that the place that has the a cess to this state
can ea ily check if the cu rent state a lows the user to pr ceed,
i stead of adding a ot er value wit in our state.

data class AuthenticationState(


val authenticationMode:
AuthenticationMode =
AuthenticationMode.SIGN_IN,
val email: String? = null,
val password: String? = null,
val passwordRequirements:
List<PasswordRequirements> =
emptyList(),
val isLoading: Boolean = false,
val error: String? = null
) {
fun isFormValid(): Boolean {
return password?.isNotEmpty() ==
true &&
email?.isNotEmpty() == true
&&
(authenticationMode ==
AuthenticationMode.SIGN_IN ||
[Link](

[Link]().toList()))
}

11









fi















}

With that in place, we now have a class that can be used to rep-
re ent the state of our UI. Over the next few se tions of this
chapter, we’ll uti ise this state when buil ing out our UI, mod fy-
ing its va ues as i te a tions with co po ables take place, trig-
ge ing r co po tions to r ect any state changes.

12












fl





Cr a ing the A then-
ti tion Vie Mo el
Now that we have the state mo elled for our A the ti tion
screen, we can start thin ing about the Vie Mo el that will be
used to ma age that state and provide a way to o che trate it
to the user i te face.

Se ting up the Vie Mo el


B fore we can get sta ted here, we’re g ing to add a new de-
pen ency to our pr ject that will give us a cess to the A droid
L f cycle Vie Mo el class:

implementation
"[Link]:lifecycle-viewmodel-
compose:2.4.0"

💡 You aren’t r quired to use a Vie Mo el when wor ing with


co pose. For the sake of these e e cises, it helps us to keep
things simple and fo low an a proach that many d velopers
are f mi ar with.

Next, we’ll cr ate a new Vie Mo el, called A the ti a-


tio Vie Mo el.

// [Link]

13
























































class AuthenticationViewModel :
ViewModel() {

This Vie Mo el is g ing to need to hold a re e ence to the


state of our screen. For this we’re g ing to uti ise Stat Flow -
this a lows us to cr ate a state-hol er o ser able ow that will
emit the d fault state we provide to it, along with any u dates
that o cur du ing its lif time. Here we’ll cr ate a new Mu able-
Stat Flow i stance, provi ing a re e ence to our A then-
ti tio State class as the d fault value for our Stat Flow:

class AuthenticationViewModel :
ViewModel() {
val uiState =
MutableStateFlow(AuthenticationState())
}

With this in place, we now have a Stat Flow re e ence that is


hol ing a re e ence to our a the ti tion state, in tia ising it
with a new i stance of the state class and r l ing on the de-
faults that the co struc or provides.

M ni la ing state u ing


events
While this state is now in place, we need to start thin ing about
the di fe ent ways in which it can be m ni lated - whene er
som thing is changed in our UI (text e ited, bu ton pressed),

14




















































fl











we’ll want to u date the state wit in our Vie Mo el so that the
o serving UI can r ect those changes.

For when this is the case, we’re g ing to mo el some


events that can be triggered in our co po able UI and in turn
these events will be used to m ni late the state wit in the
view mo el. This a lows us to have a single way of our co pos-
able UI co m ni a ing with the Vie Mo el, rather than need-
ing to pass the e tire Vie Mo el or many re e ences to se ar-
ate fun tions which could be used to tri ger state changes. In-
stead, we can pass a single fun tion re e ence to our co pos-
able UI which can then be used to tri ger these events in the
Vie Mo el. For these events we’re g ing to need to de ne dif-
fe ent types that can be triggered, so we’ll go ahead and cr ate
a new sealed class to re re ent these.

// [Link]
sealed class AuthenticationEvent {

Han ling a the ti tion


mode to gling
With this sealed class in place, we can start to think about u ing
it to re re ent the di fe ent events that can o cur. We’re g ing
to start by han ling the sce ario where the a the ti tion
mode can be toggled between sign-up and sign-in. For this,
we’ll cr ate a new event called Toggl A thenti tio Mode.

sealed class AuthenticationEvent {

15



















fl







































fi










object ToggleAuthenticationMode:
AuthenticationEvent()

With this event in place, this can now be triggered from our
co po able UI to cause a state change wit in our view mo el.
For that to ha pen though, we need to write this l gic i side of
our Vie Mo el. We’ll start here by cr a ing a new fun tion that
will be used to ‘ ip’ the cu rent a the ti tion mode - switc ing
it between sign-up and sign-in.

private fun toggleAuthenticationMode() {


val authenticationMode =
[Link]
val newAuthenticationMode = if (
authenticationMode ==
AuthenticationMode.SIGN_IN
) {
AuthenticationMode.SIGN_UP
} else {
AuthenticationMode.SIGN_IN
}
[Link] = [Link](
authenticationMode =
newAuthenticationMode
)
}

16





fl













With this code above, we take the cu rent a the ti tion mode
wit in our a the ti tion state and set it to the o po ite value.
We then use this to copy our e is ing a the ti tion state, set-
ting the a the ti tion mode to r ect the newly ca c lated
value. When this is done, the new value will be emi ted to the
o ser er of our live data - a lo ing our UI to be r co posed to
r ect this new state.

💡 The copy fun tion in Ko lin co ies the e is ing class ref-
e ence, r pl cing any va ues that have been provided as a gu-
ments to the fun tion.

Now that we have this fun tion avai able to handle the state
change of the a the ti tion mode, we need to go ahead and
add su port for tri ge ing it from ou side of our Vie Mo el.
For this, we’ll add a new fun tion to our Vie Mo el that will
take a re e ence to an A the ti tio Event.

fun handleEvent(authenticationEvent:
AuthenticationEvent) {
when (authenticationEvent) {

}
}

This fun tion takes an A the ti tio Event re e ence and


then can use that to tri ger the d sired ou come, based on the
event which has been provided to it. So in this case of the au-
the ti tion mode toggle event, we’re g ing to tri ger the
toggl A thenti tio Mode fun tion.

fun handleEvent(authenticationEvent:
AuthenticationEvent) {

17



fl






















































fl































when (authenticationEvent) {
is
[Link]
Mode -> {
toggleAuthenticationMode()
}
}
}

While we’re not g ing to i pl ment the call to handl Event


u til the next se tion of this chapter, our co po able UI will be
able to make the fo lo ing call to tri ger the a the ti tion
mode toggle:

[Link](

[Link]
Mode
)

Han ling i put eld changes


With the a the ti tion mode toggle in place, we can start
thin ing about the ot er events that we want to tri ger from
ou side of our Vie Mo el. If we take a look at the a the ti a-
tion state mo el then we can see the ot er pieces of the state
that we need to m ni late. Two key pieces of this state are the
email a dress and pas word, these will re re ent the data that
has been i put by the user and will need to be r e ted in our

18

























fi







fl










state whene er they change. For this, we’re g ing to add two
more events to our A the ti tion Event sealed class.

sealed class AuthenticationEvent {

object ToggleAuthenticationMode:
AuthenticationEvent()

class EmailChanged(val emailAddress:


String):
AuthenticationEvent()

class PasswordChanged(val password:


String):
AuthenticationEvent()

Here, we’ve a ded an Emai Changed event, along with a Pass-


wor Changed event. These will a low the UI to tri ger these
events whene er the i put of the email and pas word elds are
changed. For that to be po sible, we’ll go ahead and i ple-
ment some fun tions in our Vie Mo el that a low for this state
change to be r e ted. We’ll start with a fun tion, u dat E-
mail, that will simply be used to u date our state re e ence
with the provided email a dress.

private fun updateEmail(email: String) {


[Link] = [Link](
email = email
)

19






fl



















fi




}

When it comes to u da ing the pas word, we’ll need to cr ate


a ot er new fun tion, u dat Pas word, that will be used to
u date the pas word re e ence wit in our state.

private fun updatePassword(password:


String) {
[Link] = [Link](
password = password
)
}

Ho ever, whene er the pas word is u dated we’ll a ways want


to u date the vali ity of the pas word r quir ments. For this,
we’re g ing to cr ate a new list that co sists of Pas wor Re-
quir ments, for which we’ll add re e ences for the r quire-
ments that have been met. This list can then be set on our state
so that the UI la er can be aware of the r quir ments that have
been sa i ed. When buil ing this list of sa i ed r quir ments,
we’ll need to base this on se e al di fe ent co straints.

- When the length of the pas word is grea er than 7,


this means that the mi i um pas word length has
been met. This means that we can add the Pass-
wor R quir men s.EIGHT_CHA A TERS to the
list.

if ([Link] > 7) {

20











fi








































fi









[Link]([Link]
GHT_CHARACTERS)
}

- When the pas word co tains an u pe case cha ac-


ter, this means a ot er one of our mi i um r quire-
ments has been met. In this case, we’ll add the
Pas wor R quir men s CA I AL LE TER to the
list.

if ([Link] { [Link]() }) {

[Link]([Link]
PITAL_LETTER)
}

- F nally, if the pas word co tains any d git, this means


that this r quir ment has been sa i ed. In this case,
we’ll add the Pas wor R quir men [Link] BER
value to the list.

if ([Link] { [Link]() }) {

[Link]([Link]
MBER)
}

21























fi











With this l gic now de ned, we can slot this into our u date-
Pas word fun tion and a sign the re ult to the pas wor Re-
quir ments wit in our A the ti tio State re e ence.

private fun updatePassword(password:


String) {

val requirements =
mutableListOf<PasswordRequirements>()
if ([Link] > 7) {

[Link]([Link]
GHT_CHARACTERS)
}
if ([Link]
{ [Link]() }) {

[Link]([Link]
PITAL_LETTER)
}
if ([Link] { [Link]() }) {

[Link]([Link]
MBER)
}
[Link] = [Link](
password = password,
passwordRequirements =
[Link]()
)

22





fi












}

With these email and pas word fun tions in place, these can
now be triggered via an event whene er the i put is changed
for either of those elds. We can then go ahead and add these
fun tion calls to our handl Event fun tion - now when the
event is triggered ou side of the Vie Mo el, the state can be
u dated based on the co re pon ing event.

fun handleEvent(authenticationEvent:
AuthenticationEvent) {
when (authenticationEvent) {
is
[Link]
Mode -> {
toggleAuthenticationMode()
}
is
[Link] -> {

updateEmail([Link]
ress)
}
is
[Link] -> {

updatePassword([Link]
ord)
}
}

23


fi












}

Han ling a the ti tion trig-


gers
Now that users can toggle between a the ti tion modes and
enter their a count cr de tials, na u ally, the next step would
be to a low the UI to tri ger a the ti tion. For this event we
don’t need to send any data from the UI - this is b cause the
entered email a dress and pas word are already r e ted
wit in our A the ti tion State. So here we can simply add an-
ot er event type to our A the ti tion Event sealed class.

sealed class AuthenticationEvent {

object ToggleAuthenticationMode:
AuthenticationEvent()

class EmailChanged(val emailAddress:


String): AuthenticationEvent()

class PasswordChanged(val password:


String): AuthenticationEvent()

object Authenticate:
AuthenticationEvent()

24


































fl

With this in place, we can next add a fun tion that this event will
be used to tri ger. For this e ample, we won’t be tri ge ing a
ne work r quest, but i stead will be r spon ing to the a then-
ti tion r quest from the UI and r ec ing this via the loa ing
pro erty wit in the a the ti tion state.

private fun authenticate() {


[Link] = [Link](
isLoading = true
)
// trigger network request
}

With this fun tion in place, we now have som thing that will
si late the ne work r quest ta ing place wit in our a pli a-
tion. We can now also add this event type to our handl Event
fun tion, a lo ing the A the ti ate event to be triggered and
handled from ou side of our Vie Mo el.

fun handleEvent(authenticationEvent:
AuthenticationEvent) {
when (authenticationEvent) {
is
[Link]
Mode -> {
toggleAuthenticationMode()
}
is
[Link] -> {

25





























fl














updateEmail([Link]
ress)
}
is
[Link] -> {

updatePassword([Link]
ord)
}
is
[Link] -> {
authenticate()
}
}
}

Han ling a the ti tion er-


rors
With the above in place, we have a Vie Mo el that can handle
the di fe ent r quired state pro e ties and events that a low the
user to enter their cr de tials, toggle between a the ti tion
modes and then pr ceed with tri ge ing the a the ti tion
pr cess.

Ho ever, we still have a nal pro erty from our A the ti a-


tion State to handle - and that is the e ror. This pro erty de-
faults to null wit in our state, so in that case, our UI won’t need
to di play any kind of e ror. But when this is not null, the UI will
re re ent that e ror in some way and then also provide a way

26















fi­

























for it to be di missed from view. So that we can si late this
sce ario and then ma age the e pe ted state we will mod fy
our a the ti ate fun tion.

After we have emi ted the loa ing state we’ll add a delay to
si late a ne work r quest, fo lowed by r mo ing the loa ing
status and emi ting an e ror me sage in the a the ti tion
state.

private fun authenticate() {


[Link] = [Link](
isLoading = true
)

[Link]([Link]) {
delay(2000L)

withContext([Link]) {
[Link] =
[Link](
isLoading = false,
error = “Something went
wrong!”
)
}
}
}

You’ll n tice here that we’re ho ping between coroutine dis-


patc ers - while we’re not ma ing a real ne work r quest there,
this is to si late a real sce ario and e sure that the asyn-
chro ous work and live data emi sions ha pen u ing the ex-

27











































pe ted di patc ers. With this in place, the e ror will be emi ted
as part of the a the ti tion state for the UI to r ect. While we
haven’t cr ated the UI yet, this e ror will be co posed in the
form of an alert di log - this means that we will also need to be
able to di miss this di log. To su port this, the e ror of our state
will need to be cleared so that the UI is r co posed the alert
di log is not a part of the co po tion. So that this can be
cleared from our state from our UI, we’re g ing to add a new
A the ti tio Event called E ro ismissed.

sealed class AuthenticationEvent {

object ToggleAuthenticationMode:
AuthenticationEvent()

class EmailChanged(val emailAddress:


String): AuthenticationEvent()

class PasswordChanged(val password:


String): AuthenticationEvent()

object Authenticate:
AuthenticationEvent()

object ErrorDismissed:
AuthenticationEvent()
}

With this in place, we are now able to r ceive e ror di missal


events from our UI la er, mea ing that we also need to i ple-

28



































fl




ment the state change for when this o curs. Here we’ll cr ate a
new fun tion that will be used to simply clear the e ror

private fun dismissError() {


[Link] = [Link](
error = null
)
}

The last thing to do here is tri ger this fun tion whene er the
E ro ismissed event is triggered. For this, we’ll add a nal
check to our handl Event when clause to tri ger our fun tion.

fun handleEvent(authenticationEvent:
AuthenticationEvent) {
when (authenticationEvent) {
is
[Link]
Mode -> {
toggleAuthenticationMode()
}
is
[Link] -> {

updateEmail([Link]
ress)
}
is
[Link] -> {

29













fi­
updatePassword([Link]
ord)
}
is
[Link] -> {
authenticate()
}
is
[Link] -> {
dismissError()
}
}
}

With this i pl me ted, we are now ma aging the state of our


a the ti tion screen and provi ing the r quired entry points
for our UI la er to m ni late the state based on user i te ac-
tion. Our view mo el is now ready to be plugged into a com-
po able UI, which we’ll cr ate in the next se tion of this
chapter!

30




















Cr a ing the A then-
ti tion UI
With the Vie Mo el and state ma ag ment all in place, we’re
ready to move on and start i pl men ing the co po able UI
for our a the ti tion screen. When we’re ished buil ing this
UI, we’re g ing to end up with som thing that looks like the fol-
lo ing:

This UI will give our users a screen that can be used to log in to
an a pli tion - gi ing the o tion of pe for ing either a sign-in

31
























fi






or sign-up o e tion. While buil ing this UI we’ll dive into the
sp ci cs of how the co po ables can be co gured, along
with adding some nice touches to i prove the User E pe ence
of our A the ti tion screen.

Se ting up the entry point


B fore we can get start buil ing our pr ject, we’re g ing to
need to add a couple of d pen e cies that we’re g ing to
need. We’ll start here by adding these to the build gradle
le for our new pr ject:

// provides access to the


`ComponentActivity` class that can be
used to compose UI components
implementation
'[Link]:activity-
compose:1.8.0'

// UI components from the Compose APIs


implementation
"[Link]:ui:1.5.4"

// Material Design components from the


Compose APIs
implementation
"[Link]:material:
1.5.4"

32
fi



f­i


















fi






// Provides an extended collection of
Material Iconography
implementation
"[Link]:material-
icons-extended:1.5.4"

// Tooling functionality for


Composables, such as previews
implementation "[Link]:ui-
tooling-preview:1.5.4"
debugImplementation
"[Link]:ui-tooling:1.5.4"

The cu rent r lease of this book is buil ing against 1. 5.4 of


co pose - be sure to check co pa ibly if u ing a ne er ver-
sion.

With these a ded to our pr ject, we’re now ready to start


buil ing out our UI. We’re g ing to start here by buil ing the
a cess point to our fe ture - this is how the me saging fe ture
will in tially be co posed wit in our user i te face.

Here we’ll b gin by buil ing a root co po able fun tion,


A the ti tion that will be used to house all of our Com-
po able UI for the A the ti tion screen. For this, we’ll cr ate a
new Ko lin le called A the ti [Link] (to keep our com-
po ables nicely o ga ised), fo lowed by cr a ing a new com-
po able fun tion, A the ti tion:

// [Link]
@Composable

33












fi










































fun Authentication() { }

This co po able is g ing to be the entry point to our A then-


ti tion screen - so we don’t want this fun tion to have to take
any a g ments. The point that is na i a ing to a the ti tion
will be able to just co pose this fun tion, and this co po able
will handle everything else. While you won’t see an thing vis al
just yet, you’ll want to co pose this A the ti tion com-
po able wit in the se Co tent block of the acti ity that was
cr ated through the pr ject wi ard. Then as we build out the
pr ject, we’ll be able to vis a ise the A the ti tion when
ru ning the pr ject.

Wit in this root level co po able we’re g ing to want to


force our a pli tion theme on the co po ables that are con-
tained i side of it, so we’ll add a M te a Theme co po able
d cla tion here.

@Composable
fun Authentication() {
MaterialTheme {

}
}

💡 Wra ping your co po able hie archy in a theme a lows all


co po ents to be co sis ently styled. In most cases, this can
ha pen at the highest point of co po tion - even wit in the
se Co tent fun tion of the pa ent acti ity that is co po ing the
UI.

This now means that for any of the co po ables that are
co posed in the co tent block of our M te a Theme, these

34




























































































will be themed a cor ing to the co ors and styles that are de-
clared wit in our theme.

Sa ing that though, we don’t cu rently have any co pos-


ables that are g ing to make up the co tent of our a the ti a-
tion form. We’ll go ahead and cr ate a new co po able func-
tion here, A the ti tio Co tent. The di fe ence here is
that this co po able is g ing to be r spon ible for co po ing
our UI based on the state of the screen, mea ing that this func-
tion is g ing to take some form of a g ment. B cause it’s com-
po ing UI based on our state, it’s also g ing to need to propag-
ate any events so that the state can be u dated a cor ingly.
For this rea on, it’s also g ing to need to be able to handle the
A the ti tio Event types that we pr v ously de ned.
We’ll need to de ne two a d tio al a g ments for our co pos-
able to sa i fy these r quir ments.

@Composable
fun AuthenticationContent(
modifier: Modifier = Modifier,
authenticationState:
AuthenticationState,
handleEvent: (event:
AuthenticationEvent) -> Unit
) {

💡 It’s good pra tice to a low a mo er to be passed into a


co po able, this means that the pa ent who is co po ing the
child can co trol the co po tion to some e tent. This also

35


















fi






























fi­






















fi



helps to keep your co po able fun tions re-u able across your
UI.

This co po able will take an A the ti tio State that


re re ents the state of our screen, along with an event han ler
that a lows us to pass up A the ti tio Event i stances
when they are triggered.

So, why can’t this just be the entry point to our a the ti a-
tion screen? One clear thing here is that this d couples the
co po able with b ing co cerned about how the state is
provided - passing in the state via an a g ment makes it sim-
pler, as in, it gets passed a state and co poses UI based on it.
This also makes it much eas er to write tests our co po able -
b cause we can simply pass it a state o ject and pe form a ser-
tions based on that, rather than nee ing to si late user ac-
tions and pe form moc ing to pr duce e pe ted states.

With this co po able de ned, we can then hop up to the


root A the ti tion that we de ned and co pose the Au-
the ti tio Co tent wit in our theme block.

@Composable
fun Authentication() {

MaterialTheme {
AuthenticationContent(
modifier =
[Link](),
authenticationState = ...,
handleEvent = ...
)
}

36























fi








fi





























}

Things aren’t quite sa i ed here yet though, we need to


provide both the state and event han ler to our A the ti a-
tio Co tent. We’ll be u ing our A the ti tio Vie Mod-
el that we cr ated in the pr v ous se tions to sa i fy these ar-
g ments, so we’ll rst need to r trieve an i stance of this. So
that we can a cess this i for tion, we’re g ing to need to ob-
tain an i stance to this Vie Mo el i side of our co po able.
Here we’ll use the vie Mo el fun tion from the l f cycle-
vie mo el-co pose pac age. This will r trieve an i stance of
the d sired Vie Mo el, cr a ing one if it does not cu rently ex-
ist.

val viewModel: AuthenticationViewModel =


viewModel()

We can then use this Vie Mo el wit in our root co po able


for our r quired a g ments.

- We’ll use co le A State() to co lect the emis-


sions of our Stat Flow re e ence from our View-
Mo el as co po able state. We’ll then pass the
value of this emi sion as the state re e ence to our
co po able.

- For event han ling, our Vie Mo el co tains a func-


tion that matches the r quir ments of our lambda
fun tion a g ment. We can pass this fun tion re er-
ence di ectly to our co po able u ing vie Mod-
el::handl Event.

37


























fi














fi





















































@Composable
fun Authentication() {
val viewModel:
AuthenticationViewModel = viewModel()

MaterialTheme {
AuthenticationContent(
modifier =
[Link](),
authenticationState =

[Link]().value
,
handleEvent =
viewModel::handleEvent
)
}
}

With this in place, we have a co po able fun tion that will


house the co po ables ma ing up our a the ti tion UI. As
you might r me ber from when we cr ated the A the ti a-
tio State, there’s a lot to take into a count when it comes to the
UI. While there is plenty of space for there to be more co plex-
ity and co d tions to think about in r gards to the state, we
have plenty to think about here when it comes to buil ing the
a t al UI. When it comes to this, we’re g ing to need to break
that down into a Co po able re re en tion.

38
































• A Pa ent Box to hold the di fe ent co po ables that make up
our a the ti tion screen

• A Pr gress I di a or to si n fy the loa ing state of the


screen

• An a the ti tion form co po able that will hold the in-


put elds, bu tons and ot er form co po ents

• An Alert Di log used to di play a the ti tion e ror mes-


sages

With the above in mind, we can start buil ing out our Com-
po able UI to re re ent our a the ti tion screen. We’re g ing

39

fi









































to build a co le tion of co po able fun tions, all of which can
be plugged t get er to cr ate the co plete screen.

D ing the Pa ent Co tain-


er

We b gin at the start of the above i lu tr tion, the pa ent Box


co po able. B cause our UI can show three di fe ent child
co po ables, we need a co tai er to house those. Ot er than
the a the ti tion form, we’re g ing to be di pla ing either a
pr gress i di a or (which needs to be di played in the ce ter
of the pa ent) or an alert di log (which will be shown over the

40






fi





































top of the a the ti tion form), we need these to be placed in
a co tai er to a low su port for these di fe ent sce ar os. The
Box co po able provides su port for the alig ment of child
co po ables, as well as the abi ity to show ove la ping com-
po ables - which makes it pe fect for what we need.

// [Link]
@Composable
fun AuthenticationContent(
modifier: Modifier = Modifier
) {
Box {

}
}

Here we’ve de ned an empty Box, which is enough to a low us


to di play child co po ables i side of it. Ho ever, we need to
provide some pro e ties to have the Box ll the e tire avai able
space on-screen, while also provi ing alig ment for any chil-
dren that it di plays.

Box(
modifier = modifier,
contentAlignment = [Link]
) {

Here we use the fil Ma Size mo er to have the Box ll all


of the avai able space on the screen (both the width and height

41











fi


















fi­
fi











fi

wit in its pa ent), along with u ing the co ten lig ment ar-
g ment to align all of its chi dren in the ce ter of the Box.

Di play a Pr gress State

B fore we go ahead and start sho ing co tent to users, we’re


g ing to think about the state that o curs b for hand - which is
when a loa ing i di a or will be di played to users. We’re go-
ing to start here by u ing the i Loa ing pro erty from our Au-
the ti tio State re e ence. U ing this we’re either g ing to
want to show a pr gress i di a or or go on to di play co tent
to the user. To handle these di fe ent sce ar os, we’ll start by

42














































adding an if stat ment that checks the status of this loa ing
ag.

Box(
modifier = modifier,
contentAlignment = [Link]
) {
if ([Link]) {

} else {

}
}

With this in place, we can now d cide whet er to co pose the


loa ing state or the co tent state. Here we’re just g ing to
tackle the loa ing state of our UI, so we’ll go ahead and uti ise
one of the avai able pr gress i di a or co po ables - the Cir-
c la Pr gres I di a or.

if ([Link]) {
CircularProgressIndicator()
} else {

If we do not provide a pr gress value to the Ci c la Pr gres In-


di a or then the i di a or is co posed as an i d ter i ate in-
di a or, mea ing that it will spin i de itely while it is di played
on the screen. This is ne for our r quir ments, as we’re just go-
ing to show it on screen u til the co tent is loaded. If you need

43
fl
















fi















f­i



















to di play a sp ci c pr gress value on the i di a or, you can
provide this pr gress value to the co po able so that the in-
di a or can be co posed to re re ent that cu rent pr gress.

Di pla ing the L gin Co tent

Even though now we have a pr gress i di a or in place for our


l gin screen, this isn’t som thing we’re g ing to be di pla ing
u til the user has triggered the a the ti tion ow. So that we
can have this be triggered, we’re now g ing to go ahead and
build out the a the ti tion form UI. We’ll need to start here by

44











f­i






















fl






cr a ing a new co po able fun tion that will be used to house
our a the ti tion form.

// [Link]
@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier
)

I side of this, we’re g ing to start here by d ing a Column


co po able - our a the ti tion form is g ing to re re ent a
ve ti al stack of co po ents, so a Column is most a pr pr ate
for this. When co po ing this Column, we’ll pass the Mo fi-
er re e ence that was provided to our A the ti tio Form
co po able to a ply the provided co straints to this pa ent
co po able.

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier
) {
Column(modifier = modifier) {

}
}

Loo ing at the design of the screen, we’re also g ing to want
all of the child co po ables to be p s tioned in the ce ter ho-
r zon ally. For this we’ll uti ise the h r zon a lig ment ar-
g ment, provi ing Cente H r zon ally as the value to be
used for alig ment.

45






















































fi
















@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier,
horizontalAlignment =
[Link]
) {

}
}

With this in place, we now have a Column that will be used to


house the co tents of our l gin form.

46


Adding the A the ti tion
Title

We’ll start rst by adding the title for our a the ti tion form -
this will di play a hea er that will state that the user can either
sign in or sign up, d pen ing on the cu rent state of the
screen. So that we can di play this title, we’ll need to b gin by
adding two new strings to our strin [Link] le:

<string name="label_sign_in_to_account">
Sign In to your account
</string>

47

fi









fi






<string
name="label_sign_up_for_account">
Sign Up for an account
</string>

We’re not g ing to use these just yet, but at least they’re now in
place for when it comes to slo ting them into our UI. So that we
can start buil ing out our a the ti tion title, we’ll need to cre-
ate a new co po able fun tion, A the ti tio Title.

// [Link]
@Composable
fun AuthenticationTitle(
modifier: Modifier = Modifier
)

And so that this knows what title needs to di play, it’s g ing to
need to know the cu rent A the ti tio Mode of the screen
- which we’ll pass in as an a g ment to the co po able func-
tion.

@Composable
fun AuthenticationTitle(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
)

Next, we’ll build out a mi i al Text co po able u ing a string


r trieved from one of the pr v ously a ded string r sources.
The A the ti tio Mode re e ence we have d picts wheth-

48













































er the user is cu rently sig ing-in or sig ing-up. Based on this
mode we want to set the title of the screen, so it is clear to the
user whet er they are sig ing-in or sig ing-up. U ing the
provided A the ti tio Mode we’ll set the r source to be
used for our Text co po able.

@Composable
fun AuthenticationTitle(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
) {
Text(
text = stringResource(
if (authenticationMode ==
AuthenticationMode.SIGN_IN) {

[Link].label_sign_in_to_account
} else {

[Link].label_sign_up_for_account
}
)
)
}

Our title will now be di pla ing a string based on the A then-
ti tio Mode that is provided to the fun tion.

49






















B cause this Text co po able is the title of the screen, we’re
g ing to want to style the text a cor ing to the theme of our
a pli tion. Here we’re g ing to a ply a fon Size to our Text
co po able that feels a bit more ting for a title. We’ll do this
by passing the value of [Link] to the fon Size a g ment of
the co po able.

Text(
text = stringResource(
if
([Link]
==
AuthenticationMode.SIGN_IN)
{

50














fi






[Link].label_sign_in_to_account
} else {

[Link].label_sign_up_for_account
}
),
fontSize = [Link]
)

We’ll also a just the fon Weight of our co po able - this will
help to make it stand out a bit more at the top of our UI. We
don’t want this to be too bold when di played, so we’ll a ply
the weight as the Fon Weight Black value.

Text(
text = stringResource(
if
([Link]
==
AuthenticationMode.SIGN_IN)
{

[Link].label_sign_in_to_account
} else {

[Link].label_sign_up_for_account
}
),
fontSize = [Link],

51








fontWeight = [Link]
)

Now that our title is styled, we can go ahead and co pose it


wit in our UI. B fore we go ahead and co pose it i side of our
A the ti tio Form, we need to e sure there is a cess to
an A the ti tio Mode re e ence that our title can use.
We’re g ing to be passing this from our state, so we’ll need to
rst add this as an a g ment to our A the ti tio Form
co po able.

// [Link]
@Composable
fun AuthenticationForm(

52
fi





























modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
)

Fo lowed by passing this into our A the ti tio Form func-


tion at the point of co po tion.

// [Link]
@Composable
fun AuthenticationContent(
modifier: Modifier = Modifier,
authenticationState:
AuthenticationState,
handleEvent: (event:
AuthenticationEvent) -> Unit
) {
...

AuthenticationForm(
modifier =
[Link](),
authenticationMode =
[Link]
)

...
}

Ho ping back over to our A the ti tio Form co pos-


able, we can then co pose our A the ti tio Title and

53






















pass in the r quired A the ti tio Mode re e ence that is
now a ces ible from the a the ti tion form.

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
) {
Column(
modifier = modifier
) {
AuthenticationTitle(
authenticationMode =
authenticationMode
)
}
}

To cr ate a bit of vis al sp cing at the top of our UI, we’ll also
add a Spacer co po able, sized with a height of 32dp via the
use of the height mo er.

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
) {
Column(

54










fi­











modifier = modifier
) {
Spacer(modifier =
[Link]([Link]))
AuthenticationTitle(
authenticationMode =
authenticationMode
)
}
}

With this in place, the title for our a the ti tion form is now
b ing co posed wit in our UI - this title is then co posed
based on the cu rent A the ti tio Mode that is wit in our
state.

55















56
Cr a ing the i put co tai er

With our title now in place, we can go ahead and start pu ting
t get er the area that will be used to hold the co po ents for
the form i put area. As seen in the end goal of our design, and
the co po able stru ture in the di gram above, this is all g ing
to be co tained wit in a Card co po ent. The Card is a com-
po able used to hold child co po ents in a Card shaped con-
tai er, o ten e e ated from the su face that it is di played on
top of.

We’re still g ing to be wor ing wit in the A the ti a-


tio Form co po able that we pr v ously de ned, co tin ing

57
































fi










co po tion from where we last a ded the A the ti tion-
Title. B fore we add our Card, we’re g ing to start by adding
a ot er Spacer co po able, this time b neath our A then-
ti tio Title d cla tion - this is so that our Card does not
end up pressed up right against the bo tom of our title.

// [Link]
@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
) {
Column(
modifier = modifier
) {
Spacer(modifier =
[Link]([Link]))
AuthenticationTitle(
modifier =
[Link](),
authenticationMode =
authenticationMode
)

Spacer(modifier =
[Link]([Link]))
}
}

58























With this in place, we can now add the d cla tion for our Card
co po able.

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
) {
Column(
modifier = modifier
) {
Spacer(modifier =
[Link]([Link]))
AuthenticationTitle(
modifier =
[Link](),
authenticationMode =
authenticationMode
)

Spacer(modifier =
[Link]([Link]))

Card {
Column(
modifier =
[Link]([Link]),
horizontalAlignment =
[Link]

59





) {

}
}
}
}

The Card co po able only has one r quired a g ment, which


is the co tent of the co po able. U der the hood, the Card
uti ises a Su face which in turn uses a Box to co tain the con-
tent that we provide it with. Ho ever, we are g ing to be
stac ing co tent ve ti ally - som thing that we can’t achieve
from r l ing on the Box here. We’ll co pose a Column wit in
the co tent of our Card co po able, which will a low us to
then co pose our chi dren i side of the Column, achie ing the
re ult of ve ti ally stacked co po ables. When adding this we’ll
also a ply some sty ing so that the Column has some pa ding
a plied to it, along with alig ing its chi dren h r zon ally in the
ce ter via the use of its h r zon a lig ment a g ment.

Next, we’re g ing to add some co straints to our Card us-


ing mo ers. As seen in the design, we want the Card to ll
the ma i um width of our screen, but with some sp cing
around the ou side so that it isn’t pres ing against the edges of
the screen. To ll the width of the area we’ll use the fil Max-
Width mo er, along with the pa ding mo er to a ply ho-
r zon al pa ding to the co po able.

Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = [Link])
) {

60

















fi­






fi­




fi

































fi­















fi
Column(
modifier =
[Link]([Link]),
horizontalAlignment =
[Link]
) {

}
}

Cu rently, our card won’t look like much, but we’d be able to
see som thing like the fo lo ing cu rently.

61





By d fault the Card uses an e e tion value of 1dp, which we
can see doesn’t make our card too vi ible on the su face back-
ground. Here we’re g ing to i crease this to 4dp by ove ri ing
the d fault value by passing in our own value via the e e a-
tion a g ment of the Card.

Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = [Link]),
elevation = [Link]
) {

We can see now that our Card has grea er pa ding a plied to
it.

62


















With this in place, we now have an area that is co gured for
our form i put area. At this point, our A the ti tio Form
co po able should look som thing like the fo lo ing.

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
) {
Column(
modifier = modifier
) {

63











fi

Spacer(modifier =
[Link]([Link]))
AuthenticationTitle(
authenticationMode =
authenticationMode
)
Spacer(modifier =
[Link]([Link]))
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal =
[Link]),
elevation = [Link]
) {
Column(
modifier =
[Link]([Link]),
horizontalAlignment =
[Link]
) {

}
}
}
}

64
Di pla ing the Email A dress
I put Field

Now that we have the Card in place to hold our i put form, we
can now think about mo ing ahead with adding the rst of our
i put elds - the Email I put. We’ll start here by cr a ing a new
co po able, Emai I put.

// [Link]
@Composable
fun EmailInput(
modifier: Modifier = Modifier

65




fi










fi
)

For the email i put, we’re g ing to e plore the use of the
Tex Field co po able, which we can add di ectly to our
co po able fun tion.

@Composable
fun EmailInput(
modifier: Modifier = Modifier
) {
TextField(modifier = modifier)
}

You’ll n tice at this point the IDE will give you a war ing, that’s
b cause the Tex Field r quires two a g ments for the func-
tion to be sa i ed. These are the value (that re re ents the cur-
rent value to be di played in the tex eld) and the value
change han ler (triggered when the i put changes and used to
u date the co po able state). We’ll need to sa i fy these be-
fore we can co ti ue, so let’s start by f cu ing on the r quired
value a g ment.

Wit in our a the ti tion state, we have an email a dress


pro erty, which re re ents the cu rently entered email a dress
from the user. We’re g ing to need to use this piece of state for
our email i put eld - so we’ll need to pass it into our Email-
I put co po able and a sign it to the value a g ment of the
Tex field.

@Composable
fun EmailInput(
modifier: Modifier = Modifier,

66




















fi




fi




















fi













email: String?
) {
TextField(
modifier = modifier,
value = email ?: ""
)
}

When present, this will be shown as the cu rent i put value in


our email a dress eld and if this state changes, then the
Tex Field value will be u dated to r ect that change. How-
ever, we are not cu rently provi ing a way for our state to be
changed - so this r co po tion is ne er g ing to o cur. To x
this, let’s i pl ment an a g ment in the form of a lambda func-
tion that a lows us to hoist the latest entered email up out of
our co po able fun tion.

@Composable
fun EmailInput(
modifier: Modifier = Modifier,
email: String?,
onEmailChanged: (email: String) ->
Unit
)

We can then use this lambda to tri ger the state hois ing,
provi ing a fun tion call wit in the o Valu Change block.

@Composable
fun EmailInput(
modifier: Modifier = Modifier,

67












fi












fl






fi
email: String?,
onEmailChanged: (email: String) ->
Unit
) {
TextField(
modifier = modifier,
value = email ?: "",
onValueChange = { email ->
onEmailChanged(email)
}
)
}

This means that our email a dress will be passed up and out of
our co po able fun tion, a lo ing us to u date the state of our
screen when it comes to i pl men ing that part of the pr ject.

At this point, we have an email i put eld that is b ing dis-


played i side of our co po able fun tion. It’s very mi i al, but
it’s enough to a low user i put.

68
















fi





Adding a l bel to the email i put
eld
At the m ment out i put eld looks a little blank, and it isn’t too
clear what the i put eld is to be used for. To add some cla ity
here, we’re g ing to uti ise the l bel a g ment to provide a
co po able that will act as a l bel. We’re g ing to want this to
simply read “Email A dress”, so we’ll need to add a new string
r source to our pr ject:

69
fi









fi


fi







<string name="label_email">Email
Address</string>

We can then go ahead and co pose a Text co po able for


the l bel of our Tex Field, passing this string r source as
the co tent of that co po able.

@Composable
fun EmailInput(
modifier: Modifier = Modifier,
email: String?,
onEmailChanged: (email: String) ->
Unit
) {
TextField(
modifier = modifier,
value = email ?: "",
onValueChange = { email ->
onEmailChanged(email)
},
label = {
Text(text = stringResource(
id =
[Link].label_email)
)
}
)
}

70









With this in place, our Tex Field is now di pla ing a l bel that
adds cla ity for what the eld is to be used for.

E fo cing the su po ted nu ber


of lines
If we have a little play with the i put eld, we might n tice that
if we provide an i put that e ceeds the length of the i put eld,
the text will start to e pand onto mu tiple lines.

71





fi





fi







fi
We don’t want this as it’s not an e pe ted b h viour for this
kind of form. We can x this by uti ising the singl Line a gu-
ment of the co po able, a lo ing us to e force all entered text
to r main on a single line - the co po able will a low h r zon al
na i tion to move through an entered string when this is en-
abled.

@Composable
fun EmailInput(
modifier: Modifier = Modifier,
email: String?,
onEmailChanged: (email: String) ->
Unit
) {
TextField(

72






fi
















modifier = modifier,
value = email ?: "",
onValueChange = { email ->
onEmailChanged(email)
},
label = {
Text(text = stringResource(
id =
[Link].label_email)
)
},
singleLine = true
)
}

We can see now that with this b ing e forced, our Tex Field
b haves in a much more e pe ted way.

73






Adding some ico graphy
Our Email I put is fee ing good at this point, but we’re g ing to
add a small piece of vis al de o tion by uti ising the leadin-
con. This a g ment a lows us to provide a co po able that
will be di played at the start of the i put eld. For this, we’re
g ing to use an Icon co po able to di play an email icon -
this won’t serve any real pu pose ot er than b ing a vis al dec-
o tion.

@Composable
fun EmailInput(

74























fi






modifier: Modifier = Modifier,
email: String,
onEmailChanged: (email: String) ->
Unit
) {
TextField(
modifier = modifier,
value = email,
onValueChange = { email ->
onEmailChanged(email)
},
label = {
Text(text = stringResource(
id =
[Link].label_email)
)
},
singleLine = true,
leadingIcon = {
Icon(
imageVector =
[Link],
contentDescription =
null
)
}
)
}

B cause this is just a vis al de o tion, we don’t need to


provide a co ten D scri tion for our Icon. The email i put

75










eld also has a l bel that d scribes the pu pose of the eld, so
we can rely on that for d scri ing the co po ent to the user.
With this in place, we can now see an email icon is di played at
the start of the i put eld.

Co po ing the Email I put eld


Now that our email i put is co plete, we can go ahead and
co pose it wit in our UI. B fore we go ahead and co pose it
i side of our A the ti tio Form, we need to e sure there
is a cess to both an email and on mai Change re e ence that
the co po able can use. We’re g ing to be passing this from

76
fi












fi


















fi



fi
our state, so we’ll need to rst add these as a g ments to our
A the ti tio Form co po able.

// [Link]
@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
email: String,
onEmailChanged: (email: String) ->
Unit
)

Along with then passing this into our A the ti tio Form
fun tion at the point where it is b ing co posed. For the
email eld we can pass the email a dress re e ence from with-
in our screen state, while for the on mai Change we’ll need to
tri ger an A the ti tio Event by u ing our handle-
Event lambda. When cal ing this, we’ll uti ise the Email-
Changed event type - i sta t a ing a re e ence by provi ing the
email a dress that is passed through the cal back.

// [Link]
@Composable
fun AuthenticationContent(
modifier: Modifier = Modifier,
authenticationState:
AuthenticationState,
handleEvent: (event:
AuthenticationEvent) -> Unit

77




fi











fi

























) {
...

AuthenticationForm(
modifier =
[Link](),
authenticationMode =
[Link],
email =
[Link],
onEmailChanged = { email ->
handleEvent(

[Link](email))
}
)

...
}

Ho ping back over to our A the ti tio Form co pos-


able, we can then co pose our Emai I put and pass in the
r quired email and on mai Changed re e ences that are now
a ces ible from the a the ti tion form.

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
email: String?,

78





















authenticationMode:
AuthenticationMode,
onEmailChanged: (email: String) ->
Unit
) {
Column(
modifier = modifier,
horizontalAlignment =
[Link]
) {
Spacer(modifier =
[Link]([Link]))
AuthenticationTitle(
authenticationMode =
authenticationMode)

Spacer(modifier =
[Link]([Link]))
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal =
[Link]),
elevation = [Link]
) {
Column(
modifier =
[Link]([Link]),
horizontalAlignment =

79
[Link]
) {
EmailInput(
modifier =
[Link](),
email = email ?: "",
onEmailChanged =
onEmailChanged
)
}
}
}
}

We’re g ing to make a small tweak here to cu to ise how the


Emai I put is co posed wit in our UI. When cr a ing the
co po able, we a ded su port for an o tio al mo er. We’re
g ing to uti ise this a g ment so that we can force the email in-
put eld to ll the avai able width, which is the width of the par-
ent Column. For this, we’ll uti ise the fil Ma Width mo er.

EmailInput(
modifier = [Link](),
email = email ?: "",
onEmailChanged = onEmailChanged
)

With this in place, the email i put eld for our a the ti tion
form is now b ing co posed wit in our UI - the co tent of this
is then co posed based on the cu rent email that is wit in our

80


fi





fi














fi











fi­






fi­

state, which in turn is then u dated via the use of our onE-
mai Changed lambda fun tion.

81



Di pla ing the Pas word In-
put Field

Now that we have our email i put eld, we’re g ing to want to
cr ate a si i ar co po ent, e cept this time for the entry of a
pas word. A lot of this co po ent is g ing to be the same as
our pr v ously cr ated co po able, so we’re g ing to start by
d pli a ing what we have so far, a ap ing it for pas word entry.

@Composable
fun PasswordInput(
modifier: Modifier = Modifier,

82





















fi






password: String,
onPasswordChanged: (email: String)
-> Unit
) {
TextField(
modifier = modifier,
value = password,
onValueChange = {
onPasswordChanged(it)
},
singleLine = true,
label = {
Text(text =
stringResource(id =
[Link].label_password)
)
}
leadingIcon = {
Icon(
imageVector =
[Link],
contentDescription =
null
)
}
)
}

We can see a couple of di fe ences here:

83


- We pass some slightly di fe ent a g ments into the
co po able. This time in the form of a pas word
and o Pas wor Changed cal back.

- We now call this o Pas wor Changed whene er


the pas word entry has changed

- The leadi Ion for our co po able is uti ising the


Lock icon

We are also u ing a slightly di fe ent l bel for our i put eld,
which will need to be a ded to our strin [Link] r source le
b fore we can co ti ue.

<string name="label_password">Password</
string>

At this point we’ll have a simple i put eld that can be used for
our pas word:

84


























fi








fi
fi
Now that we have the si i a i ies i pl me ted, we can go
ahead and think about the things that make our i put eld
slightly di fe ent from the email i put.

To gling pas word vi i i ity


One co mon thing in pas word i put elds is the abi ity to
toggle between vi i i i ies of the entered pas word. We’re go-
ing to i pl ment this for our Pas wor I put co po able,
but to do so we’re g ing to need some form of state that a lows
our co po able to know how the pas word eld should be
co posed. We’ll need to add a piece of mu able state to our
co po able, i Pas wor Hi den. We’ll d fault this to true for

85
































fi








fi






fi
s cu ity rea ons, fo lowed by wra ping this in r me ber so
that the value is pe sisted across r co po tions.

@Composable
fun PasswordInput(
modifier: Modifier = Modifier,
password: String?,
onPasswordChanged: (email: String)
-> Unit
) {

var isPasswordHidden by remember {


mutableStateOf(true)
}

TextField(
modifier = modifier,
value = password ?: "",
onValueChange = {
onPasswordChanged(it)
},
singleLine = true,
label = {
Text(text =
stringResource(id =
[Link].label_password)
)
}
leadingIcon = {
Icon(

86












imageVector =
[Link],
contentDescription =
null
)
}
)
}

💡 Not all pieces of our state need to be d clared at a glo al


level. This pas word vi i i ity state is sp ci c to this co pos-
able fun tion, so cou ling this here is som thing that makes
sense for us to do.

With this state in place, we can now uti ise this to co pose
our UI. The rst thing we’re g ing to do is uti ise the trailin-
con of the Tex Field co po able. We’ll start by co pos-
ing a new Icon, whose co tent will d pend on the cu rent
state of i Pas wor Hi den When the pas word is b ing hid-
den we want to show an icon that i di ates the vi i i ity is dis-
abled, while on the ot er hand, we want to i di ate that the
pas word is cu rently vi ible. For now, we’ll use a null con-
ten D scri tion, as we’ll be i pl men ing that piece of lo-
gic shortly.

Icon(
imageVector = if (isPasswordHidden)
{
[Link]
} else [Link],
contentDescription = null

87







fi


























f­i














)

So that this icon is i trac able by the user, we’re g ing to want
to e able click events. U ing the clic able mo er we can
toggle the i Pas wor Hi den state so that when the icon is
clicked, this state ag is ipped to the o po ite value.

Icon(
modifier = [Link] {
isPasswordHidden = !
isPasswordHidden
},
imageVector = if (isPasswordHidden)
{
[Link]
} else [Link]
)

This means that now when our Icon is clicked, the state ag
will be ipped and our Icon will be r co posed to r ect this
state change.

88

fl

fl



fl











fi­

fl
fl
With this in place, we have a fun tio ing icon that can be i ter-
a ted with by the user. Ho ever, at this point are icon isn’t very
a ces ible - the click event is in place has no form of d scrip-
tion, mea ing that a ces i i ity se vices will not be aware of the
pu pose of this co po ent. What we’ll do here is uti ise the
o Clic L bel of the clic able mo er so that we can
provide a d scri tion based on the cu rent i Pas wor Hid-
den state. We’ll need to start here by adding two new string re-
sources to our strin [Link] r sources:

<string name="cd_show_password">Show
Password</string>
<string name="cd_hide_password">Hide
Password</string>

89


























fi­






With these in place, we can now a ply a l bel to our click
mo er. We’ll uti ise the stri R source co po able func-
tion here to provide a string r source based on the cu rent
value of our state.

Icon(
modifier = [Link](
onClickLabel = if
(isPasswordHidden) {
stringResource(id =

[Link].cd_show_password)
} else stringResource(id =

[Link].cd_hide_password)
) {
isPasswordHidden = !
isPasswordHidden
},
imageVector = if (isPasswordHidden)
{
[Link]
} else [Link],
contentDescription = null
)

With this d scri tion a plied, we now have a co pleted Icon


co po able that can be slo ted into the traili con block
of our Tex Field.

90



fi­




















@Composable
fun PasswordInput(
modifier: Modifier = Modifier,
password: String?,
onPasswordChanged: (email: String)
-> Unit
) {
var isPasswordHidden by remember {
mutableStateOf(true)
}
TextField(
modifier = modifier,
value = password ?: "",
singleLine = true,
onValueChange = {
onPasswordChanged(it)
},
leadingIcon = {
Icon(
imageVector =
[Link],
contentDescription =
null
)
},
trailingIcon = {
Icon(
modifier =
[Link](

91
onClickLabel = if
(isPasswordHidden) {

stringResource(id =

[Link].cd_show_password)
} else
stringResource(id =

[Link].cd_hide_password)
) {
isPasswordHidden = !
isPasswordHidden
},
imageVector = if
(isPasswordHidden) {

[Link]
} else
[Link],
contentDescription =
null
)
},
label = {
Text(text =
stringResource(id =
[Link].label_password))
}
)

92
}

While we’ve i pl me ted the fun tio a ity to now toggle this
state ag, it’s not b ing used yet to a fect the vi i i ity of the
pas word eld co tent. For these sce ar os, the Tex Field con-
tains a vis a Tran for tion pro erty that can be used to
provide a class that can provide a tran for tion to the i put
co tent. Co pose comes with a pas word tran for tion out
of the box in the form of the Pas wor Vis a Tran for a-
tion class, which can be provided for the vis a Tran form-
tion pro erty.

Here we’re g ing to want to provide the Pas wor Visu-


a Tran for tion when the pas word should be masked,
and Vis a Tran for [Link] ot e wise.

visualTransformation = if
(isPasswordHidden) {
PasswordVisualTransformation()
} else [Link]

We can then slot this into our Tex Field co po able.

@Composable
fun PasswordInput(
modifier: Modifier = Modifier,
password: String?,
onPasswordChanged: (email: String)
-> Unit
) {
var isPasswordHidden by remember {
mutableStateOf(true)

93




fl


fi























































}
TextField(
modifier = modifier,
value = password ?: "",
singleLine = true,
onValueChange = {
onPasswordChanged(it)
},
leadingIcon = {
Icon(
imageVector =
[Link],
contentDescription =
null
)
},
trailingIcon = {
Icon(
modifier =
[Link](
onClickLabel = if
(isPasswordHidden) {

stringResource(id =

[Link].cd_show_password)
} else
stringResource(id =

[Link].cd_hide_password)

94
) {
isPasswordHidden = !
isPasswordHidden
},
imageVector = if
(isPasswordHidden) {

[Link]
} else
[Link],
contentDescription =
null
)
},
label = {
Text(text =
stringResource(id =
[Link].label_password))
}
)
}

When to gling this, we will now be able to see the pas word
co tent i ping from vi ible to masked.

95

fl




Now that our pas word i put is co plete, we can go ahead
and co pose it wit in our UI. B fore we go ahead and com-
pose it i side of our A the ti tio Form, we need to en-
sure there is a cess to both a pas word and o Pas word-
Changed re e ence that the co po able can use. We’re g ing
to be passing this from our state, so we’ll need to rst add
these as a g ments to our A the ti tio Form co pos-
able.

// [Link]
@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,

96


























fi



email: String,
password: String,
onEmailChanged: (email: String) ->
Unit,
onPasswordChanged: (password:
String) -> Unit
)

Along with then passing this into our A the ti tio Form
fun tion at the point where it is b ing co posed. For the
pas word we can pass the pas word re e ence from wit in our
screen state, while for the o Pas wor Changed we’ll need to
tri ger an A the ti tio Event by u ing our handle-
Event lambda. When cal ing this, we’ll uti ise the Pas word-
Changed event type - i sta t a ing a re e ence by provi ing the
email a dress that is passed through the cal back.

// [Link]
@Composable
fun AuthenticationContent(
modifier: Modifier = Modifier,
authenticationState:
AuthenticationState,
handleEvent: (event:
AuthenticationEvent) -> Unit
) {
...

AuthenticationForm(
modifier =
[Link](),

97



































authenticationMode =
[Link],
email =
[Link],
password =
[Link],
onEmailChanged = {

handleEvent([Link]
nged(it))
},
onPasswordChanged = {
handleEvent(

[Link](it))
}
)

...
}

Ho ping back over to our A the ti tio Form co pos-


able, we can then co pose our Pas wor I put and pass in
the r quired pas word and o Pas wor Changed re e ences
that are now a ces ible from the a the ti tion form. Here we’ll
also add a Spacer co po able so that there is some vis al
space between the email and pas word i put elds.

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,

98
























fi





authenticationMode:
AuthenticationMode,
email: String,
password: String,
onEmailChanged: (email: String) ->
Unit,
onPasswordChanged: (password:
String) -> Unit
) {
Column(
modifier = modifier,
horizontalAlignment =
[Link]
) {
Spacer(modifier =
[Link]([Link]))
AuthenticationTitle(
authenticationMode =
authenticationMode)

Spacer(modifier =
[Link]([Link]))
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal =
[Link]),
elevation = [Link]
) {
Column(

99
modifier =
[Link]([Link]),
horizontalAlignment =

[Link]
) {
EmailInput(
modifier =
[Link](),
email = email ?: "",
onEmailChanged =
onEmailChanged
)
Spacer(modifier =
[Link]([Link]))
PasswordInput(
password =
password ?: "",
onPasswordChanged =
onPasswordChanged
)
}
}
}
}

We’re g ing to make a small tweak here to cu to ise how the


Pas wor I put is co posed wit in our UI. When cr a ing the
co po able, we a ded su port for an o tio al mo er. We’re
g ing to uti ise this a g ment so that we can force the pass-
word i put eld to ll the avai able width, which is the width of

100







fi



fi













fi­

the pa ent Column. For this, we’ll uti ise the fil Ma Width
mo er.

PasswordInput(
modifier = [Link](),
password = password ?: "",
onPasswordChanged =
onPasswordChanged
)

With this in place, the pas word i put eld for our a the ti a-
tion form is now b ing co posed wit in our UI - the co tent of
this is then co posed based on the cu rent pas word that is
wit in our state, which in turn is then u dated via the use of our
o Pas wor Changed lambda fun tion.

101




fi­












fi








Han ling Ke board A tions
When i te ac ing with mu tiple i put elds, you can provide a
good user e pe ence by a lo ing the elds to be na i ated
through by u ing bu tons that are provided on the ke board. In
A droid d ve o ment these have a ways been r ferred to as
IME a tions, som thing that we still have a cess to in Je pack
Co pose u der the name of Ke board O tions. In our a then-
ti tion screen we’re g ing to want to uti ise some of these op-
tions to a low:

- Cu to is tion of the ke board d pen ing on the


e pe ted i put co tent

102





































fi
fi











- Na i tion from the email a dress eld to the pass-
word eld

- Su mi sion of the form when the pas word eld is in


f cus

These o tions t get er will a low the user to na i ate and sub-
mit the form, sea lessly co ple ing the a the ti tion ow
without nee ing to sp ci ally i te act with the UI co po ents
via touch. To add these fun tio a i ies we’ll start by cu to ising
the o tions provided by the email a dress Text Field. For this,
we’ll use the Ke board o tions class to sp cify the ke board
type that is to be used for the email i put eld. This means that
if su po ted, our ke board will be laid out sp ci ally for email
i put (sho ing the ‘@‘ sy bol and cli board o tions).

TextField(
…,
keyboardOptions = KeyboardOptions(
keyboardType = [Link]
)
)

Once the i put of the email a dress has been co pleted, the
user is g ing to want to co ti ue to the next i put eld for
pas word entry. At this point we’re g ing to want to a low this
to be done u ing the ke board, so we’ll add an IME a tion to
our ke board o tions in the form of the Next a tion.

TextField(
…,
keyboardOptions = KeyboardOptions(

103







fi




















fi



















fi

fi





fi

fi










fi




fl
imeAction = [Link],
keyboardType = [Link]
)
)

We’ll be able to see now that the pr v ously shown ‘r turn’ icon
has now been r placed with a ‘next’ icon.

With this now di played wit in the ke board, we’re g ing to


want to add some form of han ling so that the i te a tion with
the IME a tion tri gers an event for the user. For this, we’re go-
ing to uti ise the ke boa A tions pro erty of our Tex Field
co po able, provi ing an i stance of the Ke boa A tions
class to handle the r quired IME a tion. This class a lows us to
provide fun tion han lers for any r quired ke board a tions,

104





































which in turn will be triggered when the co re pon ing ime-
A tion is i te a ted with. When the o Next a tion is i te ac-
ted with, we’re g ing to want to change the cu rent f cus on
the screen - ta ing this from the cu rently f cused email ad-
dress text eld, r ques ing f cus on the pas word text eld.

To i pl ment this b h viour we’re g ing to need to uti ise


the F cu Requester class. This can be a signed to a com-
poser via a mo er, and then used els where to r quest the
f cus on the gi en co po able. We’ll start here by d ing a
re e ence to a F cu Requester for our pas word text eld.

AuthenticationTitle(
modifier = [Link](),
authenticationMode =
authenticationMode
)
Spacer(modifier =
[Link]([Link]))
val passwordFocusRequester =
FocusRequester()
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = [Link]),
elevation = [Link]
) { ... }

With this de ned, we’re now g ing to need to a sign it to our


pas word text eld. This can be done u ing the f cus-
Requester mo er, which a lows for the F cu Requester

105









fi

fi






fi




fi­

fi­



























fi

fi

fi





re e ence to be used to r quest f cus for the co po able that
it is a signed to.

PasswordInput(
…,
modifier =
[Link](passwordFocusReq
uester)
)

With this F cu Requester now in place we’re g ing to need


to tri ger the f cus r quest, this is g ing to ha pen when the
user i te acts with the next IME a tion that we’ve e abled via
the ke board o tions. While we’ve provided this o tion, we
haven’t provided any form of han ler for it - this is done u ing
the ke boa A tions pro erty of the Tex Field. This prop-
erty takes a Ke boa A tions i stance, used to provide
han lers for each of the avai able IME a tions. In our case for
the email a dress text eld, we’re just g ing to provide an im-
pl men tion for the o Next a tion.

TextField(
…,
keyboardActions = KeyboardActions(
onNext = {

}
)
)

106
























fi





















At this point though, we don’t have an thing that we can trig-
ger from wit in this o Next block. We could pass the f cus re-
quester re e ence into the Emai I put co po able, but it’ll
make for a clea er (and more tes able) co po able if we pass
this event up to the pa ent co po able. For this, we’ll add a
new a g ment-less lambda a g ment to our co po able func-
tion.

@Composable
fun EmailInput(
modifier: Modifier = Modifier,
email: String?,
onEmailChanged: (email: String) ->
Unit,
onNextClicked: () -> Unit
)

Wit in the o Next block of our Ke boa A tions i stance


we’re g ing to tri ger the o Nex Clicked lambda.

TextField(
…,
keyboardActions = KeyboardActions(
onNext = {
onNextClicked()
}
)
)

We’re now g ing to need to hop over to the pa ent co pos-


able and i pl ment this r quired o Nex Clicked a g ment.

107












































Wit in this i pl men tion, we’re then g ing to use our Fo-
cu Requester to tri ger the r sues F cus() fun tion.

EmailInput(
modifier = [Link](),
email = email,
onEmailChanged = onEmailChanged
) {

[Link]()
}

When this fun tion call is triggered, the f cus will be r que ted
for our pas word Tex Field, which is the co po able that our
F cu Requester is a tached to. If the r quest is su ces ful,
this Tex Field will come into f cus, a lo ing the user to move
between the Email A dress and Pas word text elds without
nee ing to man ally i te act with either of the co po ables.

Su por ing form su mi sions via


IME a tions
As well as u ing IME a tions to na i ate between i put elds,
there is a range of ot er events which can be handled. For ex-
ample, we can also use them to su mit forms that the user has
entered data into - this is done in the form of the Done IME Ac-
tion. This can be handled in a very si i ar way to the Next ac-
tion that we just handled, i stead of provi ing the Im Ac-

108














































fi






fi



tion Done a tion type to our Ke boa O tions - a sig ing
this to our Pas wor I put Tex Field.

TextField(
…,
keyboardOptions = KeyboardOptions(
imeAction = [Link]
)
)

We’ll be able to see now that the pr v ously shown ‘r turn’ icon
has now been r placed with a ‘done’ icon.

109
















We then again need to provide a Ke boa A tions i pl ment-
tion to our tex eld, this time i pl men ing the o Done hand-
ler. In most cases the pas word eld will be vi ited after the
email a dress eld, mea ing that the form will be co plete
and the user can move fo ward to a the ti ate their cr den-
tials.

TextField(
…,
keyboardActions = KeyboardActions(
onDone = {

}
)
)

At this point though, we don’t have an thing that we can trig-


ger from wit in this o Done block. For this, we’ll add a new ar-
g ment-less lambda a g ment to our co po able fun tion.

@Composable
fun PasswordInput(
modifier: Modifier = Modifier,
password: String,
onPasswordChanged: (email: String)
-> Unit,
onDoneClicked: () -> Unit
)

110





fi
fi







fi



















With that in mind, wit in this han ler, we’ll use our o Done-
Clicked() fun tion to tri ger the cal back.

TextField(
…,
keyboardActions = KeyboardActions(
onDone = {
onDoneClicked()

handleEvent([Link]
cate)
}
)
)

We’re now g ing to need to hop up to the pa ent co po able


and i pl ment this r quired o Don Clicked a g ment.

PasswordInput(
modifier = [Link]()
.focusRequester(passwordFocusReq
uester),
password = password,
onPasswordChanged =
onPasswordChanged,
onDoneClicked = {

111

















}
)

Even though we’ve now i pl me ted this cal back, we want to


tri ger the a the ti tion ow but we don’t have any way of
tri ge ing that yet. To be able to achieve this, we’ll need to add
an a g ment to our A the ti tio Form co po able - this
is g ing to work in the same way as the email + pas word
change han lers.

// [Link]
@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
email: String?,
password: String?,
onEmailChanged: (email: String) ->
Unit,
onPasswordChanged: (password:
String) -> Unit,
onAuthenticate: () -> Unit
)

We’ll then also need to hop up again into the A the ti a-


tio Co tent co po able so that we can sa i fy this new re-
quired a g ment. At this point wit in the A the ti tion-
Co [Link] le, we have a cess to the handl Event fun tion
- we’re now g ing to uti ise this to tri ger the A the ti ate
event.

112













fi









fl



























// [Link]

AuthenticationForm(
modifier = [Link](),
email = [Link],
password =
[Link],
authenticationMode =
[Link],
onEmailChanged = {

handleEvent([Link]
nged(it))
},
onPasswordChanged = {

handleEvent([Link]
Changed(it))
},
onAuthenticate = {

handleEvent([Link]
cate)
}
)

Ho ping back down into the A the ti tio [Link] le,


we can now uti ise the onA thenti ate fun tion that is
passed into our A the ti tio Form co po able. Here we
can di ectly pass this to our Pas wor I put co po able, this

113























fi
is b cause the fun tion si n tures match - so there’s no need
for us to re-i pl ment the cal back.

PasswordInput(
modifier = [Link]()
.focusRequester(passwordFocusReq
uester),
password = password,
onPasswordChanged =
onPasswordChanged,
onDoneClicked = onAuthenticate
)

Now, when the IME A tion for the pas word eld is pressed, the
a the ti tion ow will be triggered. One nal thing we can do
here for the pas word eld is provide a ke board type to our
Ke board O tions i stance. Si i ar to how we did for the Email
A dress text eld, this will a low the ke board to be cu to ised
based on the type of co tent that is b ing entered.

TextField(
…,
keyboardOptions = KeyboardOptions(
imeAction = [Link],
keyboardType = [Link]
)
)

When co pared the email i put type that we de ned for our
email a dress text eld, we can see here now that the keys
avai able on the ke board di fer slightly, f cu ing on nu bers

114












fi
fl





fi

fi













fi­

fi

fi



and le ters, r mo ing the use of some sp cial cha a ters and
cli board o tions.

115








Pas word R quir ments

At this point, our user can enter their cr de tials into our au-
the ti tion form. When we de ned some of the bus ness l gic
for our state, we de ned an enum, Pas wor R quir ments.
Wit in our state class, we also de ned a list of these Pass-
wor R quir ments, a lo ing us to e force ce tain r quire-
ments on the entered pas word. To r cap, these r quir ments
were:

enum class PasswordRequirements {

116








fi




fi
fi















CAPITAL_LETTER, NUMBER,
EIGHT_CHARACTERS
}

This means that the pas word must co tain a ca i al le ter, a


nu ber and be at least 8 cha a ters long. While we have these
r quir ments de ned, we need to let the user know about
them - so we’re g ing to cr ate a co po able that di plays the
r quir ments du ing sign-up, mar ing the r quir ments vis ally
as sa i ed when they have been met.

Cr a ing the R quir ment Com-


po able
We’re g ing to start here by loo ing at the co po able that will
be used to re re ent each of the r quir ments that we’ve
stated in our enum.

117









fi




fi
























For this co po able, we’re g ing to di play a simple UI com-
po ent that will di play a l bel for the r quir ment, along with
an icon that will i di ate if this r quir ment has been sa i ed.
We’ll start by cr a ing a new co po able fun tion that takes
our d fault mo er as an a g ment.

@Composable
fun Requirement(
modifier: Modifier = Modifier
)

118







fi­


















fi
We me tioned above about the two r quir ments for this com-
po able fun tion - the me sage to be di played, along with
whet er the r quir ment is cu rently sa i ed. We’ll add more
two a g ments - a string r source that will be used for the l bel
of our r quir ment co po able, along with a boolean ag to
su port the sa i ed status.

@Composable
fun Requirement(
modifier: Modifier = Modifier,
message: String,
satisfied: Boolean
)

With these a g ments in place, we can start to build out the


co tent of our co po able. Here we’ll add a Row co po able
so that we can la out the chi dren h r zon ally next to one an-
ot er, along with adding some mo ers to cu to ise the be-
h viour + di play of co tent. We’ll chain from the mo f er
that is passed into the co po able fun tion, u ing the pad-
ding mo er to add some pa ding to our co po able. We’ll
also use the ve tica lig ment a g ment to align the chil-
dren in the ve ti al ce ter - b cause we’re sho ing an icon with
a text l bel to the side of it, we want these to be aligned on the
Y axis which can be achieved via the use of Alig ment.-
Cente Ve ti ally.

@Composable
fun Requirement(
modifier: Modifier = Modifier,
message: String,
satisfied: Boolean

119















fi­












fi























fi­






fi












fl




) {
Row(
modifier =
[Link]([Link]),
verticalAlignment =
[Link]
) {

}
}

We’ll now go ahead and add an Icon co po able which will


be used to show a Check icon u ing the Ico [Link]-
[Link] icon from the co pose pac age. We’ll use the
size mo er to x this size to [Link], along with se ting null
as the co tent d scri tion. While we will be u ing this to si n fy
the cu rent status of the r quir ment, we’re g ing to f cus on
the a ces i i ity of this co po ent after the foun tions are in
place.

@Composable
fun Requirement(
modifier: Modifier = Modifier,
message: String,
satisfied: Boolean
) {
Row(
modifier =
[Link]([Link]),
verticalAlignment =
[Link]

120







fi­


fi



















) {
Icon(
modifier =
[Link]([Link]),
imageVector =
[Link],
contentDescription = null
)
}
}

💡 It’s i por ant to r me ber that null should only be used in


cases where a d scri tion is not a pli able for the co pos-
able. This could be either b cause the co po able is purely
de o a ive, or b cause you are provi ing a d scri tion some
ot er way - such as through the pa ent co po able when mer-
ging s mantics.

With our icon now in place, let’s add the l bel that will de-
scribe what the r quir ment is. For this, we’ll use the Text com-
po able and a sign the me sage from the co po able func-
tion a g ments.

@Composable
fun Requirement(
modifier: Modifier = Modifier,
message: String,
satisfied: Boolean
) {
Row(
modifier =
[Link]([Link]),

121


































verticalAlignment =
[Link]
) {
Icon(
modifier =
[Link]([Link]),
imageVector =
[Link],
contentDescription = null
)
Text(
text = message
)
}
}

Cu rently, we’ll have som thing that looks like this co posed:

122



We can i prove the a pea ance of this slightly by ma ing two
small tweaks. We’ll rst add a Spacer co po able between
the Icon and Text to cr ate some vis al space (u ing the
width mo er to set the width of this as 8dp). Next, we’ll over-
ride the d fault fon Size of the Text co po able - the text
size is cu rently quite big in the scree shot above, so we’ll use
12sp so that this feels a bit more in style with the icon.

@Composable
fun Requirement(
modifier: Modifier = Modifier,
message: String,
satisfied: Boolean
) {
Row(

123





fi­
fi












modifier =
[Link]([Link]),
verticalAlignment =
[Link]
) {
Icon(
modifier =
[Link]([Link]),
imageVector =
[Link],
contentDescription = null
)
Spacer(modifier =
[Link]([Link]))
Text(
text = stringResource(id =
message),
fontSize = [Link]
)
}
}

💡 When mod f ing the fon Size, it’s i por ant to not make the
font too small - it still needs to r main rea able for users. It can
be hel ful in these cases to use the t p graphy va ues from the
theme to help pr vent these i sues from o cu ring.

We can see now, that things look a little bit be ter here.

124
















Cu rently, we have this sa i fied ag wit in our co po able,
but we’re not d ing an thing with it. We’re g ing to uti ise this
here to vis ally re re ent the status of the r quir ment - which
we’ll do by u ing a co or for the icon and text based on whet er
the r quir ment is sa i ed. Here we’ll use the theme of our ap-
pli tion to cr ate a co or re e ence.

val tint = if (satisfied) {


[Link]
} else
[Link](alph
a = 0.4f)

We use the primary co or in our sa i ed sce ario, as this will


stand out to the user du ing the a the ti tion pr cess. For the

125















fi








fl



fi












case where the pas word does not meet the r quir ments, we
copy the o Su face co or from our theme, mod f ing the al-
pha value so that the co or a pears slightly faded out in our UI.
U ing this co or re e ence, we can then a ply this to the Icon
u ing the tint a g ment, along with a pl ing it to the l bel us-
ing the co or a g ment on the Text co po able.

@Composable
fun Requirement(
modifier: Modifier = Modifier,
message: String,
satisfied: Boolean
) {
val tint = if (satisfied) {
[Link]
} else
[Link](alph
a = 0.4f)
Row(
modifier =
[Link]([Link]),
verticalAlignment =
[Link]
) {
Icon(
modifier =
[Link]([Link]),
imageVector =
[Link],
contentDescription = null,

126


























tint = tint
)
Spacer(modifier =
[Link]([Link]))
Text(
text = stringResource(id =
message),
fontSize = [Link],
color = tint
)
}
}

With this in place, we now have a co po able that will di play


a r quir ment, along with the ing it to re re ent the cu rent
state of the sa i ed ag.

127




fi
fl







I pro ing the r quir ment ac-
ces i i ity
While we have the above co po able fun tion in place to com-
pose a r quir ment, we can i prove things here when it comes
to the use of a ces i i ity se vices. Cu rently, the l bel of the re-
quir ment will be read - but we are r l ing on a co or re res-
en tion to si n fy whet er the r quir ment is sa i ed or not.
This isn’t very a ces ible, so we’ll a ply some mod tions to
i prove things here. What we’ll do is add a d scri tion to the
r quir ment, so that when the a ces i i ity se vice is d scri ing

128














































fi
fi






the el ment, it can be d scribed whet er or not the r quire-
ment is cu rently sa i ed.

We’ll start here by adding two new string r sources to our


strin [Link] r source le.

<string
name="password_requirement_satisfied">
%s, satisfied
</string>
<string
name="password_requirement_needed">
%s, needed
</string>

We use %s as a plac hol er for a string that we will be r pla-


cing it with, this will be the l bel for the r quir ment. This
means that the a ces i i ity se vices will d scribe this as “At
least 8 cha a ters, sa i ed” or “At least 8 cha a ters, needed”.
Wit in our R quir ment co po able we can now build our
d scri tion by a ces ing this r source via the use of the
stri R source co po able fun tion.

@Composable
fun Requirement(
modifier: Modifier = Modifier,
message: String,
satisfied: Boolean
) {
val requirementStatus = if
(satisfied) {

129





















fi



fi

fi



















stringResource(id =

[Link].password_requirement_satisfied,
message)
} else {
stringResource(id =

[Link].password_requirement_not_satisf
ied, message)
}
}

You’ll n tice here that we pass the me sage a g ment from our
co po able fun tion as an a g ment to the stri R source
fun tion. This is b cause we are u ing the %s plac hol er, so
any a g ments that are provided here will be used as the re-
plac ment. With this in place, we can now go ahead and a ply
the s mantics mo er to our Row co po able. When d ing
this we’ll want to set the merg De cen ants ag as true (as
we don’t need the child co po ables to be d scribed i d vidu-
ally), along with se ting the text s mantics as the r quire-
men Status that we ge e ated above. This pro erty r quires
the A no ate String type, so we’ll i sta t ate an i stance by
provi ing our r quir men Status.

Row(
modifier = [Link]([Link])
.semantics(mergeDescendants =
true) {
text =
AnnotatedString(requirementStatus)

130



















fi­






















fl













},
verticalAlignment =
[Link]
)

B cause we’re now se ting the s mantics for our Row and
me ging the co tent of the co tai er, we can go ahead and
clear the s mantics on the child Text co po able, u ing the
clea An Se S mantics mo er to do so. This will avoid any
d scri tions from b ing d pli ated with the s mantics tree.

Text(
modifier =
[Link] { },
text = message,
fontSize = [Link],
color = tint
)

With this in place, we now have an u dated co po able func-


tion that has i proved a ces i i ity su port.

@Composable
fun Requirement(
modifier: Modifier = Modifier,
message: String,
satisfied: Boolean
) {
val tint = if (satisfied) {
[Link]

131






















fi­










} else
[Link](alph
a = 0.4f)
val requirementStatus = if
(satisfied) {
stringResource(id =

[Link].password_requirement_satisfied,
message)
} else {
stringResource(id =

[Link].password_requirement_not_satisf
ied, message)
}
Row(
modifier =
[Link]([Link])
.semantics(mergeDescendants
= true) {
text =
AnnotatedString(requirementStatus)
},
verticalAlignment =
[Link]
) {
Icon(
modifier =
[Link]([Link]),

132
imageVector =
[Link],
contentDescription = null,
tint = tint
)
Spacer(modifier =
[Link]([Link]))
Text(
modifier =
[Link] { },
text = message,
fontSize = [Link],
color = tint
)
}
}

Buil ing the R quir ment items


At this point, we now have a R quir ment co po able func-
tion, but we’re not buil ing these wit in our UI. We’ll start here
by d ing a new co po able fun tion, Pas wor R quire-
ments, that takes a list of sa i ed r quir ments - we have this
value wit in our state, so this will simply be provided from there
when the time comes to i pl men ing that.

Wit in this fun tion, we’ll also co pose a Column, as we’re


g ing to be sho ing a ve ti al list of the r quir ment co pos-
ables.

133



fi















fi

















@Composable
fun PasswordRequirements(
modifier: Modifier = Modifier,
satisfiedRequirements:
List<PasswordRequirements>
) {
Column(modifier = modifier) {

}
}

Co po ing the r quir ments is a very mi i al pr cess. We


pr v ously de ned the Pas wor R quir ments enum, with
each value ha ing a l bel pro erty in the form of a string re-
source. That means that wit in this co po able we can simply
loop through the va ues of the Pas wor R quir ments
enum, co po ing a R quir ment u ing the cu rent item in
the loop. Here we’ll use the l bel pro erty to r trieve a string
that can be passed for the me sage a g ment of the Re-
quir ment, along with u ing the Pas wor R quir ments
re e ence to check whet er the r quir ment is cu rently sa is-
ed.

@Composable
fun PasswordRequirements(
modifier: Modifier = Modifier,
satisfiedRequirements:
List<PasswordRequirements>
) {
Column(
modifier = modifier

134
fi








fi









































) {

[Link]().forEach
{ requirement ->
Requirement(
message =
stringResource(
id =
[Link]),
satisfied =
[Link](
requirement
)
)
}
}
}

With this, we can now co pose a co le tion of r quir ments


and their cu rent sa i ed status.

135



fi





Co po ing the R quir ment
items
With the Pas wor R quir ments co po able now in place,
we’re g ing to want to slot this into our UI. Ho ever, we’re only
g ing to want to show this to the user when they are sig ing up
- a user who has signed in will know their pas word and have a
va id pas word, so this va i tion wit in the UI does not make
too much sense. We could simply co pose this based on our
A the ti tio Mode re e ence wit in our screen state, but
i stead, we’re g ing to uti ise the A i ate Vi i i ity com-
po able to a i ate our co po able in and out, based on its
vi ible ag. This means we could de ne, anA i ate Vi ib-

136








fl
























fi

















i ity co po able, se ting the vi ible ag u ing an a se tion
against the A the ti tio Mo e.SIGN_UP value, and
provide our Pas wor R quir ments co po able as the con-
tent.

// [Link]

AnimatedVisibility(
visible = authenticationMode ==
AuthenticationMode.SIGN_UP
) {
PasswordRequirements(...)
}

This would mean that when the cu rent A the ti tio Mode
wit in our state is not equal to SIGN_UP, the Pas wor Re-
quir ments would a i ated out of view - a i a ing into view
when that state is toggled by the user (som thing that we have
not i pl me ted yet!).

While the above would work, we need to co pose it wit in


our UI. For the A the ti tio Form co po able fun tion,
we already have a cess to an A the ti tio Mode re e ence
- we’ll just need to add a new a g ment to the fun tion in the
form of List<Pas wor R quir ments>.

//[Link]

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,

137






































fl


























authenticationMode:
AuthenticationMode,
email: String,
password: String,
completedPasswordRequirements:
List<PasswordRequirements>,
onEmailChanged: (email: String) ->
Unit,
onPasswordChanged: (password:
String) -> Unit
)

We’ll then also need to hop over to our A the ti tio Con-
[Link] le to mod fy the co po tion of our A the ti a-
tio Form co po able. We’ll need to pass a value for the com-
plete Pas wor R quir ments - the state re e ence that we
are u ing for the ot er a g ments here has the cu rent r quire-
ments wit in it, so we’ll simply need to pass that here.

// [Link]

AuthenticationForm(
modifier = [Link](),
email = [Link],
password =
[Link],
completedPasswordRequirements =
[Link]
,
authenticationMode =

138




fi

























[Link],
onEmailChanged = {

handleEvent([Link]
nged(it))
},
onPasswordChanged = {

handleEvent([Link]
Changed(it))
}
)

Hea ing back over to our A the ti tio Form co po able,


we can now co pose our A i ate Vi i i ity and Pass-
wor R quir ments co po ables. Here we’ll also add a
Spacer co po able so that there is some vis al space
between UI co po ents.

// [Link]

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
email: String,
password: String,
completedPasswordRequirements:
List<PasswordRequirements>,

139

























onEmailChanged: (email: String) ->
Unit,
onPasswordChanged: (password:
String) -> Unit
) {
Column(
modifier = modifier,
horizontalAlignment =
[Link]
) {
EmailInput(
modifier =
[Link](),
email = email,
onEmailChanged =
onEmailChanged
) {

[Link]()
}
Spacer(modifier =
[Link]([Link]))
PasswordInput(
modifier =
[Link]()
.focusRequester(password
FocusRequester),
password = password,
onPasswordChanged =
onPasswordChanged,

140
onSubmitForm =
onAuthenticate
)
Spacer(modifier =
[Link]([Link]))

AnimatedVisibility(
visible = authenticationMode
==

AuthenticationMode.SIGN_UP
) {

PasswordRequirements(completedPasswordRe
quirements)
}
}

With this in place, we now have our Pas wor R quir ments
co posed wit in our a the ti tion UI. When sig ing up, en-
te ing a pas word will now mod fy the r quir ments UI to dis-
play the cu rently sa i ed r quir ments.

141







fi














142
Tri ge ing the A the ti tion
ow

In our UI the user can now enter their email a dress and pass-
word, but they can’t yet tri ger the a the ti tion ow to a low
them to pr ceed in our app. To make this po sible, we’re now
g ing to add a bu ton that a lows them to pe form this a then-
ti tion ow u ing the cr de tials that they have entered into
the form. While this is po sible u ing the IME a tion that we ad-
ded in the last se tion, a bu ton to tri ger this ow would be
e pe ted by a lot of users. We’ll start here by cr a ing a new

143
fl






fl























fl


fl




co po able fun tion, A the ti tio Bu ton with a d fault
Mo f er a g ment.

// [Link]

@Composable
fun AuthenticationButton(
modifier: Modifier = Modifier
)

Next, we’ll add a Bu ton co po able, this r quires two of its


pro e ties to be provided - an o Click cal back han ler and a
co po able that re re ents the body of the Bu ton.

@Composable
fun AuthenticationButton(
modifier: Modifier = Modifier
) {
Button(
modifier = modifier
) {

}
}

As it is, this Bu ton isn’t too much use to us as it’s not sho ing
or tri ge ing an thing. To change this we’ll add a co po able
for the body of the bu ton, this will re re ent either a “Sign in”
or “Sign up” me sage, d pen ing on the cu rent a the ti a-
tio Mode from our a the ti tion state re e ence. We’ll need

144





















































to start by adding a couple of string r sources for the bu ton
text:

<string name="action_sign_up">Sign Up</


string>
<string name="action_sign_in">Sign In</
string>

So that we know what one of these strings to show wit in our


bu ton, the co po able fun tion is g ing to need to know what
A the ti tio Mode is cu rently s le ted. For this, we’re go-
ing to need to pass this into our co po able fun tion.

@Composable
fun AuthenticationButton(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
)

Next, we’ll use an if stat ment check to handle what the mes-
sage should be d pen ing on the state, then set this as the
co tent of a Text co po able wit in the body of our Bu ton.

@Composable
fun AuthenticationButton(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
) {

145



























Button(
modifier = modifier
) {
Text(
text = stringResource(
if (authenticationMode
==

AuthenticationMode.SIGN_IN) {

[Link].action_sign_in
} else {

[Link].action_sign_up
}
)
)
}
}

With this Text in place, we’ll now be able to see a bu ton on-
screen whose body re re ents the cu rent a the ti tion mode
that is set wit in our state.

146









At this point, we can now think about han ling the o Click
tri ger from our Bu ton. What we want to do here is tri ger an
event that will start the a the ti tion ow - si i ar to how else
we have handled events in this pr ject, we’re g ing to a low the
pa ent co po able to handle this event for us. With this in
mind, we’ll add a lambda fun tion a g ment to our co pos-
able fun tion, onA thenti ate. We’ll then want to tri ger this
lambda wit in the o Click a g ment of our Bu ton co pos-
able.

@Composable
fun AuthenticationButton(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,

147



















fl












onAuthenticate: () -> Unit
) {
Button(
modifier = modifier,
onClick = {
onAuthenticate()
}
) {
Text(
text = stringResource(
if (authenticationMode
==

AuthenticationMode.SIGN_IN) {

[Link].action_sign_in
} else {

[Link].action_sign_up
}
)
)
}
}

Now when our Bu ton is pressed, the a the ti tion ow will be


triggered and the cr de tials from our Tex Fields will be used
du ing this pr cess. Ho ever, these elds might not a ways
co tain va id data - we have an e abl A thenti tion
pro erty wit in our a the ti tion state re e ence. While this
only checks if both the email a dress and pas word are not

148

















fi









fl



empty, this helps to avoid the ow b ing triggered when data
might not have been entered yet. We can handle this via the
Bu ton by di abling the bu ton from b ing i te a ted with
when the form co tent is not va id. For this we’ll use the en-
abled pro erty of the Bu ton, a sig ing thee abl Au-
thenti tion value to it.

@Composable
fun AuthenticationButton(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
enableAuthentication: Boolean,
onAuthenticate: () -> Unit
) {
Button(
modifier = modifier,
onClick = {
onAuthenticate()
},
enabled = enableAuthentication
) {
Text(
text = stringResource(
if (authenticationMode
==

AuthenticationMode.SIGN_IN) {

[Link].action_sign_in

149








fl










} else {

[Link].action_sign_up
}
)
)
}
}

Now that our bu ton co po able is i pl me ted, we can go


ahead and co pose it wit in our A the ti tio Form com-
po able. We’ll need to start by adding a new a g ment to our

150
















A the ti tio Form co po able, this will be e abl Au-
thenti tion in the form of a Boolean value.

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
email: String,
password: String,
completedPasswordRequirements:
List<PasswordRequirements>,
enableAuthentication: Boolean,
onEmailChanged: (email: String) ->
Unit,
onPasswordChanged: (password:
String) -> Unit
)

We’ll then also need to hop over to our A the ti tio Con-
[Link] le to mod fy the co po tion of our A the ti a-
tio Form co po able. We’ll need to pass a value for the en-
abl A thenti tion - our state re e ence that we are u ing
for the ot er a g ments here has the cu rent e abled state
wit in it, so we’ll simply need to pass that here.

// [Link]

AuthenticationForm(
modifier = [Link](),

151








fi































email = [Link],
password =
[Link],
completedPasswordRequirements =
[Link]
,
authenticationMode =

[Link],
enableAuthentication =
[Link](),
onEmailChanged = {

handleEvent([Link]
nged(it))
},
onPasswordChanged = {

handleEvent([Link]
Changed(it))
},
onAuthenticate = {

handleEvent([Link]
cate)
}
)

Hea ing back over to our A the ti tio Form co po able,


we can now co pose our A the ti tio Bu ton. Here

152















we’ll also add a Spacer co po able so that there is some visu-
al space between UI co po ents.

// [Link]

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
email: String,
password: String,
completedPasswordRequirements:
List<PasswordRequirements>,
onEmailChanged: (email: String) ->
Unit,
onPasswordChanged: (password:
String) -> Unit
) {
Column(
modifier = modifier,
horizontalAlignment =
[Link]
) {
EmailInput(
modifier =
[Link](),
email = email,
onEmailChanged =
onEmailChanged

153




) {

[Link]()
}
Spacer(modifier =
[Link]([Link]))
PasswordInput(
modifier =
[Link]()
.focusRequester(password
FocusRequester),
password = password,
onPasswordChanged =
onPasswordChanged,
onSubmitForm =
onAuthenticate
)
Spacer(modifier =
[Link]([Link]))

AnimatedVisibility(
visible = authenticationMode
==

AuthenticationMode.SIGN_UP
) {

PasswordRequirements(completedPasswordRe
quirements)
}

154
Spacer(modifier =
[Link]([Link]))

AuthenticationButton(
enableAuthentication =
enableAuthentication,
authenticationMode =
authenticationMode,
onAuthenticate =
onAuthenticate
)
}
}

With this in place, our A the ti tio Bu ton is now b ing


co posed i side of our a the ti tion form - this now a lows
the user to tri ger the a the ti tion ow.

155















fl




156
To gling the A the ti tion
mode

With all of the above in place, our user can pe form a the ti a-
tion u ing the provided elds. Ho ever, the UI only cu rently
su ports the d fault a the ti tion type - which is cu rently
co gured to be sign in. This means that if the user does not
cu rently have an a count, they won’t be able to cr ate one us-
ing our a the ti tion form. In this se tion we’re g ing to go
ahead and de ne a Bu ton that a lows the user to toggle
between sign up and sign in, r co po ing our a the ti tion

157



fi




fi





fi


























form to r ect the cu rent mode. We’ll start here by cr a ing a
new co po able fun tion, Toggl A thenti tio Mode
with a d fault Mo f er a g ment.

@Composable
fun ToggleAuthenticationMode(
modifier: Modifier = Modifier
)

With this co po able fun tion in place, we can build out the
co tent r quired to di play our toggle bu ton. We’re g ing to
start by d ing the use of a Su face, which act as the con-
tai er for our toggle co po able. The Su face co po able
will co pose the provided body i side of a Box co po able. It
will also theme i self use the su face co or from the a pli a-
tion theme, which is what we want to be a plied to our se tings
item in terms of sty ing. This saves us from u ing a Box co pos-
able and a pl ing a co le tion of sty ing ourselves when this
co po ent already e ists to do it for us. When co po ing this,
we’ll a ply the mo er from the a g ment of our co po able
fun tion, along with ove ri ing the d fault e e tion of the
se vice with the value of 8dp.

@Composable
fun ToggleAuthenticationMode(
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier,
elevation = [Link]
) {

158













fl
fi












fi­

















































}
}

Next, we’re g ing to add a bu ton to our Su face, and for this,
we will use the Tex Bu ton co po able. This is a co po able
offered by the co pose m te al pac age which a lows us to
co pose a at bu ton that di plays some co po able co tent
for its body. When co po ing a Tex Bu ton we will need to
provide an o Click han ler and some co tent body to be dis-
played wit in the co po able bu ton

@Composable
fun ToggleAuthenticationMode(
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier
.padding(top = [Link]),
elevation = [Link]
) {
TextButton(
onClick = {

}
) {

}
}
}

159


fl






























Now that we have the co po able de ned, we need to go
ahead and po late these pro e ties of the co po able. Be-
fore we add this co po able, we’ll go ahead and add some
a d tio al r sources to be used for the body of the co pos-
ables.

<string name="action_need_account">
Need an account?
</string>
<string
name="action_already_have_account">
Already have an account?
</string>

Next, we’ll use the co tent pro erty of the Tex Bu ton so that
we can see some form of vis al re ult on our screen. Now we
can add a Text co po able to the body of our Tex Bu ton,
this will di play the co tent of a string r source, d pen ing on
the cu rent a the ti tion mode re re e ted by our state. Be-
fore we can do this though, we’ll need to know the A then-
ti tio Mode which should be used when co po ing this
co po able. We’ll add this as an a g ment for our co po able
fun tion so that it can be passed in from the pa ent.

@Composable
fun ToggleAuthenticationMode(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
)

160




































fi
















With this in place, we can now uti ise this to co pose the but-
ton co tent. I side of our bu ton, we’re g ing to co pose a
Text co po able, se ting the co tent based on the provided
A the ti tio Mode.

@Composable
fun ToggleAuthenticationMode(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode
) {
Surface(
modifier = modifier
.padding(top = [Link]),
elevation = [Link]
) {
TextButton(
onClick = {

}
) {
Text(
text = stringResource(
if
(authenticationMode ==

AuthenticationMode.SIGN_IN) {

[Link].action_need_account
} else {

161
















[Link].action_already_have_account
}
)
)
}
}
}

F nally, we use need to handle the o Click cal back for our
Tex Bu ton. When the bu ton is clicked we want to tri ger an
event that will change the a the ti tion mode for the state of
our screen. - this will be an event in the form of Toggl Au-
thenti tio Mode, which we cr ated in an earl er se tion.
Again we’ll want to pass this event up to the pa ent, so we’ll
need to add a new lambda a g ment to our co po able func-
tion.

@Composable
fun ToggleAuthenticationMode(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
toggleAuthentication: () -> Unit
)

When this event is triggered, our Vie Mo el will ip the value


of our a the ti tion mode, a lo ing us to switch between the
sign in and sign up state. So this will o cur, we’ll go ahead and
tri ger this wit in the o Click han ler for our Tex Bu ton
co po able.

162
































fl








@Composable
fun ToggleAuthenticationMode(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
toggleAuthentication: () -> Unit
) {
Surface(
modifier = modifier
.padding(top = [Link]),
elevation = [Link]
) {
TextButton(
modifier = Modifier
.background(MaterialThem
[Link])
.padding([Link]),
onClick = {
toggleAuthentication()
}
) {
Text(
text = stringResource(
if
(authenticationMode ==

AuthenticationMode.SIGN_IN) {

[Link].action_need_account
} else {

163
[Link].action_already_have_account
}
)
)
}
}
}

Wit in our UI, this toggle bu ton was pushed right to the bot-
tom of the screen - this isn’t som thing that can be co gured
wit in the pro e ties of the pa ent Column co tai er.

164










fi
Co po ing the Toggle Bu ton
Now that our bu ton is i pl me ted, we can go ahead and
co pose it wit in our A the ti tio Form co po able.
We’ll need to start by adding a new a g ment to our A then-
ti tio Form co po able, this will be o Toggl Mode in
the form of a lambda fun tion.

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
email: String,
password: String,
completedPasswordRequirements:
List<PasswordRequirements>,
enableAuthentication: Boolean,
onEmailChanged: (email: String) ->
Unit,
onPasswordChanged: (password:
String) -> Unit,
onToggleMode: () -> Unit
)

We’ll then also need to hop over to our A the ti tio Con-
[Link] le to mod fy the co po tion of our A the ti a-
tio Form co po able. We’ll need to pass an a g ment for the
o Toggl Mode - for this, we’ll i pl ment a lambda fun tion
that will be used to tri ger handl Event. For this call, we’re
g ing to need to pass an A the ti tio Event, which we’ll

165









fi
















































do so in the form of the Toggl A thenti tio Mode type.
When this is triggered and handled by our Vie Mo el, the cur-
rent A the ti tio Mode will be toggled to the o po ite
value and emi ted to our UI.

// [Link]

AuthenticationForm(
modifier = [Link](),
email = [Link],
password =
[Link],
completedPasswordRequirements =
[Link]
,
authenticationMode =

[Link],
enableAuthentication =
[Link](),
onEmailChanged = {

handleEvent([Link]
nged(it))
},
onPasswordChanged = {

handleEvent([Link]
Changed(it))
},

166















onAuthenticate = {

handleEvent([Link]
cate)
},
onToggleMode = {
handleEvent(

[Link]
Mode)
}
)

Hea ing back over to our A the ti tio Form co po able,


we can now co pose our A the ti tio Bu ton u ing the
e is ing a the ti tio Mode re e ence, along with the now
provided o Toggl Mode lambda fun tion.

// [Link]

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
email: String,
password: String,
completedPasswordRequirements:
List<PasswordRequirements>,

167




























onEmailChanged: (email: String) ->
Unit,
onPasswordChanged: (password:
String) -> Unit,
onToggleMode: () -> Unit
) {
Column(
modifier = modifier,
horizontalAlignment =
[Link]
) {
EmailInput(
modifier =
[Link](),
email = email,
onEmailChanged =
onEmailChanged
) {

[Link]()
}
Spacer(modifier =
[Link]([Link]))
PasswordInput(
modifier =
[Link]()
.focusRequester(password
FocusRequester),
password = password,

168
onPasswordChanged =
onPasswordChanged,
onSubmitForm =
onAuthenticate
)
Spacer(modifier =
[Link]([Link]))

AnimatedVisibility(
visible = authenticationMode
==

AuthenticationMode.SIGN_UP
) {

PasswordRequirements(completedPasswordRe
quirements)
}

Spacer(modifier =
[Link]([Link]))

AuthenticationButton(
enableAuthentication =
enableAuthentication,
authenticationMode =
authenticationMode,
onAuthenticate =
onAuthenticate
)

169
ToggleAuthenticationMode(
modifier =
[Link](),
authenticationMode =
authenticationMode,
toggleAuthentication = {
onToggleMode()
}
)
}
}

We’ll n tice here now that our Toggl A thenti tio Mode
co po able is pressed against the bo tom of our A the tic-
tio Bu ton co po able.

170
















As per the design, we want the Toggl A thenti tio Mode
to be pushed against the bo tom of the pa ent co po able.
Between these bu tons we e se tially want a large amount of
white space - this white spice needs to ll the avai able space
between these two bu tons, which in e fect will push our toggle
bu ton to the bo tom of our UI. For this, we’re still g ing to use
the Spacer co po able for cr a ing space between these two
co po ables, with the a d tion of the weight mo er.

Spacer(modifier = [Link](1f))

A pl ing a weight of 1f to this Spacer will cause it to take up all


of the avai able height to it wit in the Column - which in this
case will be all of the space between the A the ti ate and
toggle bu tons. We are not a pl ing any weigh ing to any ot er
co po ables so that the r mai ing weight here does not need

171




























fi







fi­









to be di tri uted between mu tiple co po ables. If we were to
a sign some weigh ing to one of those bu tons also, we would
see a di fe ent re ult as the weight would b come di tri uted.
But b cause we are only a sig ing weight here to the Spacer
co po able, it takes the avai able space for i self.

// [Link]

@Composable
fun AuthenticationForm(
modifier: Modifier = Modifier,
authenticationMode:
AuthenticationMode,
email: String,
password: String,
completedPasswordRequirements:
List<PasswordRequirements>,
onEmailChanged: (email: String) ->
Unit,
onPasswordChanged: (password:
String) -> Unit,
onToggleMode: () -> Unit
) {
Column(
modifier = modifier,
horizontalAlignment =
[Link]
) {
EmailInput(

172





















modifier =
[Link](),
email = email,
onEmailChanged =
onEmailChanged
) {

[Link]()
}
Spacer(modifier =
[Link]([Link]))
PasswordInput(
modifier =
[Link]()
.focusRequester(password
FocusRequester),
password = password,
onPasswordChanged =
onPasswordChanged,
onSubmitForm =
onAuthenticate
)
Spacer(modifier =
[Link]([Link]))

AnimatedVisibility(
visible = authenticationMode
==

AuthenticationMode.SIGN_UP

173
) {

PasswordRequirements(completedPasswordRe
quirements)
}

Spacer(modifier =
[Link]([Link]))

AuthenticationButton(
enableAuthentication =
enableAuthentication,
authenticationMode =
authenticationMode,
onAuthenticate =
onAuthenticate
)

Spacer(modifier =
[Link](1f))

ToggleAuthenticationMode(
modifier =
[Link](),
authenticationMode =
authenticationMode,
toggleAuthentication = {
onToggleMode()
}
)

174
}
}

With this change, we can now see the toggle bu ton is pushed
to the bo tom of the pa ent co po able, via the u age of the
Spacer co po able with its a signed weight.

With these co po ables now a ded, our a the ti tion form is


fun tio ally co plete - o fe ing a way for users to both sign up
and sign in to our a pli a-
tion.

175























Di pla ing A the ti tion Er-
rors

With everything we’ve done pr v ously in this se tion, the user


can enter some cr de tials and pe form either a sign-in or
sign-up o e tion. While this o e tion can su ceed and a low
the user to co ti ue into our a pli tion, som times that re-
quest may fail. In these cases, we’ll want to r ect this state to
the user so they know som thing has gone wrong. Our a then-
ti tion state has an e ror pro erty wit in it, so we’ll be able to
use this pro erty to di play an alert di log to the user.

176
































fl





Wit in our A the ti tio State class there is an e ror
pro erty, which is used to d pict that an e ror has o curred
du ing the a the ti tion pr cess. While this e ror might be
set du ing the a the ti tion ow, we’re not cu rently uti ising
this wit in our UI to co m ni ate this to the user. To handle
this sce ario, we’re g ing to cr ate a new co po able, Au-
the ti tio Er o Di log, which is g ing to take this er-
ror value and co pose it wit in our UI.

// [Link]

@Composable
fun AuthenticationErrorDialog(
modifier: Modifier = Modifier,
error: String
)

Wit in this co po able fun tion, we’re then g ing to co pose


an Aler Di log. There are two Aler Di log co po able
fun tions avai able, we’re g ing to use the one with the fo low-
ing set of a g ments.

@Composable
fun AuthenticationErrorDialog(
modifier: Modifier = Modifier,
error: String,
dismissError: () -> Unit
) {
AlertDialog(
modifier = modifier,
onDismissRequest = {

177












































fl


















},
confirmButton = {

},
title = {

},
text = {

}
)
}

As we can see here, there are a co le tion of a g ments that we


need to provide to the co po able fun tion:

- mo er: this is o tio al, but a lows us to provide


co straints to be a plied to the co po able

- onDi mi Request: triggered when the di log is re-


que ted to be di missed, which we’ll need to use to
tri ger an u date to our state so that the di log is
not co posed

- co r Bu ton: a co po able that re re ents the


bu ton used to co rm the r quest of the di log,
with the i te tion to di miss it

- title: the co po able to be used as the title of the


di log

178








fi


fi­












fi





















- text: the co po able to be used for the co tent
body of the di log

With that in mind, let’s start buil ing out the co po ables for
each of these a g ments. We’ll start with the title, which we’ll
need to start by adding a new string to our strin [Link] re-
source le.

<string name="error_title">Whoops</
string>

Then u ing the stri R source co po able fun tion, we’ll


co pose a Text co po able provi ing this string for the text
a g ment. B cause we’re wor ing with a title here, we’ll over-
ride the d fault fon Size of the co po able and a sign a
value of 18sp - this is so that is styled la ger than the me sage
of the di log.

// [Link]

@Composable
fun AuthenticationErrorDialog(
modifier: Modifier = Modifier,
error: String
) {
AlertDialog(
modifier = modifier,
onDismissRequest = {

},
confirmButton = {

179




fi





























},
title = {
Text(
text = stringResource(
id =
[Link].error_title),
fontSize = [Link]
)
},
text = {

}
)
}

At this point, we can see this text b ing used for the title of our
di log.

180


Next, we’ll use a ot er Text co po able, but this time for the
text a g ment of the Aler Di log co po able. Here we will
simply provide the e ror that is passed to our co po able
fun tion for the text a g ment of the co po able.

// [Link]

@Composable
fun AuthenticationErrorDialog(
modifier: Modifier = Modifier,
error: String
) {
AlertDialog(
modifier = modifier,

181


















onDismissRequest = {

},
confirmButton = {

},
title = {
Text(
text = stringResource(
id =
[Link].error_title),
fontSize = [Link]
)
},
text = {
Text(
text = error
)
}
)
}

With this in place, we can now see the title of our Aler Dia-
log b ing a co pa ied by our e ror me sage.

182







Next, we need to co pose the bu ton that will be used to dis-
miss the Aler Di log - this will be provided u ing the but-
ton a g ment of our co po able. We’ll rst start by co pos-
ing a Tex Bu ton to be used for this a tion. This a lows us to
co pose a at bu ton that is vis ally re re e ted as some text.
The di fe ence with this, when co pared to a Text co pos-
able, is that the Tex Bu ton has the e pe ted touch ta get siz-
ing of an i trac able co po ent, ma ing it a ces ible to all of
our users.

There are two r quired a g ments for the Tex Bu ton -


the o Click cal back and the co tent, which is used to
provide the co po able body of our bu ton.

TextButton(
onClick = {

183








fl



























fi












}
) {

We’ll start by co po ing the body of our bu ton, for which we’ll
need to add a new r source to our strin [Link] le.

<string name="error_action">OK</string>

We can then use this to co pose a Text co po able for the


co tent of our bu ton.

TextButton(
onClick = {

}
) {
Text(text = stringResource(id =
[Link].error_action))
}

Next up we’ll need to add some a tion to our o Click call-


back. When our bu ton is clicked we’re simply g ing to want to
di miss the di log, so we’ll need to co m ni ate this a tion
back up to the pa ent so that our state can be u dated and in
turn, the di log will no longer be co posed. For this, we’ll
need to add a new lambda a g ment to our co po able func-
tion.

184
























fi




@Composable
fun AuthenticationErrorDialog(
modifier: Modifier = Modifier,
error: String,
dismissError: () -> Unit
)

With this lambda in place, we can now tri ger this from wit in
the o Click of our Tex Bu ton co po able.

// [Link]

@Composable
fun AuthenticationErrorDialog(
modifier: Modifier = Modifier,
error: String,
dismissError: () -> Unit
) {
AlertDialog(
modifier = modifier,
onDismissRequest = {

},
confirmButton = {
TextButton(
onClick = {
dismissError()
}
) {

185







Text(text =
stringResource(
id =
[Link].error_action))
}
},
title = {
Text(
text = stringResource(
id =
[Link].error_title),
fontSize = [Link]
)
},
text = {
Text(
text = error
)
}
)
}

At this point, we’ll now have a vis ally co plete e ror di log
that di plays the title, me sage and a tions to the user.

186







The last thing to do here is to add some i pl men tion to the
onDi mi Request lambda body of the Aler Di log. In the
pr v ous step, we a ded the di mi E ror a g ment to our
co po able fun tion, so we’ll now want to tri ger this wit in
the onDi mi Request lambda.

// [Link]

@Composable
fun AuthenticationErrorDialog(
modifier: Modifier = Modifier,
error: String,
dismissError: () -> Unit
) {

187


























AlertDialog(
modifier = modifier,
onDismissRequest = {
dismissError()
},
buttons = {
Box(
modifier = Modifier
.fillMaxWidth(),
contentAlignment =
[Link]
) {
TextButton(
onClick = {
dismissError()
}
) {
Text(text =
stringResource(
id =
[Link].error_action))
}
}
},
title = {
Text(
text = stringResource(
id =
[Link].error_title),
fontSize = [Link]

188
)
},
text = {
Text(
text = error
)
}
)
}

With the co po able in place, we’ll now want to co pose this


wit in our UI. One thing to note here is that we want to co di-
tio ally co pose this, so it will only be co posed when there is
an e ror present wit in our state. When pe for ing co po i-
tion, we can wrap the co po tion of the A the ti tion-
Er o Di log wit in a ko lin let block, a lo ing us to a sert
the nu la i ity of the e ror pro erty. This way the block will
only be run if e ror is not null, so our co po able will only
ever be co posed if it should be.

// [Link]

[Link]?.let { error
->
AuthenticationErrorDialog(
...
)
}

We can slot this into our A the ti tio Co tent co pos-


able, provi ing the e ror from our a the ti tion state for the
e ror a g ment, along with i pl men ing the di mi E ror

189





























































lambda. Wit in this lambda we’ll want to uti ise the handle-
Event lambda that is provided to our co po able fun tion, us-
ing the E ro ismissed A the ti tio Er or to tri ger
our state b ing u dated and r mo ing the e ror (and in turn,
the alert di log will no longer be co posed).

// [Link]

@Composable
fun AuthenticationContent(
modifier: Modifier = Modifier,
authenticationState:
AuthenticationState,
handleEvent: (event:
AuthenticationEvent) -> Unit
) {
Box(
modifier = modifier,
contentAlignment =
[Link]
) {
if
([Link]) {
CircularProgressIndicator()
} else {
AuthenticationForm(
modifier =
[Link](),
email =
[Link],

190






















password =
[Link],

completedPasswordRequirements =

[Link]
,
authenticationMode =

[Link],
enableAuthentication =

[Link](),
onEmailChanged = {
handleEvent(

[Link](it))
},
onPasswordChanged = {
handleEvent(

AuthenticationEvent
.PasswordCha
nged(it))
},
onAuthenticate = {
handleEvent(

[Link])
},

191
onToggleMode = {

handleEvent(AuthenticationEvent
.ToggleAuthentic
ationMode)
}
)

[Link]?.let { error
->

AuthenticationErrorDialog(
error = error,
dismissError = {
handleEvent(

[Link])
}
)
}
}
}
}

At this point we now have an Aler Di log that will be com-


posed wit in our UI whene er the state re re ents an e ror
state, a lo ing us to co m ni ate any a the ti tion e rors to
the user.

192

















193
Wra ping Up

Throug out this pr ject, we’ve now fully i pl me ted our au-
the ti tion fe ture - a lo ing our users to sign-up or sign-in to
our a pli tion, with the abi ity to toggle between the two au-
the ti tion modes. With this to gling, we’ve been able to ex-
plore the co po tion of di fe ent states and co po ents with-
in our UI, on top of the ma ag ment of state when it comes to
our a the ti tion form.

194

































With all of this in place, we’ll want to e sure these co po ents
r main fun tio al wit in our app. In the next chapter, we’re go-
ing to e plore wri ing aut mated UI tests for these co pos-
ables.

195











Testing the A then-
ti tion UI
Now that we’ve built our A the ti tion screen, we’re g ing to
take a look at how we can write tests for our co po ables.
We’re g ing to be wri ing some i str men tion tests u ing the
co pose ui-test-j nit pac age - a lo ing us to ver fy that our
co po ables are di played and fun tio ing as e pe ted.

B fore we can get sta ted with our tests, we’re g ing to
need to add a couple of test sp ci c d pen e cies to our pro-
ject:

androidTestImplementation
"[Link]:ui-test-
junit4:1.5.4"
debugImplementation
"[Link]:ui-test-
manifest:1.5.4"

We’re also g ing to need to add mocks to our test - this a lows
us to ea ily provide mock re e ences to any liste ers that are
provided to our co po able fun tions, a lo ing us to ea ily
ver fy they are triggered whene er e pe ted.

androidTestImplementation
"[Link]:mockito-
kotlin:4.0.0"

196


























f­i



























androidTestImplementation
"[Link]:mockito-android:4.5.1"

With these in place, we now have a cess to the r quired rules


and fun tio a ity that a low us to test our co po able UI.

Se ting up the test class


We’ll next start by cr a ing a new class, A the ti tio Test -
this class will be used to co tain the di fe ent tests that we’re
g ing to write.

class AuthenticationTest {

I side of this class, we now need to de ne a re e ence to the


Co p s Conten Tes Rule class - this is what we’re g ing to use
to set the co po able co tent on screen, a lo ing us to per-
form i te a tions and a se tions from wit in our tests.

@get:Rule
val composeTestRule =
createComposeRule()

When u ing this rule, we don’t need to sp cify any form of


acti ity for our co po ables to be launched in, the test rule will
handle that for us. So u ing this rule we will set the co po able
co tent to be co posed, the test will then launch a host acti ity
which will be used to co pose our provided co tent i side of.

197
































fi






















Tes ing the A the ti tion
Co po able
At the root of our fe ture is the A the ti tion co po able.
What makes this di fe ent from our lower level co po ables
(such as each UI co po ent co po able), is that this co pos-
able a lows us to i te act with el ments to tri ger state u dates
and r co po tion of our UI. This means that with the tests for
the A the ti tion co po able we can a sert not only that
the e pe ted UI co po ents are di played, but also that i ter-
a tions with them re ult in the e pe ted state changes.

Tes ing for A the ti tion Mode


changes
With that in mind, we’re g ing to start with some tests to e sure
that the e pe ted state is co posed when the UI is i te a ted
with. Wit in the A the ti tion co po able the user can
toggle between the sign-in + sign-up mode, so we’ll start with
some tests that a sert the co po tions that are d pen ant on
these di fe ent modes.

To start with, the title of the a the ti tion screen is de-


pen ing on the a the ti tion mode re re e ted wit in our
state. So here, we’re g ing to write some tests to a sert that the
co tent of the title co rectly r ects the state of our screen. By
d fault our screen is in the sign in state, so we’re g ing to write
a test to a sert that this is the case.

198






















































fl













































To write this rst test we’ll use the @Test a not tion and
cr ate a new fun tion to test that the Sign In title is di played by
d fault wit in our UI.

@Test
fun Sign_In_Title_Displayed_By_Default()
{

I side of this test, we’re g ing to need to start by se ting the


co po able co tent that is to be di played on screen for us to
a sert against. Here we’ll use the test rule that we pr v ously
de ned, along with its se Co tent fun tion. This fun tion
takes a co po able fun tion as an a g ment, a lo ing us to
de ne what is to be co posed on screen for our tests. B cause
we’re wan ing to test the A the ti tion Co po able that we
de ned in the pr v ous se tions of this chapter, we’ll go ahead
and pass the A the ti tion co po able fun tion for this
co po able a g ment.

@Test
fun Sign_In_Title_Displayed_By_Default()
{
[Link] {
Authentication()
}
}

While we aren’t yet pe for ing any a se tions, ru ning this test
will launch an acti ity that di plays the co tent of our A the tic-
tion co po able. With this now b ing di played, we can next

199







fi
fi
fi







fi




















































pe form the r quired a se tions to e sure that the Sign In title
is b ing di played wit in our co po able UI. We’ll do this by
uti ising the o Nod Wit Text fun tion from our test rule re er-
ence.

The o Nod Wit Text fun tion can be used to lo ate a


co po able that is di pla ing the text that we have provided to
the fun tion. Co po ables will be lo ated in the form of a se-
man ic node - b cause our co po ables are re re e ted via
s mantics, in our tests we are g ing to be lo a ing nodes wit in
our s man ic tree. In this case, this is done u ing the o Node-
Wit Text fun tion, which will r turn us with a S manti s-
Nod I te a tion re e ence to pe form a se tions against.

@Test
fun Sign_In_Title_Displayed_By_Default()
{
[Link] {
Authentication()
}

[Link](

[Link]
on()
.[Link](

[Link].label_sign_in_to_account)
)
}

200



























































For this test we want to a sert that this node is b ing di played
wit in our co posed UI, so we’re g ing to go ahead and uti ise
the a ser I Di played fun tion. This is one of the a se tions
avai able on the S manti Nod I te a tion class, a lo ing
us to a sert whet er this node is b ing di played on the screen.

@Test
fun Sign_In_Title_Displayed_By_Default()
{
[Link] {
Authentication()
}

[Link](

[Link]
on()
.[Link](

[Link].label_sign_in_to_account)
).assertIsDisplayed()
}

If you run this test wit in your IDE, you’ll not only see the UI
spin up i side of the co ne ted device / em la or, but the tests
should also be passing due to the r quired string b ing com-
posed wit in the UI.

Alon side the Sign In title b ing di played by the d fault


Sign In a the ti tion mode, the Need A count a count
should also be co posed wit in our UI. For these tests, we’re
g ing to be able to r use a lot of the same code, with the key

201



















































di fe ence here b ing that we need to a sert the co po tion
of the a tion_need_a count string as o posed to the sign in
title.

@Test
fun Need_Account_Displayed_By_Default()
{
[Link] {
Authentication()
}

composeTestRule
.onNodeWithText(

[Link]
on()
.[Link](

[Link].action_need_account)
)
.assertIsDisplayed()
}

With these two tests in place, we are now able to a sert that the
e pe ted co po ables for the Sign In A the ti tion Mode are
b ing co posed. But what ha pens if the user toggles the Au-
the ti tion Mode? In this sce ario, we know that to gling to
the Sign Up mode will change the title and toggle me sages -
so we’re g ing to write some tests to a sert these r co po i-
tions are triggered when this toggle o curs.

202


































For this, we’re g ing to start by tes ing that the title is
changed to the Sign Up title when the A the ti tion Mode
toggle is clicked. Just like the last test, we’ll start by se ting the
co tent to be co posed i side of our UI.

@Test
fun
Sign_Up_Title_Displayed_After_Toggled()
{
[Link] {
Authentication()
}
}

Now that we have some form of state b ing co posed into our
UI, we’re g ing to want to tri ger the title change - this will be
done by clic ing the A the ti tion Mode toggle bu ton, which
switches our screen from the sign-in to sign-up state. For this in-
te a tion we’re g ing to use the pe for Click fun tion, this is
a ge ture a tion that is avai able on the S manti Nod In-
te a tion class, a lo ing us to pe form a click ge ture on the
sp ci ed node.

@Test
fun
Sign_Up_Title_Displayed_After_Toggled()
{
[Link] {
Authentication()
}

203







fi

































composeTestRule
.onNodeWithText(

[Link]
on()
.[Link](

[Link].action_need_account)
).performClick()
}

Once this i te a tion has taken place, it is e pe ted that the au-
the ti tion mode will be toggled. When this o curs, the title of
the screen should switch to re re ent the Sign Up mode. So
with this test, we want to a sert that the e pe ted Sign Up title
is co posed. Here we’ll match the a se tion that we used for
the title in the pr v ous test, e cept this time we’ll check for our
[Link].l bel_sign_up for_a count String r source.

@Test
fun
Sign_Up_Title_Displayed_After_Toggled()
{
[Link] {
Authentication()
}

composeTestRule
.onNodeWithText(

204
























[Link]
on()
.[Link](

[Link].action_need_account)
).performClick()

composeTestRule
.onNodeWithText(

[Link]
on()
.[Link](

[Link].label_sign_up_for_account)
).assertIsDisplayed()
}

With this test in place, we’re now able to a sert that our title is
r co posed a cor ingly when the a the ti tion mode is
toggled. When this toggle o curs, we will also e pect the au-
the ti tion bu ton to be r co posed to r ect the Sign Up
a tion, as o posed to Sign In. For this, we’re g ing to start with
a test that looks very si i ar to the pr v ous - we’ll need to com-
pose our A the ti tion co po able, fo lowed by u ing
the pe for Click fun tion to i te act with the bu ton used to
toggle the a the ti tion mode.

@Test

205




































fl







fun
Sign_Up_Button_Displayed_After_Toggle()
{
[Link] {
Authentication()
}

composeTestRule
.onNodeWithText(

[Link]
on()
.[Link](

[Link].action_need_account)
).performClick()
}

Now, this is clicked, our a the ti tion bu ton should be show-


ing the Sign Up a tion. We’re g ing to want to a sert this to en-
sure this is the case, so we’ll need to start by adding a tag to the
a the ti tion bu ton co po able. We’ll cr ate a new o ject,
Tags, and de ne a new tag that can be a signed to our a then-
ti tion bu ton. We’re also g ing to be i te ac ing with the au-
the ti tion toggle across a couple of tests, so we’ll add a tag
for that too.

//[Link]

object Tags {

206










fi



















const val TAG_AUTHENTICATE_BUTTON =
"authenticate_button"
const val TAG_AUTHENTICATION_TOGGLE
= "authentication_mode_toggle"
}

With these tags de ned we can now use the tes Tag fun tion
to a sign this tag to our Bu ton co po able.

//[Link]

Button(
modifier =
[Link](TAG_AUTHENTICATE_BUTTON
),
...
)

We’ll also do the same for the Toggl A thenti tio Mode
Bu ton.

// [Link]

TextButton(
modifier =
[Link](TAG_AUTHENTICATION_TOGG
LE),
...
)

207


fi










With these tags in place, it can now be used to lo ate a node
wit in our co po able hie archy u ing the o Nod Wit ag
fun tion. On this node, we can then use the a ser Te t-
Equals fun tion to a sert that the text of this co po able is
equal to the co tent of the a tion_sign_up r source.

@Test
fun
Sign_Up_Button_Displayed_After_Toggle()
{
[Link] {
Authentication()
}

[Link](
TAG_AUTHENTICATION_TOGGLE
).performClick()

[Link](
TAG_AUTHENTICATE_BUTTON
).assertTextEquals(

[Link]
on()
.[Link]([Link].
action_sign_up)
)
}

With this test in place, we can now be ce tain that when the au-
the ti tion mode is toggled, the co tent di played wit in the

208




























a the ti tion bu ton no longer re re ents the sign in a tion,
but i stead the sign up a tion.

Aside from the title, we’re g ing to want to check that our
a the ti tion toggle now di plays the co tent that r ects the
change a the ti tion mode. For this, we’re g ing to again in-
te act with the toggle bu ton to toggle the a the ti tion
mode, and then we’ll want to a sert that the text of that a then-
ti tion bu ton re re ents the e pe ted value.

Here we’ll set up a test that will co pose our A the tic-
tion co po able, fo lowed by uti ising our TAG_AU-
THENTI TION TOGGLE tag to again lo ate the node that rep-
re ents the toggle bu ton. We can then use this re e ence to
pe form a tions and a se tions.

B cause we’re g ing to be pe for ing mu tiple i te ac-


tions on this node, we’ll use the ko lin a ply fun tion to chain
mu tiple o e tions. We’ll start by u ing the pe for Click
fun tion to pe form a click a tion on the bu ton, when this is
clicked the a the ti tion mode will be ipped from sign in to
sign up. When this o curs, the bu ton should di play the con-
tent of our a tion_already_have_a count r source. We’ll
use the a ser Te Equals fun tion to a sert that this is the
case.

@Test
fun
Already_Have_Account_Displayed_After_Tog
gle() {
[Link] {
Authentication()
}

209







































































fl
















fl








[Link](
TAG_AUTHENTICATION_TOGGLE
).apply {
performClick()
assertTextEquals(

[Link]
on()
.[Link](

[Link].action_already_have_account)
)
}
}

If this test passes, it means that the a the ti tion toggle but-
ton is b ing su ces fully r co posed with the co re pon ing
state for the sign-up mode that has been switched to. Ot er-
wise, it means the co po able has not been co posed with
the e pe ted state.

The title and a the ti tion bu ton are d na ic co pon-


ents in the sense that any co po tion should take into a count
the a the ti tion mode. Now we have these tests, we’re able
to a sert that the co tent of these co po ables co rectly re-
ects the cu rent a the ti tion mode wit in our state.

210
fl













































Tes ing the A the ti tion But-
ton
Aside from a ap ing to the a the ti tion mode, the A the tic-
tion bu ton is also co posed based on ot er parts of our
state. Based on the cu rent co tent that is i put into the email
and pas word text elds, the a the ti tion bu ton will be com-
posed with an e abled state. This means that if the email or
pas word in our state is empty, then the a the ti tion bu ton
will be di abled.

By d fault, our a the ti tion bu ton should be di abled,


as there will be no co tent in either of the email or pas word
pro e ties of our state. Si i ar to the pr v ous tests we’ve writ-
ten for our A the ti tion co po able, we’re g ing to cre-
ate a new test that co poses our A the ti tion co pos-
able, fo lowed by u ing the test rule to lo ate a node u ing our
pr v ously de ned TAG_A THENTI ATE_BU TON tag.

@Test
fun
Authentication_Button_Disabled_By_Defaul
t() {
[Link] {
Authentication()
}

composeTestRule
.onNodeWithTag(TAG_AUTHENTICATE_
BUTTON)
}

211














fi




fi





















































If this node has been lo ated, then we’re g ing to need to per-
form an a se tion to check that the bu ton is di abled - this is
b cause there the email and pas word state pro e ties are
cu rently empty. To pe form this a se tion we’re g ing to use
the a ser I No E abled fun tion. This S manti Matc er
will check that the s mantics for the lo tion node has the Se-
manti Pro e [Link] abled pro erty, mea ing that the
co po able is di abled.

@Test
fun
Authentication_Button_Disabled_By_Defaul
t() {
[Link] {
Authentication()
}

composeTestRule
.onNodeWithTag(TAG_AUTHENTICATE_
BUTTON)
.assertIsNotEnabled()
}

With this small test, we’ll now be able to a sert that by d fault,
the a the ti tion bu ton is di abled. On the ip side, when
those i put elds do have co tent, we want to a sert that the
A the ti tion bu ton is e abled. We’ll start wri ing a new test
here to a sert this co d tion.

@Test

212

















fi































fl











fun
Authentication_Button_Enabled_With_Valid
_Content() {
[Link] {
Authentication()
}
}

While we could use the A the ti tion state to pr load va ues


to be used for the email and pas word elds, I wanted to si u-
late user b h viour here - so we’re g ing to use the pe form-
Te I put fun tion to type some text into the sp ci ed text
eld. B fore we can i te act with our text elds in such a way,
we’re g ing to need to add tags for them so that the nodes can
be lo ated from our UI.

const val TAG_INPUT_EMAIL =


"input_email"
const val TAG_INPUT_PASSWORD =
"input_password"

We’ll then a sign these tags to each of the email and pas word
i put elds u ing the tes Tag mo er.

// [Link]

TextField(
modifier =
[Link](TAG_INPUT_EMAIL),
...
)

213
fi




fi


















fi­

fi
fi


fi




// [Link]

TextField(
modifier =
[Link](TAG_INPUT_PASSWORD),
...
)

With these tags in place, we can now uti ise the pe for Te In-
put fun tion to i put a provided string into the co re pon ing
nodes.

[Link](
TAG_INPUT_EMAIL
).performTextInput("contact@[Link]
emy")

[Link](
TAG_INPUT_PASSWORD
).performTextInput("password")

After our state has been co posed, we’ll use both the email
and pas word i put elds to pe form text i put - gi ing both of
these elds va id co tent that would a low the user to a then-
ti ate against. Once these calls are in place, we can again loc-
ate the A the ti tion Bu ton u ing its tag but this time a sert
that it is e abled u ing the a ser I E abled fun tion. We
pr v ously used the a ser I No E abled fun tion, the key
di fe ence here is that a ser I E abled is chec ing that the

214





fi












fi

































S manti Pro e [Link] abled s man ic pro erty is not
present on the sp ci c co po able.

@Test
fun
Authentication_Button_Enabled_With_Valid
_Content() {
[Link] {
Authentication()
}

[Link](
TAG_INPUT_EMAIL
).performTextInput("contact@compose.
academy")

[Link](
TAG_INPUT_PASSWORD
).performTextInput("password")

[Link](
TAG_AUTHENTICATE_BUTTON
).assertIsEnabled()
}

B cause the email and pas word elds have va id co tent, the
a the ti tion bu ton, in this case, should be e abled - which
our test should now be a ser ing for us.

Some fu ther tes ing here could i clude r mo ing text from
the i put elds and a ser ing that our a the ti tion bu ton is

215









fi






f­i









fi













di abled from r co po tion. At this point, the in tial test for
the di abled state, fo lowed by the e able state serves as a
mi i al r quir ment for our tes ing - but feel free to e plore
fu ther co e age here!

Tes ing a the ti tion e rors


Now that we’ve pe formed a se tions on the co tent that is
used to a the ti ate the user, we can look at the next stages in
the a pli tion ow. While the user might be su ces fully au-
the ti ated and move to the next screen, that isn’t a ways g ing
to be the case - to cover these sce ar os, we a ded a di log
co po able to be di played when an e ror o curs du ing au-
the ti tion.

To a sert that this di log is di played in the co rect sce ari-


os, we’re g ing to go ahead and start by tes ing that the di log
is not di played by d fault. This will a low us to e sure that
users are not g ing to be shown the e ror di log when an e ror
hasn’t happened.

@Test
fun
Error_Alert_Not_Displayed_By_Default() {

So that we can try to lo ate the node that re re ents our alert
di log, we’re g ing to de ne a ot er tag.

216





























fl










fi





































const val TAG_ERROR_ALERT =
"error_alert"

We’ll then a sign this tag to our Aler Di log co po able u ing
the tes Tag mo er.

AlertDialog(
modifier =
[Link](TAG_ERROR_ALERT),
...
)

With this tag in place, we can now a tempt to lo ate the node
and then pe form a se tions against it. To do this we’ll use the
o Nod Wit ag fun tion, fo lowed by u ing a ser Doe No-
E ist to a sert that a node with this tag does not e ist -
mea ing that the e ror di log does not cu rently e ist wit in
our UI.

@Test
fun
Error_Alert_Not_Displayed_By_Default() {
[Link] {
Authentication()
}

[Link](
TAG_ERROR_ALERT
).assertDoesNotExist()
}

217













fi­





















Now that we know our alert di log is not sho ing when an er-
ror doesn’t e ist, we’re g ing to want to test the ip side of this
and a sert that the e ror di log is di played when an e ror has
o curred. We’ll start here by d ing a new test fun tion to rep-
re ent this test case.

@Test
fun Error_Alert_Displayed_After_Error()
{

Next, we need to co pose our state so that the e ror di log is


di played - we’ll do this by co po ing our A the ti tion-
Co tent and provi ing an A the ti tio State re e ence
that has an e ror value a signed to it.

[Link] {
AuthenticationContent(
AuthenticationState(
error = "Some error"
)
) { }
}

B cause our state now has an e ror value, an alert di log will be
co posed wit in our UI. Ho ever, we’re g ing to want to nal-
ise our test and a sert that this is the case. We’ll wrap up this
test by lo a ing the alert di log u ing the tag we pr v ously as-
signed to the Aler Di log co po able, fo lowed by u ing

218



























fi















fl












fi­

the a ser I Di played fun tion to ver fy that the alert dia-
log has been co posed wit in our UI.

@Test
fun Error_Alert_Displayed_After_Error()
{
[Link] {
AuthenticationContent(
AuthenticationState(
error = "Some error"
)
) { }
}

[Link](
TAG_ERROR_ALERT
).assertIsDisplayed()
}

Tes ing the loa ing state


When the A the ti tion Bu ton is clicked, the a the ti tion
pr cess is triggered - in this sce ario we would likely be mak-
ing a ne work r quest, di pla ing a pr gress di log on-screen
in the pr cess. Du ing these state changes, we show and hide a
large amount of the UI co po ents, so we want to be sure that
these state changes re ult in the e pe ted UI co d tions. We’ll

219




































write a couple more tests to e sure that these changes b have
as e pe ted.

So that we can pe form a se tions against the pr gress in-


di a or, we’ll de ne a new tag and a sign it to our Ci c lar-
Pr gres I di a or co po able u ing the tes Tag mo -
er.

// [Link]

object Tags {
const val TAG_PROGRESS = "progress"
}

CircularProgressIndicator(
modifier =
[Link](TAG_PROGRESS)
)

We’ll then go ahead and add a simple rst test that a serts our
pr gress i di a or is not co posed of the d fault state of our
UI. Here we use the o Nod Wit ag fun tion to lo ate our
node u ing the sp ci ed tag, fo lowed by a ser ing that the
node does not e ist u ing the a ser Doe N E ist fun tion.

@Test
fun Progress_Not_Displayed_By_Default()
{
[Link] {
Authentication()
}

220













fi





fi















fi


















fi
[Link](
TAG_PROGRESS
).assertDoesNotExist()
}

Now we’ve a se ted that our pr gress i di a or is not com-


posed with the d fault a the ti tion state, we can now write a
test to e sure that the loa ing state is r e ted in our com-
posed UI. For this we’ll go ahead and co pose our A then-
ti tio Co tent, provi ing a re e ence to the A the tic-
tio State class with the i Loa ing ag marked as true.

With this in place, we can co ti ue to lo ate the node that


re re ents our loa ing i di a or, fo lowed by pe for ing the
a se tion that it is di played wit in our co posed UI.

@Test
fun Progress_Displayed_While_Loading() {
[Link] {
AuthenticationContent(

AuthenticationState(isLoading = true)
) { }
}

[Link](
TAG_PROGRESS
).assertIsDisplayed()
}

221


































fl





fl








After our o e tion has ished loa ing, we’ve har coded our
Vie Mo el to set an e ror state. When this ha pens, our UI
should hide the pr gress i di a or and di play the a the ti a-
tion form to the user. For us to a sert that this is the case, we’ll
need to tri ger the a the ti tion pr cess from our UI. To save
us e te ing text into the tex elds at runtime, we’ll co pose
our test UI with some pre-loaded state for the email a dress
and pas word va ues.

Once that’s done, we next need to pe form a click i te ac-


tion on our A the ti tion Bu ton - this will tri ger the a then-
ti tion pr cess and set the e ror state from our Vie Mo el.
When this ha pens, our pr gress i di a or should no longer
e ist in our UI. To e sure that this is the case, we’ll add an a ser-
tion by u ing the a ser Doe N E ist fun tion to check that
the pr gress i di a or does not e ist wit in our UI.

@Test
fun
Progress_Not_Displayed_After_Loading() {
[Link] {
AuthenticationContent(
authenticationState =
AuthenticationState(
email =
"contact@[Link]",
password = "password"
)
) { }
}

[Link](

222






























fi










fi






























TAG_AUTHENTICATE_BUTTON
).performClick()

[Link](
TAG_PROGRESS
).assertDoesNotExist()
}

Once we reach this state of our pr gress i di a or not b ing


di played (b cause the loa ing pr cess has been co pleted),
we’re g ing to want to e sure that the co tent of our UI has
been co posed again - this is the a the ti tion form, a lo ing
users to a tempt re-a the ti tion. If this didn’t di play again
wit in the UI, things would be quite broken for the users - so
we’ll write a test to a sert that this is the case.

@Test
fun Content_Displayed_After_Loading()

B fore we can pe form a se tions against the co tent area of


our UI, we’re g ing to need to de ne a new tag and a sign it to
the pa ent of our co tent area.

// [Link]

object Tags {
const val TAG_CONTENT = "content"
}

We’ll then need to set this tag on the co re pon ing co pos-
able wit in our A the ti tio Form.

223


























fi





















// [Link]

Column(
modifier =
[Link](TAG_CONTENT),
horizontalAlignment =
[Link]
)

While we could pe form a se tions against the i d vid al chil-


dren that already have tags a signed to them, this a proach al-
lows us to refer to the co tent area as a whole. Si i ar to the
pr v ous test, we can pe form the a the ti tion ow, fo lowed
by pe for ing the a se tion that the co tent area e ists wit in
our UI.

B cause there has been an e ror state loaded at this point,


there will be an alert di log co posed wit in our UI. For this
rea on, we use the e ists check i stead of a di played
check, this is b cause the alert di log will be covered a good
chunk of the co tent UI so we ca not a ways gua a tee that the
di played a se tion would be sa i ed.

@Test
fun Content_Displayed_After_Loading() {
[Link] {
Authentication()
}

[Link](
TAG_INPUT_EMAIL

224




























fi









fl










).performTextInput("contact@compose.
academy")

[Link](
TAG_INPUT_PASSWORD
).performTextInput("password")

[Link](
TAG_AUTHENTICATE_BUTTON
).performClick()

[Link](
TAG_CONTENT
).assertExists()
}

Tes ing the A the ti tion


Title
Now that we have tests in place that pe forms a se tions
against our A the ti tion co po able, we’re g ing to fo-
cus on wri ing some ne-grained tests for the i d vid al com-
po able fun tions that re re ent our i d vid al se ting items.
This a lows us to f cus on pe for ing a se tions on the b ha-
viour of the co po able fun tion i self, without the co cern of
our glo al state. We’ll start here by cr a ing a new test class,
A the ti tio Ti l Test, co u ing our Co p se-
Conten Tes Rule ready for use.

class AuthenticationTitleTest {

225


















fi














fi
























@get:Rule
val composeTestRule =
createComposeRule()

We’re g ing to start here by wri ing a test to a sert that the
co po able co rectly di plays the title co re pon ing title for
the A the ti tio Mode that is provided to it. The A then-
ti tio Title co tains the l gic that d picts which string
r source is used based on the A the ti tio Mode that is
provided to it. For this rea on, we’ll want to write these tests to
e sure this l gic is wor ing as e pe ted.

Here we’ll cr ate a new test fun tion, Sign_In_-


Title_Di played, where we will a sert that the Sign In title is
co posed when the A the ti tio Mo e.SIGN_IN is
provided for the a the ti tio Mode a g ment. We’ll start
here by co po ing an A the ti tio Title, passing the
Sign In mode for the a the ti tio Mode.

@Test
fun Sign_In_Title_Displayed() {
[Link] {
AuthenticationTitle(
authenticationMode =
AuthenticationMode.SIGN_IN
)
}
}

226
































































When the A the ti tio Title is co posed for the
SIGN_IN A the ti tio Mode, it is e pe ted that the la-
bel_sign_in_to_a count will be di played. We’ll need to
pe form an a se tion for this in our tests, so we’ll go ahead and
use the o Nod Wit Text fun tion on our test rule to lo ate a
node that has the text co tained wit in our la-
bel_sign_in_to_a count r source. We’ll then use the as-
ser I Di played fun tion to pe form the a se tion that this
co po able is di played.

@Test
fun Sign_In_Title_Displayed() {
[Link] {
AuthenticationTitle(
authenticationMode =
AuthenticationMode.SIGN_IN
)
}
composeTestRule
.onNodeWithText(

[Link]
on()
.[Link](

[Link].label_sign_in_to_account)
)
.assertIsDisplayed()
}

227





































If this test fails, it means that the e pe ted title is not b ing
co posed for the SIGN_IN A the ti tio Mode. On the
ip side, we’ll also want to a sert that the e pe ted text co tent
is co posed for the SIGN_UP A the ti tio Mode. This test
is g ing to look the same as the pr v ous, e cept this time we’ll
pass in A the ti tio Mo e.SIGN_UP when co po ing the
A the ti tio Title, as well as u ing the la-
bel_sign_up for_a count r source when pe for ing our
a se tion.

@Test
fun Sign_Up_Title_Displayed() {
[Link] {
AuthenticationTitle(
authenticationMode =
AuthenticationMode.SIGN_UP
)
}
composeTestRule
.onNodeWithText(

[Link]
on()
.[Link](

[Link].label_sign_up_for_account)
)
.assertIsDisplayed()
}

228
fl












































With these tests in place, we can now be sure that the A then-
ti tio Title is u ing the e pe ted string r source du ing
co po tion, based on the A the ti tio Mode that is
provided to it.

Tes ing the A the ti tion


Bu ton
Alon side the A the ti tio Title b ing co posed
based on the A the ti tio Mode that is provided to it, the
A the ti tio Bu ton also b haves in the same way - we’ll
also want to write some tests to a sert this co po tion also.
We’ll start by cr a ing a new A the ti tio Bu to Test
class, se ting up the co pose rule in the pr cess.

class AuthenticationButtonTest {

@get:Rule
val composeTestRule =
createComposeRule()

Next, we’ll write the rst test wit in this test class, which will be
used to a sert that the Sign In a tion is di played wit in the but-
ton when e pe ted. For this we’ll need to co pose the Au-
the ti tio Bu ton, passing the A the ti tio Mod-
e.SIGN_IN value for the a the ti tio Mode a g ment.

@Test

229



























fi

























































fun Sign_In_Action_Displayed() {
[Link] {
AuthenticationButton(
enableAuthentication = true,
authenticationMode =
AuthenticationMode.SIGN_IN,
onAuthenticate = { }
)
}
}

Now that the A the ti tio Bu ton is g ing to be com-


posed in our test, we can pe form the r quired a se tions. We
already have the TAG_A THENTI ATE_BU TON tag a signed to
our co po able from some pr v ous tests that we wrote, so we
can use this to lo ate the r quired node. Once we’ve done that,
the a ser Te Equals can then be used to a sert that the ex-
pe ted text of the r trieved node matches the value that we
provide. Here we’ll provide the string value for our ac-
tion_sign_in r source, which re re ents the “Sign In” value
that is e pe ted to be di played when the A the ti tion-
Mo e.SIGN_IN is provided to the co po able.

@Test
fun Sign_In_Action_Displayed() {
[Link] {
AuthenticationButton(
enableAuthentication = true,
authenticationMode =
AuthenticationMode.SIGN_IN,
onAuthenticate = { }

230









































)
}
composeTestRule
.onNodeWithTag(TAG_AUTHENTICATE_
BUTTON)
.assertTextEquals(

[Link]
on()
.[Link]([Link]
ing.action_sign_in)
)
}

We’ll also want to a sert that the a tion_sign_up string is dis-


played when the A the ti tio Mo e.SIGN_UP is passed
to the co po able. We’ll write a co re pon ing test here which
will mostly match the pr v ous test we wrote, e cept we’ll pass
A the ti tio Mo e.SIGN_UP for the a the ti tion-
Mode a g ment, along with u ing the a tion_sign_up string
r source when pe for ing the a ser Te Equals a se tion.

@Test
fun Sign_Up_Action_Displayed() {
[Link] {
AuthenticationButton(
enableAuthentication = true,
authenticationMode =
AuthenticationMode.SIGN_UP,
onAuthenticate = { }
)

231







































}
composeTestRule
.onNodeWithTag(TAG_AUTHENTICATE_
BUTTON)
.assertTextEquals(

[Link]
on()
.[Link](

[Link].action_sign_up)
)
}

Alon side the A the ti tio Mode based a se tions that


we’ve pe formed above, the A the ti tio Bu ton also
takes an onA thenti ate a g ment. When this lambda is in-
voked by our A the ti tio Bu ton, the pa ent co pos-
able will use this to tri ger the a the ti tion mode - if this
broke, users would not be able to pe form a the ti tion with-
in our app. For this rea on, we’re g ing to write a test to a sert
that the lambda is i voked when e pe ted. Here we’re g ing to
pass in a mock lambda fun tion for the onA thenti ate a gu-
ment. This means that we can use this mock to ver fy that i ter-
a tions have taken place based off of co po able events.

@Test
fun Authenticate_Triggered() {
val onAuthenticate: () -> Unit =
mock()
[Link] {

232





















































AuthenticationButton(
enableAuthentication =
false,
authenticationMode =
AuthenticationMode.SIGN_UP,
onAuthenticate =
onAuthenticate
)
}
}

With the A the ti tio Bu ton b ing co posed, we’ll now


be able to r trieve the node that re re ents this a the ti tion
bu ton u ing the TAG_A THENTI ATE_BU TON tag. We’ll then
use the pe for Click fun tion to pe form a click a tion on
this node. When this click a tion is triggered, this is the point
that we would e pect the onA thenti ate to be i voked so
that the pa ent co po able can handle the a the ti tion
event. We can ver fy this wit in our test by u ing mockito and
its ver fy fun tion to a sert that the lambda has been i voked.
If this is the case, the test will su ceed - ot e wise, the lambda
not b ing triggered will mean that our ver tion will not be
sa i ed and the test will fail.

@Test
fun Authenticate_Triggered() {
val onAuthenticate: () -> Unit =
mock()
[Link] {
AuthenticationButton(

233



fi


































fi
















enableAuthentication =
false,
authenticationMode =
AuthenticationMode.SIGN_UP,
onAuthenticate =
onAuthenticate
)
}

composeTestRule
.onNodeWithTag(TAG_AUTHENTICATE_
BUTTON)
.performClick()

verify(onAuthenticate).invoke()
}

The A the ti tio Bu ton co po able also takes an en-


abl A thenti tio a g ment. It could also be b n cial to
write some tests to a sert the co po tion based on the value
of this a g ment - we already have some tests for the A then-
ti tion co po able that i volved the e abled state, so we
won’t cover that here!

Tes ing the A the ti tion


Mode Toggle
So far we’ve been wri ing tests for var ous co po ables that
uti ise the A the ti tio Mode from our state o ject. The
bu ton that is used to toggle this value also takes an A then-
ti tio Mode re e ence, this is also so that it can be com-

234





















































fi­
posed to di play the co re pon ing co tent for the provided
A the ti tio Mode. After se ting up a test class with a cor-
re pon ing test rule, we’ll cr ate a test fun tion that will be
used to a sert the a tion_need_a count r source text is dis-
played wit in our co po able.

class AuthenticationModeToggleTest {

@get:Rule
val composeTestRule =
createComposeRule()

@Test
fun Need_Account_Action_Displayed()
{

Wit in this test we’ll need to start by co po ing a Toggl Au-


thenti tio Mode, provi ing the a the ti tio Mode ar-
g ment in the form of the A the ti tio Mo e.SIGN_IN
value. When this SIGN_IN value is provided, we e pect that the
co re pon ing co tent is di played i side the bu ton - this is in
the form of the a tion_need_a count r source. After lo at-
ing the node for this co po able u ing the o Nod Wit T-
ag(TAG_A THENTI TION TOGGLE) fun tion call, we can as-
sert that this e pe ted text is di played via the use of the as-
ser Te Equals fun tion.

235








































































@Test
fun Need_Account_Action_Displayed() {
[Link] {
ToggleAuthenticationMode(
authenticationMode =
AuthenticationMode.SIGN_IN,
toggleAuthentication = { }
)
}
composeTestRule
.onNodeWithTag(TAG_AUTHENTICATIO
N_TOGGLE)
.assertTextEquals(

[Link]
on()
.[Link](

[Link].action_need_account)
)
}

We’ll next ip this around so that we can a sert that the e pec-
ted a tion_already_have_a count value is di played
when the A the ti tio Mo e.SIGN_UP value is provided
for the a the ti tio Mode a g ment. Our test here is go-
ing to look the same as above, aside from the tweak to the au-
the ti tio Mode a g ment that we pass, along with the
a tion_already_have_a count value that is now b ing
provided to the a ser Te Equals fun tion call.

236






fl


























@Test
fun
Already_Have_Account_Action_Displayed()
{
[Link] {
ToggleAuthenticationMode(
authenticationMode =
AuthenticationMode.SIGN_UP,
toggleAuthentication = { }
)
}
composeTestRule
.onNodeWithTag(TAG_AUTHENTICATIO
N_TOGGLE)
.assertTextEquals(

[Link]
on()
.[Link](

[Link].action_already_have_account)
)
}

With this test in place, we can now be ce tain that the passing
tests mean the provided a the ti tio Mode value is g ing
to di play the e pe ted text i side of our co po able.
Whene er the user clicks the bu ton that is di pla ing this text,
the lambda fun tion that is provided to the co po able should
be triggered - this is the toggl A thenti tion lambda. If
this for some rea on was not b ing triggered, the user would

237


























not be able to switch to the sign-up mode - so if a user does
not cu rently have an a count, they wouldn’t be able to cr ate
one. To e sure this r mains fun tio al, let’s write a quick test to
a sert that this event does o cur.

Wit in this next test, we’re g ing to pass in a mock lambda


fun tion for the toggl A thenti tion a g ment. This
means that we can use this mock to ver fy that i te a tions have
taken place based off of co po able events.

@Test
fun Toggle_Authentication_Triggered() {
val toggleAuthentication: () -> Unit
= mock()
[Link] {
ToggleAuthenticationMode(
authenticationMode =
AuthenticationMode.SIGN_UP,
toggleAuthentication =
toggleAuthentication
)
}
}

With the Toggl A thenti tio Mode b ing co posed,


we’ll now be able to use the r trieve the node that re re ents
this toggle bu ton u ing the TAG_A THENTI TION TOGGLE
tag. We’ll then use the pe for Click fun tion to pe form a
click a tion on this node. When this click a tion is triggered, this
is the point that we would e pect the toggl A thenti a-
tion to be i voked so that the pa ent co po able can handle
the a the ti tion event. We can ver fy this wit in our test by

238




























































u ing mockito and its ver fy fun tion to a sert that the
lambda has been i voked. If this is the case, the test will suc-
ceed - ot e wise, the lambda not b ing triggered will mean
that our ver tion will not be sa i ed and the test will fail.

@Test
fun Toggle_Authentication_Triggered() {
val toggleAuthentication: () -> Unit
= mock()
[Link] {
ToggleAuthenticationMode(
authenticationMode =
AuthenticationMode.SIGN_UP,
toggleAuthentication =
toggleAuthentication
)
}
composeTestRule
.onNodeWithTag(TAG_AUTHENTICATIO
N_TOGGLE)
.performClick()

verify(toggleAuthentication).invoke()
}

239



fi







fi



Tes ing the Email A dress In-
put
When it comes to the Emai I put co po able, an email ar-
g ment is used to provide the co tent that is to be di played
i side of the text eld. This is a very i por ant part of the au-
the ti tion ow, so we’ll want to write a test to e sure that this
provided value is di played i side of our co po able. To do
this we’ll need to start by se ting up a new test class, Emai In-
pu Test.

class EmailInputTest {

@get:Rule
val composeTestRule =
createComposeRule()

We’ll start here by cr a ing a new test, co po ing the Email-


I put co po able. We’ll provide empty i pl men tions for
the on mai Changed and o Focu Reque ted a g ments,
but will need to provide a string value for the email a g ment.
We’ll cr ate a string var able re e ence here, provi ing this to
our Emai I put co po able.

@Test
fun Email_Displayed() {
val email =
"contact@[Link]"

240













fl


fi





































[Link] {
EmailInput(
email = email,
onEmailChanged = { },
onNextClicked = { }
)
}
}

Next, we’ll need to a sert that this email value is di played in-
side of our co po able. In a pr v ous test, we de ned the
TAG_I PUT_ MAIL tag, so we’ll use this here to lo ate the
node that re re ents our email text eld. Once this node has
been lo ated we can uti ise the a ser Te Equals fun tion
to a sert that the text s man ic value of the node matches our
provided email var able.

@Test
fun Email_Displayed() {
val email =
"contact@[Link]"
[Link] {
EmailInput(
email = email,
onEmailChanged = { },
onNextClicked = { }
)
}
composeTestRule
.onNodeWithTag(TAG_INPUT_EMAIL)
.assertTextEquals(email)

241
















fi




fi


}

With this test in place, we can now be ce tain that a passing test
means the provided email value is g ing to be di played in-
side of our co po able. When the user enters co tent into the
text eld to u date this email value that is co ing from our
state, the lambda fun tion that is provided to the co po able
is triggered - this is the on mai Changed lambda. If this for
some rea on was not b ing triggered, the user would be un-
able to enter their email a dress into the text eld. To e sure
this r mains fun tion, let’s write a quick test to a sert that this
event does o cur.

As b fore, we’ll need to co pose the Emai I put com-


po able to pe form our a se tions against. We’ll need to
provide a string value for the email a g ment - we’ll cr ate a
var able re e ence for this so that we can a sert the lambda is
triggered with the e pe ted value. We’re also g ing to pass in
a mock lambda fun tion for the on mai Changed a g ment.
This means that we can use this mock to ver fy that i te a tions
have taken place based off of co po able events.

@Test
fun Email_Changed_Triggered() {
val onEmailChanged: (email: String)
-> Unit = mock()
val email =
"contact@[Link]"
[Link] {
EmailInput(
email = email,
onEmailChanged =
onEmailChanged,

242


fi

































fi















onNextClicked = { }
)
}
}

While we have provided this lambda to our co po able, we


now need to tri ger it so that we can ver fy the e pe ted b ha-
viour. To tri ger this lambda we need to enter some text into
the i put eld, which we can do so u ing the pe for Te In-
put on a sp ci ed node. We’re g ing to a pend some text
onto the e is ing i put, which we’ll store in a var able re e ence,
a pe de Text so that we can use this du ing the a se tion.
Here we’ll lo ate the i put eld node u ing our e is ing tag, fol-
lowed by i pu ting this co tent u ing the pe for Te I put
fun tion.

val appendedText = ".jetpack"


composeTestRule
.onNodeWithTag(TAG_INPUT_EMAIL)
.performTextInput(appendedText)

With this in place, we can now add the check to ver fy that the
lambda fun tion is called as e pe ted. When this is triggered,
we would e pect that the email value r turned here would
re re ent the e is ing co tent with the a d tion of the a pen-
de Text value. We can ver fy this wit in our test by u ing
mockito and its ver fy fun tion to a sert that the lambda has
been i voked with the e is ing value in the i put eld (email)
a pe ded with the value of a pe de Text. If this is the case,
the test will su ceed - ot e wise, the lambda not b ing

243











fi











fi








fi






























fi


















triggered will mean that our ver tion will not be sa i ed and
the test will fail.

@Test
fun Email_Changed_Triggered() {
val onEmailChanged: (email: String)
-> Unit = mock()
val email =
"contact@[Link]"
[Link] {
EmailInput(
email = email,
onEmailChanged =
onEmailChanged,
onNextClicked = { }
)
}
val appendedText = ".jetpack"
composeTestRule
.onNodeWithTag(TAG_INPUT_EMAIL)
.performTextInput(appendedText)

verify(onEmailChanged).invoke(email
+ appendedText)
}

244

fi




fi
Tes ing the Pas word I put
When it comes to the Pas wor I put co po able, a pass-
word a g ment is used to provide the co tent that is to be dis-
played i side of the text eld. This is a very i por ant part of
the a the ti tion ow, so we’ll want to write a test to e sure
that this provided value is di played i side of our co po able.
To do this we’ll need to start by se ting up a new test class,
Pas wor I pu Test.

class PasswordInputTest {

@get:Rule
val composeTestRule =
createComposeRule()

We’ll start here by cr a ing a new test, co po ing the Pass-


wor I put co po able. We’ll provide empty i pl men a-
tions for the o Pas wor Changed and o Don Clicked a gu-
ments, but will need to provide a string value for the pas word
a g ment. We’ll cr ate a string var able re e ence here, provid-
ing this to our Pas wor I put co po able.

@Test
fun Password_Displayed() {
val password = "password123"
[Link] {
PasswordInput(
password = password,

245




















fl






fi































onPasswordChanged = { },
onDoneClicked = { }
)
}
}

Next, we’ll need to a sert that this pas word value is in fact dis-
played i side of our co po able. In a pr v ous test, we de ned
the TAG_I PUT PAS WORD tag, so we’ll use this here to lo ate
the node that re re ents our pas word text eld. Once this
node has been lo ated we can uti ise the a ser Te Equals
fun tion to a sert that the text s man ic value of the node
matches our provided pas word var able.

@Test
fun Password_Displayed() {
val password = "password123"
[Link] {
PasswordInput(
password = password,
onPasswordChanged = { },
onDoneClicked = { }
)
}
composeTestRule
.onNodeWithTag(TAG_INPUT_PASSWOR
D)
.assertTextEquals(password)
}

246






















fi



fi

With this test in place, we can now be ce tain that a passing test
means the provided pas word value is g ing to be di played
i side of our co po able. When the user enters co tent into
the text eld to u date this pas word value that is co ing
from our state, the lambda fun tion that is provided to the com-
po able is triggered - this is the o Pas wor Changed lambda.
If this for some rea on was not b ing triggered, the user would
be u able to enter their pas word into the text eld. To e sure
this r mains fun tion, let’s write a quick test to a sert that this
event does o cur.

As b fore, we’ll need to co pose the Pas wor I put


co po able to pe form our a se tions against. We’ll need to
provide a string value for the pas word a g ment - we’ll cr ate
a var able re e ence for this so that we can a sert the lambda is
triggered with the e pe ted value. We’re also g ing to pass in
a mock lambda fun tion for the o Pas wor Changed a gu-
ment. This means that we can use this mock to ver fy that i ter-
a tions have taken place based off of co po able events.

@Test
fun Password_Changed_Triggered() {
val onEmailChanged: (email: String)
-> Unit = mock()
val password = "password123"
[Link] {
PasswordInput(
password = password,
onPasswordChanged = { },
onDoneClicked = { }
)
}

247









fi



































fi












}

While we have provided this lambda to our co po able, we


now need to tri ger it so that we can ver fy the e pe ted b ha-
viour. To tri ger this lambda we need to enter some text into
the i put eld, which we can do so u ing the pe for Te In-
put on a sp ci ed node. We’re g ing to a pend some text
onto the e is ing i put, which we’ll store in a var able re e ence,
a pe de Text so that we can use this du ing the a se tion.
Here we’ll lo ate the i put eld node u ing our e is ing tag, fol-
lowed by i pu ting this co tent u ing the pe for Te I put
fun tion.

val passwordText = "456"


composeTestRule
.onNodeWithTag(TAG_INPUT_PASSWORD)
.performTextInput(passwordText)

With this in place, we can now add the check to ver fy that the
lambda fun tion is called as e pe ted. When this is triggered,
we would e pect that the pas word value r turned here would
re re ent the e is ing co tent with the a d tion of the a pen-
de Text value. We can ver fy this wit in our test by u ing
mockito and its ver fy fun tion to a sert that the lambda has
been i voked with the e is ing value in the i put eld (pass-
word) a pe ded with the value of a pe de Text. If this is the
case, the test will su ceed - ot e wise, the lambda not b ing
triggered will mean that our ver tion will not be sa i ed and
the test will fail.

@Test
fun Password_Changed_Triggered() {

248










fi












fi








fi







fi



























fi









fi











val onEmailChanged: (email: String)
-> Unit = mock()
val password = "password123"
[Link] {
PasswordInput(
password = password,
onPasswordChanged = { },
onDoneClicked = { }
)
}
val passwordText = "456"
composeTestRule
.onNodeWithTag(TAG_INPUT_PASSWOR
D)
.performTextInput(passwordText)

verify(onEmailChanged).invoke(password +
passwordText)
}

When it comes to the Pas wor I put co po able, we i ple-


me ted the abi ity to toggle the vi i i ity of the pas word u ing
a vi i i ity toggle bu ton. We’re g ing to write a test to a sert
that the state of this is r e ted co rectly, based on the i ter al
state of the fun tion that is b ing used to ma age the pass-
word vi i i ity.

When it comes to tes ing this, we’ll just write a single test to
check that the vi i i ity toggle co po able r ects the e pec-
ted state. We’ll need to start here by adding a new tag to our
Tags o ject so that we can lo ate and i te act with the vi i i ity

249
















fl

















fl












co po able. We’ll end this tag with an u de score so that we
can a pend the cu rent boolean value of the toggle to the tag,
mea ing that we can lo ate the tag based on the e abled state
of the toggle.

// [Link]

object Tags {
...
const val TAG_PASSWORD_HIDDEN =
"password_hidden_"
}

Next, we’ll need to a sign this tag our co po able u ing the
tes Tag fun tion. When d ing this we’ll also a pend the cur-
rent value of our i Pas wor Hi den state, so that we can loc-
ate the node u ing this value. We do this as if the value is not
aligned as e pe ted, then the node won’t be found and the
tests will fail.

// [Link]

trailingIcon = {
Icon(
modifier =
[Link](TAG_PASSWORD_HIDDEN +
isPasswordHidden),
...
)
}

250
























With this in place, we can now start wor ing on the test. Here
we’ll b gin by co po ing the Pas wor I put with a string
value for the pas word a g ment.

@Test
fun Password_Toggled_Reflects_state() {
[Link] {
PasswordInput(
password = "password123",
onPasswordChanged = { },
onDoneClicked = { }
)
}
}

We’ll then want to lo ate the vi i i ity toggle co po able. By


d fault the vi i i ity ag will be true, si n f ing that the pass-
word is hi den. Here we’re g ing to lo ate the node with the
value of true a pe ded to the tag, click on it and then a sert
that the tag with the value of false a pe ded to the tag is dis-
played. Here we’re g ing to start by lo a ing the node for the
hi den state of our vi i i ity toggle co po able - we’ll need
this so that we can pe form a click i te a tion on the co pos-
able. Here we’ll use the TAG PAS WORD_HI DEN tag, a pend-
ing the value of true on the end to match the e pe ted co di-
tion of the state for the pas word vi i i ity.

@Test
fun Password_Toggled_Reflects_state() {
[Link] {
PasswordInput(

251











fl













































password = "password123",
onPasswordChanged = { },
onDoneClicked = { }
)
}
composeTestRule
.onNodeWithTag(TAG_PASSWORD_HIDD
EN + "true")
.performClick()
}

With this in place, we are not pe for ing a click i te a tion on


the co po able, this means that now the co po able state
should have r co posed the vi i i ity toggle co po able. This
means that now, a node with the TAG PAS WORD_HI DEN tag
for the vi ible state of the pas word should be vi ible. We can
a sert this here u ing the a ser I Di played fun tion on the
lo ated node.

@Test
fun Password_Toggled_Reflects_state() {
[Link] {
PasswordInput(
password = "password123",
onPasswordChanged = { },
onDoneClicked = { }
)
}
composeTestRule
.onNodeWithTag(TAG_PASSWORD_HIDD
EN + "true")

252






























.performClick()

composeTestRule
.onNodeWithTag(TAG_PASSWORD_HIDD
EN + "false")
.assertIsDisplayed()
}

While we could have a se a ate test here to a sert that the true
co d tion is di played, this test co ers both sce ar os. This is
b cause the rst o Nod Wit ag call will fail the test if the
node is not found - this will mean that the tag for the hi den
state of the vi i i ity toggle would not cu rently be b ing dis-
played on the screen. B cause this test r quires the hi den
state of the vi i i ity toggle to pe form the a ser I Dis-
played a se tion, we cover both sce ar os in a single test.

Tes ing the Pas word Re-


quir ments
While we’re ver f ing the entry of a pas word from the tests
above, our user is still r quired to enter a pas word that meets
the mi i um r quir ments that are de ned wit in our View-
Mo el. These r quir ments are co m ni ated to the user via
the Pas wor R quir ments co po able, with the co pos-
able co tai ing l gic to d pict the me sage to be di played
based on the provided r quir ment statuses. So here, we’ll
write some tests here to ver fy that this co po able is o e at-
ing as e pe ted.

// [Link]

253
















fi






































fi





















class PasswordRequirementsTest {

@get:Rule
val composeTestRule =
createComposeRule()

We’re rst g ing to write a test to a sert that each of the pass-
word r quir ments is di played as e pe ted. To keep things
simple here and avoid nee ing to write mu tiple test co di-
tions, we’re g ing to write a test that will a sign ra dom pass-
word r quir ments as sa i ed. This way we can a sert that ex-
pe ted r quir ments are di played as both sa i ed and u sat-
i ed.

We’re g ing to start here by r trie ing the list of avai able
r quir ments from our Pas wor R quir ment type, along
with ge ting a ra dom item from this list to be used as the sa is-
ed r quir ment.

val requirements =
[Link]().toList()
val satisfiedRequirement =
requirements[(0 until
[Link]()).random()]

We could use a ra dom nu ber of sa i ed r quir ments to


vary the nu ber between tests, but we’ll use a single one here
to keep things simple for e amples sake. We’ll next co pose a

254
fi


fi



fi

















fi














fi






fi








Pas wor R quir ments, provi ing a list for the sa i fied-
R quir ments a g ment that co sists of the ra dom r quire-
ment that we r trieved above, sa i fie R quir ment.

@Test
fun
Password_Requirements_Displayed_As_Not_S
atisfied() {
val requirements =
[Link]().toList()
val satisfiedRequirement =
requirements[(0 until
[Link]()).random()]

[Link] {
PasswordRequirements(
satisfiedRequirements =
listOf(satisfied)
)
}
}

When the Pas wor R quir ments is co posed, it should be


the case that the r quir ments are co posed based on the sat-
i ed r quir ments that are provided. To test this we’re g ing
to need to start by loo ing through each of the avai able
Pas wor R quir ment va ues:

[Link]().forEach {

255


fi





































}

Next, we’ll use each of their l bels, along with the provided
sa i fie R quir ment to build the string that we’re g ing
to a sert for. The Pas wor R quir ments co po able is
forma ting two di fe ent string re re en tions based on the
sa i ed state of each. If a r quir ment is marked as sa i ed,
then the pas word_r quir ment_sa i fied r source is
used to build a string for that r quir ment, ot e wise the
pas word_r quir ment_needed is used. Here for each of
the r quir ments in the loop, we’re g ing to r trieve the string
for the l bel of the r quir ment, along with buil ing a string
based on whet er the r quir ment in the loop matches the
sa i fie R quir ment that we co gured earl er in the test.

[Link]().forEach
{ requirement ->
val requirement =

[Link]
on()
.[Link]([Link])

val result = if (requirement ==


satisfiedRequirement) {

[Link]
on()
.[Link](

[Link].password_requirement_satisfied,

256





fi




































fi

















fi
requirement)
} else {

[Link]
on()
.[Link](

[Link].password_requirement_needed,
requirement)
}
}

Now, this string is b ing built, we can use this to lo ate a node
and pe form an a se tion to e sure that it is b ing di played
wit in the co po able.

[Link]().forEach
{ requirement ->
val requirement =

[Link]
on()
.[Link]([Link])
val result = if (requirement ==
satisfiedRequirement) {

[Link]
on()
.[Link](

257











[Link].password_requirement_satisfied,
requirement)
} else {

[Link]
on()
.[Link](

[Link].password_requirement_needed,
requirement)
}

composeTestRule
.onNodeWithText(result)
.assertIsDisplayed()
}

With this loop, our test is now loo ing through each of the
avai abl Pas wor R quir ment va ues and a ser ing that the
e pe ted r quir ment me sage is di played wit in the com-
po able.

@Test
fun
Password_Requirements_Displayed_With_Sta
te() {

val requirements =
[Link]().toList()

258


















val satisfied = requirements[(0
until 3).random()]

[Link] {
PasswordRequirements(
satisfiedRequirements =
listOf(satisfied)
)
}
[Link]().forEach
{
val requirement =

[Link]
on()
.[Link]([Link]
bel)
val result = if (it ==
satisfied) {

[Link]
on()
.[Link](

[Link].password_requirement_satisfied,
requirement)
} else {

[Link]
on()

259
.[Link](

[Link].password_requirement_needed,
requirement)
}

composeTestRule
.onNodeWithText(result)
.assertIsDisplayed()
}
}

Tes ing the E ror Di log


Wit in our co le tion of co po ables for the a the ti tion
screen, we also have the A the ti tio Er o Di log that
is used to di play e rors to the user. While this only fe tures two
a g ments that are used to di play and di miss the e ror, these
are key to the o e tion of the di log, so we’ll add some tests
to a sert that these o e ate as e pe ted. These tests will live in-
side of a new test class, A the ti tio Er o Di l Test.

class AuthenticationErrorDialogTest {

@get:Rule
val composeTestRule =
createComposeRule()

260















































}

We’ll start here by wri ing a test that will be used to a sert that
the provided e ror me sage is di played wit in our di log as
e pe ted. Here we’ll need to de ne a new test that will be used
to house this test l gic, co po ing an A the ti tio Err-
o Di log that will be co posed u ing the provided e ror
re e ence. With this co po tion in place, we can then use our
text rule to a sert that there is a node di played that has the ex-
act text b ing provided via the e ror a g ment.

@Test
fun Error_Displayed() {
val error = "This is an error"
[Link] {
AuthenticationErrorDialog(
error = error,
dismissError = { }
)
}
composeTestRule
.onNodeWithText(error)
.assertTextEquals(error)
}

When it comes to di mis ing the di log, the A the ti a-


tio Er o Di log co po able takes a di mi E ror a gu-
ment that is used to n t fy the pa ent co po able that dis-
missal has been r que ted and our state needs to be u dated.
If this was broken for some rea on, then the user wouldn’t be

261































fi




























able to di miss the di log and be u able to pe form a the tic-
tion.

Si i ar to ot er tests that we’ve wri ten for these co pos-


ables, we’re g ing to use mockito to provide a mock i ple-
men tion of our di mi E ror, fo lowed by ver f ing that the
lambda has been i voked when e pe ted. After co po ing
this A the ti tio Er o Di log and provi ing the re-
quired a g ments, we can tri ger the di missal by clic ing the
node the has the e ror_a tion string r source a signed to it.
When this is clicked, it is e pe ted that the di missal lambda
will be triggered. So here we’ll use the ver fy fun tion from
mockito to ver fy that the di mi E ror lambda has been
triggered as e pe ted.

@Test
fun Dismiss_triggered_from_action() {
val dismissError: () -> Unit =
mock()
[Link] {
AuthenticationErrorDialog(
error = "This is an error",
dismissError = dismissError
)
}
composeTestRule
.onNodeWithText(

[Link]
on()
.[Link]([Link]
ing.error_action)

262


























































)
.performClick()

verify(dismissError).invoke()
}

With all of these tests in place, we’ve covered a lot of di fe ent


cases that help to e sure our UI is wor ing as e pe ted. We’ve
not only tested that co po ables are b ing co posed based
on the i for tion that they are provided with, but also that
they triggered the e pe ted cal backs and tri ger state m nip-
l tions wit in our co po ables. While the tests here aren’t ex-
ten ive, we’ve been able to learn not only what o tions are
avai able to us while tes ing co po ables, but also the ap-
proaches that we can take when d ing so.

263





























You might also like