4. Write your first mutation
8m

Overview

We're familiar with queries, but when we want to actually change, insert, or delete data, we need to reach for a new tool: .

In this lesson, we will:

  • Implement a to log in with an email
  • Enable our app's login view and execute the

Building a mutation

A is an we use to change data on the server. In our application, the login mutation will create a session based on our email address.

Note: The way you log in to this particular server might differ from the way you log in with your own server. Login is often handled by middleware, or a layer totally separate from , like OAuth.

Also note that a typical authentication flow should require a password—but for this tutorial, anyone is allowed to book flights with a valid email address!

Let's take a look at the that exist in our app's schema.

Open up Sandbox, or expand the embedded Sandbox below.

Let's return to the Schema tab. Just below the Query tab, we'll find Mutation. Let's click on this type and scroll down to take a look at the login :

https://studio.apollographql.com/sandbox/schema

A screenshot of Sandbox's Schema page, highlighting the Mutation type and the login field

Click the play button on the right to open that in the Explorer tab. When it opens, click the plus sign next to login to add the :

https://studio.apollographql.com/sandbox/explorer

Sandbox Explorer opened to the Documentation tab, showing the login field and highlighting the plus button

Adding this prefills the Operation panel with the following syntax.

mutation Login {
login {
}
}

Notice the red error indicator that shows up in Explorer? This is because the type returned by the is User, which is is an object type: this means we need to choose at least one of the User type's for the to return. For our purposes, we only need the token , so add it by clicking the plus sign next to it.

You'll also notice that email wasn't automatically added as an even though it doesn't seem to have a default value: that's because email is of type String—without an exclamation point (!)—which remember, in , means that it's not required (although obviously you won't get too far without it).

Click the plus sign next to the email to add that argument.

https://studio.apollographql.com/sandbox/explorer

The Operation panel showing our updated query with the email variable

We'll also notice that Explorer has added a to your Variables section to match the login email.

Click the Submit Operation button to run the . Because we sent null for the email address, we'll get back null for the login:

https://studio.apollographql.com/sandbox/explorer

The Response panel reflecting a null response for our query with a null email

Now, replace null in the Variables section with an actual email address:

Variables panel
{
"email": "me@example.com"
}

Submit the , and this time you'll get an actual response:

https://studio.apollographql.com/sandbox/explorer

The Response panel reflecting a valid response for our query sent with an actual email

Next, copy the , either manually or using the three-dot menu's Copy operation option.

Add the mutation to the project

Now that your is working, add it to your project. Create a file named Login.graphql next to schema.graphqls and your other files and paste the contents of the :

app/src/main/graphql/Login.graphql
mutation Login($email: String!) {
login(email: $email) {
token
}
}

Note: We've also marked the email as non-nullable by adding ! to the end of the type, since we always want to pass a value for it.

Build your project to generate the LoginMutation class.

Connect the Submit button to your mutation

To navigate back to the previous screen after logging in, add a navigateBack lambda parameter to the Login composable:

app/src/main/kotlin/com/example/rocketreserver/Login.kt
@Composable
fun Login(navigateBack: () -> Unit) {

Again, it should be initialized in MainActivity:

app/src/main/kotlin/com/example/rocketreserver/MainActivity.kt
composable(route = NavigationDestinations.LOGIN) {
Login(
navigateBack = {
navController.popBackStack()
}
)
}

Go back to Login.kt and create a new function to execute the Login :

app/src/main/kotlin/com/example/rocketreserver/Login.kt
private suspend fun login(email: String): Boolean {
val response = apolloClient.mutation(LoginMutation(email = email)).execute()
return when {
response.exception != null -> {
Log.w("Login", "Failed to login", response.exception)
false
}
response.hasErrors() -> {
Log.w("Login", "Failed to login: ${response.errors?.get(0)?.message}")
false
}
response.data?.login?.token == null -> {
Log.w("Login", "Failed to login: no token returned by the backend")
false
}
else -> {
val token = response.data!!.login!!.token!!
Log.w("Login", "Setting token: $token")
TokenRepository.setToken(token)
true
}
}
}

The possible error cases are handled and a boolean is returned to indicate if the login was successful or not. If it was, the token is saved in the TokenRepository.

Note that the function is marked as suspend and so will need to be called from a coroutine. To do that, declare a scope in Login:

app/src/main/kotlin/com/example/rocketreserver/Login.kt
// Submit button
val scope = rememberCoroutineScope()
Button(

Then, in the onClick lambda, replace the TODO with a call to login(), and handle the result:

app/src/main/kotlin/com/example/rocketreserver/Login.kt
Button(
modifier = Modifier
.padding(top = 32.dp)
.fillMaxWidth(),
onClick = {
scope.launch {
val ok = login(email)
if (ok) navigateBack()
}
}
) {
Text(text = "Submit")
}

To improve the UX, add a loading indicator that will be shown while the login is in progress (let's also disable the button to avoid multiple clicks):

app/src/main/kotlin/com/example/rocketreserver/Login.kt
// Submit button
var loading by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Button(
modifier = Modifier
.padding(top = 32.dp)
.fillMaxWidth(),
enabled = !loading,
onClick = {
loading = true
scope.launch {
val ok = login(email)
loading = false
if (ok) navigateBack()
}
}
) {
if (loading) {
Loading()
} else {
Text(text = "Submit")
}
}

Test the login flow

Go to the details screen, tap Book, which takes you to the Login screen. Enter your email and tap Submit. You now have a token that allows you to authenticate your .

We're not doing anything with this token yet, so to verify that everything's working as expected, open logcat and look for a success message:

Logcat in Android Studio showing "Setting token:"

Task!

Up next

We've implemented our mechanism to "log in", but there's more work to do: in the next lesson, we'll learn how to authenticate with our login token.

Previous

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.