developers

Get Started with Android Authentication Using Kotlin: Part 2

Learn how to implement login, logout, and user profiles in Android apps using Kotlin and Auth0.

Sep 20, 202122 min read

In the previous section, you started an Android project that uses Auth0 for user login, logout, and reading and updating user metadata. You set up the project on both the Auth0 and app sides. In this section, you’ll complete the project and update it to ensure that it works in both portrait and landscape orientations.

Write the code

Everything you’ve done so far in this exercise is just a preamble. It’s now time to write the actual code! This is the biggest task in the exercise, so let’s do it in small steps.​

🛠 Move to the

app/java/com.example.login
folder and open the main activity’s file,
MainActivity.kt
. Its contents should look like this:

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Import the necessary libraries

🛠 Add the following

import
statements to the ones already in the file:

import androidx.core.view.isVisible
import com.auth0.android.Auth0
import com.auth0.android.authentication.AuthenticationAPIClient
import com.auth0.android.authentication.AuthenticationException
import com.auth0.android.callback.Callback
import com.auth0.android.management.ManagementException
import com.auth0.android.management.UsersAPIClient
import com.auth0.android.provider.WebAuthProvider
import com.auth0.android.result.Credentials
import com.auth0.android.result.UserProfile
import com.google.android.material.snackbar.Snackbar

Most of these

import
statements import classes from Auth0’s libraries. Here’s what those classes do:

  • AuthenticationAPIClient
    : Accesses the Auth0 Authentication API. The app uses this to retrieve the user’s profile information.
  • AuthenticationException
    : Defines errors and exceptions that may arise during authentication.
  • Callback
    : Defines an object containing “success” and “failure” callback functions that Auth0 should call after completing an API function.
  • ManagementException
    : Defines errors and exceptions that
    Callback
    objects may have to handle.
  • UsersAPIClient
    : Manages user information. The app uses this to retrieve the user’s profile information.
  • WebAuthProvider
    : Provides Auth0’s web page-based login to the app. The app uses this to log the user in via a login page in a web browser and log the user out.
  • Credentials
    : Stores the user’s credentials, including ID, access, and refresh tokens.
  • UserProfile
    : Stores the user’s profile information, including their user ID, names, email address, and metadata.

🛠 You’ll also need to import the

Activity
’s auto-generated view binding library, which will make it possible for your code to reference views in the layout (or, to put it more simply: access the onscreen widgets). Do this by adding the following
import
statement, replacing
{YOUR_PACKAGE_NAME_HERE}
with the name of your app’s package, which you’ll find on the first line of the file:

import {YOUR_PACKAGE_NAME_HERE}.databinding.ActivityMainBinding

The package name for my app is

com.example.login
, so my
import
statement looks like this:

import com.example.login.databinding.ActivityMainBinding

Add class properties

🛠 Add the following properties to

MainActivity
so that the start of the class looks like this:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    // Login/logout-related properties
    private lateinit var account: Auth0
    private var cachedCredentials: Credentials? = null
    private var cachedUserProfile: UserProfile? = null


    override fun onCreate(savedInstanceState: Bundle?) {
    
    ...

You’ll use the first property,

binding
, to access the widgets on the screen. The other three properties are related to logging in and out and are described in more detail below:

  • account
    : represents the app’s Auth0 account and is instantiated using the app’s client ID and the domain for the app’s Auth0 tenant. Let me make this clear: this isn’t the account of the user trying to log in, but the account of the developer or organization who is delegating the login/logout process to Auth0. In the case of this example, it’s your Auth0 developer account.
    account
    ’s value is set in
    onCreate()
    when the activity is instantiated.
  • cachedCredentials
    : contains the user’s credentials that are returned from Auth0 after a successful login. Its value should be
    null
    when the user is not logged in. When the user is logged in, it should reference an instance of
    Credentials
    . A
    Credentials
    instance has the following properties:
    • idToken
      : The ID token, which contains user information that the app can use to customize the user’s experience.
    • accessToken
      : The access token, which is a credential that allows the app to access the Auth0 API.
    • refreshToken
      : The refresh token, which can be used to request a new access token when the original access token expires, without requiring the user to re-authenticate.
    • type
      : The type of the received access token.
    • expiresAt
      : The date/time when the received access token expires.
    • scope
      : The scopes granted to the access token. I’ll explain what scopes are in the next section, The login method.
  • cachedUserProfile
    : holds the user’s profile information. Its value should be
    null
    when the user is not logged in. When the user is logged in, it should reference an instance of
    UserProfile
    . A
    UserProfile
    instance has the following properties:
    • email
      : The email address corresponding to the user account.
    • isEmailVerified
      :
      true
      if the user responded to the verification email sent by Auth0 after they registered themselves as a user.
    • name
      : The user’s full name.
    • givenName
      : The user’s given name, often referred to as their “first name” or “forename”.
    • familyName
      : The user’s family name, often referred to as their “last name” or “surname”.
    • nickname
      : The user’s nickname, sometimes referred to as thier “familiar name” or “moniker”.
    • PictureURL
      : The URL where the user’s picture can be retrieved.
    • createdAt
      : The creation date and time of the user’s account.

Update the
onCreate()
method

First, let’s fill out the

onCreate()
method that was automatically generated for the project and use it to initialize everything in the activity.

🛠 Update the

onCreate()
method so that it looks like this:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    account = Auth0(
        getString(R.string.com_auth0_client_id),
        getString(R.string.com_auth0_domain)
    )

    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    binding.buttonLogin.setOnClickListener { login() }
    binding.buttonLogout.setOnClickListener { logout() }
    binding.buttonGet.setOnClickListener { getUserMetadata() }
    binding.buttonSet.setOnClickListener { setUserMetadata() }
}

This method:

  • Defines the account object, which contains the necessary credentials to connect to your Auth0 account.
  • Creates a view binding object, which you’ll use to access the onscreen widgets.
  • Connects the Log in, Log out, Get, and Set buttons to the methods they should call when tapped.

Add the
login()
method

The next step is to implement the method that gets called when the user taps the Log In button.

🛠 Add this to the class after

onCreate()
:

private fun login() {
    WebAuthProvider
        .login(account)
        .withScheme(getString(R.string.com_auth0_scheme))
        .withScope(getString(R.string.login_scopes))
        .withAudience(getString(R.string.login_audience, getString(R.string.com_auth0_domain)))
        .start(this, object : Callback<Credentials, AuthenticationException> {

            override fun onFailure(exception: AuthenticationException) {
                showSnackBar(getString(R.string.login_failure_message, exception.getCode()))
            }

            override fun onSuccess(credentials: Credentials) {
                cachedCredentials = credentials
                showSnackBar(getString(R.string.login_success_message, credentials.accessToken))
                updateUI()
                showUserProfile()
            }
        })
}

login()
uses the Auth0 SDK’s
WebAuthProvider
class, which gives the app the ability to use Auth0’s authentication service. The
WebAuthProvider
methods that you’ll use most often are its
login()
and
logout()
methods.

Although this method is formatted to span several lines, it’s just a single line of code. The single line is made of a call to a chain of

WebAuthProvider
’s methods starting with
login()
. If you ignore all the comments and parameters, the method chain looks like this:

WebAuthProvider
    .login()
    .withScheme()
    .withScope()
    .withAudience()
    .start()

This is the Builder design pattern in action. From

login()
to
withAudience()
, each method in the chain takes an argument that provides additional information about the login, using that information to creates a
WebAuthProvider
object that it passes to the next method in the chain. The final method in the chain,
start()
, takes the resulting
WebAuthProvider
object as its argument and uses it to display the login page and define what should happen when the login succeeds and when it fails.

Let’s take a look at what each of the methods in the chain does.

login()
initiates the login process and specifies the Auth0 account used by the application.

withScheme()
specifies the scheme to use for the URL that Auth0 redirects to after a successful login. For web apps, the scheme is
http
or
https
. This value is arbitrary for native mobile apps, so we use
app
to make it clear to other developers and other people who may use the Auth0 settings for this app that the redirect is not to a web page.

withScope()
specifies which sets of user data the app is authorized to use if the user logs in successfully. The OpenID Connect and OAuth frameworks, on which Auth0’s authentication and authorization are based, use the term scope to represent the authorization to access user’s data and resources. The method takes a space-delimited string as its argument, where each “word” in the string specifies a different scope. The string used in this app contains these scopes:

  • openid
    : Indicates that application that uses OpenID Connect for authentication. This is the only required scope; all other scopes are optional.
  • profile
    : Authorizes the application to access basic user profile information, including first name, surname, nickname, their photo or avatar, and so on.
  • email
    : Authorizes the application to access the user’s email address.
  • read:current_user
    : Authorizes the application with read-only access to the
    current_user
    claim.
  • update:current_user_metadata
    : Authorizes the application with read and write access to the
    current_user_metadata
    claim. This scope allows us to get and set the
    country
    value in the user’s metadata.

withAudience()
specifies the URL that the app will use to connect to Auth0’s login service. This URL is constructed using the domain of the Auth0 tenant used by the app and the endpoint for the Auth0 authentication API.

start()
takes the
WebAuthProvider
object constructed by all the previous methods in the chain and opens the browser window to display the login page. It takes two parameters: a context (a reference to the
Activity
that’s initiating the browser window) and an anonymous object with two callback methods:

  • onFailure()
    : Defines what should happen if the user returns from the browser login screen without successfully logging in. This typically happens when the user closes the browser login screen or taps the “back” button while on that screen. The app displays a
    SnackBar
    that notifies the user that login failed, followed by an error code.
  • onSuccess()
    : Defines what should happen if the user returns from the browser login screen after successfully logging in. The app processes the successful response, displays a
    SnackBar
    notifying the user that login was successful, and updates the UI to its “logged in” state.

Add the
logout()
method

You’ve probably guessed that if there’s a

login()
method that’s called when the user presses the Log In button, there must also be a
logout()
method that’s called when the user presses the Log Out button.

🛠 Add this method to the class after

login()
:

private fun logout() {
    WebAuthProvider
        .logout(account)
        .withScheme(getString(R.string.com_auth0_scheme))
        .start(this, object : Callback<Void?, AuthenticationException> {

            override fun onFailure(exception: AuthenticationException) {
                updateUI()
                showSnackBar(getString(R.string.general_failure_with_exception_code,
                    exception.getCode()))
            }

            override fun onSuccess(payload: Void?) {
                cachedCredentials = null
                cachedUserProfile = null
                updateUI()
            }

        })
}

As with

login()
,
logout()
also uses the Auth0 SDK’s
WebAuthProvider
class and is a one-liner that uses the Builder pattern. This time, that one line calls a shorter chain of
WebAuthProvider
’s methods starting with
logout()
. If you ignore all the parameters, the method chain looks like this:

WebAuthProvider
    .logout()
    .withScheme()
    .start()

logout()
initiates the logout process and specifies the Auth0 account used by the application, which should be the same account as the one used to log in.

withScheme()
specifies the scheme to use for the URL that Auth0 redirects to after successful logout. This should be the same scheme as the one used to log in.

start()
takes the
WebAuthProvider
object constructed by all the previous methods in the chain to log the user out. It takes two parameters: a context (a reference to the
Activity
that’s initiating the logout process) and an anonymous object with two callback methods:

  • onFailure()
    : Defines what should happen when the logout process fails. This rarely happens and usually indicates a network or server issue. In this example, the app updates the UI (which remains in the “logged in” state) and displays a
    SnackBar
    that notifies the user that logout failed, followed by an error code.
  • onSuccess()
    : Defines what should happen when the logout process succeeds. In this example, the app destroys its local copies of the user’s credentials and profile and updates the UI to its “logged out” state.

Add the
showUserProfile()
method

Every Auth0 user has a user profile associated with their account. The user profile contains the following basic information about the user:

  • Names: The user’s full name, given name, surname, and nickname
  • Email info: The user’s email address, and whether it was verified
  • Picture: The location of an image that identifies the user
  • Creation date: The date and time when the user’s account was created

When the user successfully logs in, the app should display their name and email onscreen. It does so by calling

showUserProfile()
immediately after a successful login.

🛠 Add this method to the class after

logout()
:

private fun showUserProfile() {
    // Guard against showing the profile when no user is logged in
    if (cachedCredentials == null) {
        return
    }

    val client = AuthenticationAPIClient(account)
    client
        .userInfo(cachedCredentials!!.accessToken!!)
        .start(object : Callback<UserProfile, AuthenticationException> {

            override fun onFailure(exception: AuthenticationException) {
                showSnackBar(getString(R.string.general_failure_with_exception_code,
                    exception.getCode()))
            }

            override fun onSuccess(profile: UserProfile) {
                cachedUserProfile = profile
                updateUI()
            }

        })
}

This method is the final task performed by the

onSuccess()
callback method in
loginWithBrowser()
. It initializes the
cachedUserProfile
property, which contains the user’s profile information.

As a precaution, it returns immediately if the

cachedCredentials
property is
null
, which implies that no user is logged in, and therefore there isn’t any user profile to show.

In order to get this information, it does the following:

  • It creates an instance of
    AuthenticationAPIClient
    , which retrieves Auth0 account information. Like the login and logout methods, this also uses the Builder pattern.
  • It uses
    AuthenticationAPIClient
    ’s
    userInfo()
    method to specify that we want to retrieve user profile information from Auth0. This method requires a valid access token, which it extracts from the
    cachedCredentials
    property.
  • Finally, it defines callback methods for the cases where it failed and succeeded in retrieving the user profile information from Auth0. If the retrieval was successful, the profile information is stored in
    cachedUserProfile
    , and the UI is updated to display the user’s name and email address.

Add the
getUserMetadata()
and
setUserMetadata()
methods

The user profile contains information that generally applies to every user account regardless of the type of application it’s being used for — name, email, photo, and date/time created. While this is necessary information, it’s probably not all the user information that you want to store in their profile.

That what the user metadata is for. Think of it as a key-value store where you can place additional user information that isn’t covered by the user profile. In this app, the user metadata will store just one additional piece of user information — their country — and it will allow the user to retrieve and update this information.

🛠 Add the following to the class after

showUserProfile()
:

private fun getUserMetadata() {
    // Guard against getting the metadata when no user is logged in
    if (cachedCredentials == null) {
        return
    }

    val usersClient = UsersAPIClient(account, cachedCredentials!!.accessToken!!)

    usersClient
        .getProfile(cachedUserProfile!!.getId()!!)
        .start(object : Callback<UserProfile, ManagementException> {

            override fun onFailure(exception: ManagementException) {
                showSnackBar(getString(R.string.general_failure_with_exception_code,
                    exception.getCode()))
            }

            override fun onSuccess(userProfile: UserProfile) {
                cachedUserProfile = userProfile
                updateUI()

                val country = userProfile.getUserMetadata()["country"] as String?
                binding.edittextCountry.setText(country)
            }

        })
}

private fun setUserMetadata() {
    // Guard against getting the metadata when no user is logged in
    if (cachedCredentials == null) {
        return
    }

    val usersClient = UsersAPIClient(account, cachedCredentials!!.accessToken!!)
    val metadata = mapOf("country" to binding.edittextCountry.text.toString())

    usersClient
        .updateMetadata(cachedUserProfile!!.getId()!!, metadata)
        .start(object : Callback<UserProfile, ManagementException> {

            override fun onFailure(exception: ManagementException) {
                showSnackBar(getString(R.string.general_failure_with_exception_code,
                    exception.getCode()))
            }

            override fun onSuccess(profile: UserProfile) {
                cachedUserProfile = profile
                updateUI()

                showSnackBar(getString(R.string.general_success_message))
            }

        })
}

While

showUserProfile()
uses an instance of
AuthenticationAPIClient
to get the user profile information,
getUserMetadata()
and
setUserMetadata()
use a different object type:
UsersAPIClient
.

Unlike

AuthenticationAPIClient
, which needs only an
Auth0
account object to be instantiated, you need both an
Auth0
account object and an access token to instantiate a
UsersAPIClient
object.

getUserMetadata()
specifies the user profile using
UsersAPIClient
’s
getProfile()
method and the ID of the user, followed by the
start()
method to attempt to get the user profile and define callback methods for failure and success.

getUserMetadata()
’s
onSuccess()
callback is almost the same as
showUserProfile()
’s — it just has these two additional lines that extract the
country
value from the user’s metadata and display it onscreen:

val country = userProfile.getUserMetadata()["country"] as String?
binding.edittextCountry.setText(country)

setUserMetadata()
defines a
Map
with a single key-value pair, where the key is the string
country
and the corresponding value is the contents of the
EditText
where the user enters the name of their country. It then passes that
Map
along with the user’s ID to
UsersAPIClient
’s
updateMetadata()
method to specify the change to be made, followed by the
start()
method to initiate the update and define callback methods for failure and success.

setUserMetadata()
’s
onSuccess()
callback is almost the same as
getUserMetadata()
’s — but instead of updating the
EditText
where the user enters the name of their country, it simply displays a
SnackBar
notifying the user that it was successful in updating the metadata.

Add the UI methods

The final step is to add the methods that present information to the user.

🛠 Add the following to the class after

getUserMetadata()
and
setUserMetadata()
:

private fun updateUI() {
    val isLoggedIn = cachedCredentials != null

    binding.textviewTitle.text = if (isLoggedIn) {
        getString(R.string.logged_in_title)
    } else {
        getString(R.string.logged_out_title)
    }
    binding.buttonLogin.isEnabled = !isLoggedIn
    binding.buttonLogout.isEnabled = isLoggedIn
    binding.linearlayoutMetadata.isVisible = isLoggedIn

    binding.textviewUserProfile.isVisible = isLoggedIn

    val userName = cachedUserProfile?.name ?: ""
    val userEmail = cachedUserProfile?.email ?: ""
    binding.textviewUserProfile.text = getString(R.string.user_profile, userName, userEmail)

    if (!isLoggedIn) {
        binding.edittextCountry.setText("")
    }
}

private fun showSnackBar(text: String) {
    Snackbar
        .make(
        binding.root,
        text,
        Snackbar.LENGTH_LONG
    ).show()
}

If you’ve made it this far, I have great news for you: you’ve written all the code for the app!

See the App in Action

Run the app. You’ll see this:

The app’s screen when launched.

Tap the Log in button. The app will open a browser window that will display the login web page:

The Auth0 login web page.

Log in using the email address and password of the user account you created earlier. Since this is the first time the account has logged into the app, the Authorize App page will appear in the browser window:

The “Authorize App” screen. It says that the app is requesting access to your user account, particularly the “profile”, “current_user”, and “current_user_metadata” information.

This page is asking you to authorize the app to access the following information in your user account:

  • Profile: access to your profile and email
  • Current_user: read your current_user
  • Current_user_metadata: update your current_user_metadata

If these items sound familiar, it’s because you’ve seen them recently. They’re the names of the scopes in the

login_scopes
string in the
strings.xml
resource...

<string name="login_scopes">
    openid profile email read:current_user update:current_user_metadata
</string>

...which the call to

withScope()
in the
login()
method uses to specify which sets of user data the app is authorized to use. This additional step informs the user of this use of their data and gives them a choice to approve or decline.

Tap the Approve button. The browser will disappear, and you will return to the app, which will now look like this:

The app’s screen when the user is logged in.

Tap the Get button. If you’ve never edited the value of the

country
field in the user’s metadata, the metadata text box will display the hint text “Enter country”.

Enter a country’s name (or any other text if you like) into the text box and tap the Set button. You’ll see this:

The app’s screen, with the word “Morocco”, entered into the text field.

Two things just happened:

  1. If the user’s metadata already had a
    country
    field, its value changed to whatever text you entered. If the user’s metadata didn’t have a
    country
    field, a
    country
    field was added to the metadata, and its value was set to whatever text you entered.
  2. An Android pop-up message called a
    Snackbar
    appeared at the bottom of the screen with the message “Success!”

Log out of the app, then log in again. Tap the Get button. The app should fill the metadata text box with the value of the

country
metadata field.

Let’s look at the updated user metadata from the Auth0 side. In the Auth0 dashboard, go to the list of users. Do this by clicking User Management in the menu on the left side of the page, followed by clicking on Users. Select the user that is currently logged in on the app. You’ll be taken to that user’s Details page:

The Auth0 dashboard displaying the user’s “User Details” page. In the “user_metadata” field, a JSON object contains a single key-value pair with the key “country” and the value “Morocco”.

Scroll down to the Metadata section of the page. You’ll see an area labeled user_metadata. If you filled out the

EditText
in the app, you’ll see it in the JSON object there:

{
    "country": {WHATEVER_TEXT_YOU_ENTERED}
}

Each Auth0 user account can store two kinds of metadata:

  • User metadata, which is meant for data intentionally provided by and controlled by the user. It’s typically used for storing information that the user wants to share with the app, such as their address, contact information, preferences, and similar data. Only data that the user can read and edit should be stored here.
  • App metadata, which is meant for data provided by and controlled by the app. It’s typically used for storing information about the user that is primarily for the app’s use, such as the user’s roles, permissions, status, and similar data. There may be cases where the user can read this data, but you should not use app metadata to store data provided directly by the user.

While a detailed look at the user account pages is beyond the scope of this article, you should explore them to see the kinds of data and functionality that are associated with user accounts.

When Things Go Sideways

The expression “to go sideways” means “to go wrong” or “to not go according to plan.” In this section, I’ll show you what can go wrong when you’re writing an Android app and fail to account for what happens when the user turns their phone on its side.

Inducing app amnesia

Run the app and log in. You should see this:

The app’s screen when the user is logged in, in portrait orientation.

Rotate your device to landscape orientation:

The app screen viewed in landscape orientation. It has taken on the appearance of just having been launched.

Notice what happened:

  • The greeting text changed from “You’re logged in.” to “Welcome to the app!”, which is its state when the app is launched.
  • The buttons have also been returned to their initial state: Log in is enabled, and Log out is disabled.

It appears as if turning your device on its side has given your app amnesia and returned it to its initial state. What happened?

Configuration changes and reloading

To use a developer cliché: this isn’t a bug, but a feature.

Whenever it detects a configuration change, Android’s default response is to reload the current activity. There are many configuration changes, such as language, keyboard availability, or the change you made just now: screen orientation. Reloading an activity when a configuration change happens makes it run its initialization methods again and reload resources, allowing it to set itself up for the new configuration.

This behavior was designed for applications that present different interfaces in different orientations. YouTube is an example of such an app, which has different portrait and landscape UI setups:

The YouTube app, shown in both orientations. In portrait orientation, it shows the video, ratings, and comments. In landscape orientation, the video takes up the entire screen.

Changing the screen orientation reloaded the activity and restored the app to its initial state, but it did not change your “logged in” status. The

logout()
method was not called when you turned your device on its side. You’re still logged in!

You can confirm your “logged in” status by tapping the Log in button. Note that there wasn’t an intermediate step where you had to enter your email address and password — you were taken straight to the app:

The app screen, viewed in landscape orientation, showing the user logged in. The bottom portion of the screen is not in sight.

Note that that app never presented the login page. That’s because there wasn’t any need — you were still logged in. Let’s solve this problem.

Curing your app’s amnesia

You can instruct Android not to reload Activities in the event of specified configuration changes in the app’s manifest.

🛠 Open

AndroidManifest.xml
and change the
<activity>
tag to the following:

<activity
    android:name=".MainActivity"
    android:exported="true"
    android:configChanges="orientation|screenSize" >

This change adds the attribute

android:configChanges= “orientation|screenSize”
to
MainActivity
, which tells Android that the activity should not reload if the device orientation or screen size changes. Instead of reloading the activity, Android uses a callback method to notify the app of the change. The assumption is that you’ll handle those configuration changes yourself. Since we’ll ignore this callback, the configuration change has no effect.

Run the app and log in. Change the screen orientation, going from portrait to landscape and back. It no longer causes the activity to reload.

Conclusion

You’ve just built a simple app that features basic username/password authentication — the ability to identify a known user. In addition to log a user in and out, you can also retrieve the information in their profile, and read and update their metadata.

You can find the code for the complete project for this article in this repository on the Auth0 Blog Samples GitHub account. The only change that you’ll need to make in order to run it is to enter your app’s client ID and tenant’s domain in the

auth0.xml
resource file.

This is the first in a new series of articles on Android development with Auth0. Future articles will cover new developments in Android 12 and Android Studio, as well as deeper dives into authentication and authorization with Android and Auth0 and alternatives to the standard username-and-password approach. Watch this space!

Next Step: Read the preview part hereI ran into an issue