Building A Simple Login Screen With SharedPreferences
A login screen is a useful first Android project because it brings together layout design, input validation, button events and local data storage. For a small learning app, SharedPreferences provides a straightforward way to remember whether a user has signed in before.
This example uses Kotlin and Android Studio. It creates a simple form with an email field, a password field, a login button and a logout action. The app stores a small sign-in state on the device, allowing the user to reopen the app without entering the same details each time.
The approach suits practice projects, prototypes and offline demonstrations. It is not a replacement for a secure server-based authentication system, especially when handling real customer accounts in Australia or any other market.
Define The Login Screen
Start with a new Android Studio project using an Empty Views Activity. A vertical LinearLayout or ConstraintLayout can hold the input controls. Use clear labels such as “Email address” and “Password” rather than relying only on hint text, because labels remain available after the user starts typing.
A basic form might include two EditText controls, a Button, and a small TextView for status messages. Set the email field to inputType="textEmailAddress" and the password field to inputType="textPassword". Add suitable margins and make the button large enough to tap comfortably on common phones used by customers in Sydney, Melbourne or Brisbane.
Keep the first version deliberately small. The purpose is to understand how a preference value is written, read and removed before adding navigation, registration or password recovery.
Create The SharedPreferences Store
SharedPreferences stores key-value pairs in an XML file private to the application. It is suitable for small settings such as a Boolean named is_logged_in, a display name, or a last-selected option. It is not intended for storing raw passwords, payment details or private tokens.
In an Activity, create a preference object with a private file name:
private val prefs by lazy {
getSharedPreferences("login_settings", MODE_PRIVATE)
}
When the login button is tapped, validate the fields and save only the state required by this demonstration:
prefs.edit()
.putBoolean("is_logged_in", true)
.putString("user_email", email)
.apply()
apply() updates the in-memory values immediately and writes them in the background. Use commit() only when you specifically need to know whether the write completed synchronously. For a normal login screen, apply() keeps the interface responsive.
Connect The Form To Kotlin
The Activity should read the saved state when it starts. If is_logged_in is already true, the app can display a welcome layout or open a home Activity. Otherwise, it shows the login form.
A simple click listener can handle validation and storage:
binding.loginButton.setOnClickListener {
val email = binding.emailInput.text.toString().trim()
val password = binding.passwordInput.text.toString()
when {
email.isBlank() -> binding.emailInput.error = "Enter your email"
password.length < 6 ->
binding.passwordInput.error = "Use at least 6 characters"
else -> {
prefs.edit()
.putBoolean("is_logged_in", true)
.putString("user_email", email)
.apply()
showHomeScreen(email)
}
}
}
For a tutorial, this can accept any valid-looking email and a password of six or more characters. A real app would send credentials over HTTPS to an authentication service, check the server response and handle errors such as an unknown account or a temporary network failure.
Protect The Login Flow
The local demo should follow a few practical rules:
- Never save the user’s plain-text password in preferences.
- Disable or hide the login button while a network request is running.
- Show useful errors without revealing sensitive account details.
- Use HTTPS and short-lived tokens for real authentication.
The Australian Privacy Act and Australian Privacy Principles are relevant when an app collects personal information from local users. A production application should explain what it stores, why it stores it and how users can request access or deletion.
Choose The Right Local Storage
SharedPreferences works well for a Boolean session flag, but it is not a database. If the project needs multiple user records, structured queries or relationships between data, use SQLite or another persistent data solution. This SQLite database guide explains the traditional Android approach with SQLiteOpenHelper.
The following comparison helps define the boundary between common storage options:
| Storage option | Good for | Limitations |
|---|---|---|
| SharedPreferences | Small settings and login state | Basic key-value data only |
| SQLite | Local records and searchable data | Requires schema and query management |
| DataStore | Modern settings storage | Needs coroutine and Flow concepts |
| Server authentication | Real accounts and synchronised access | Requires an API, security and connectivity |
For a new production project, Jetpack DataStore is often a better choice for preferences because it offers stronger asynchronous patterns. SharedPreferences remains valuable for learning, maintaining older applications and implementing a small offline flag.
Restore And End The Session
On startup, retrieve the stored value with a default of false:
val loggedIn = prefs.getBoolean("is_logged_in", false)
if (loggedIn) {
val email = prefs.getString("user_email", "") ?: ""
showHomeScreen(email)
}
A logout button should clear the session state and return the user to the login form. Calling clear() removes every value in that preference file, while removing individual keys is safer if the file contains unrelated settings:
binding.logoutButton.setOnClickListener {
prefs.edit()
.remove("is_logged_in")
.remove("user_email")
.apply()
showLoginScreen()
}
Test this flow on an emulator and a physical phone. Check what happens after the app is closed, the device is restarted and the Activity is recreated during rotation. Android users on prepaid plans or slower regional connections may also reopen an app after a long delay, so the screen should restore state predictably.
Check The Demo Before Sharing It
Run through these cases before treating the sample as complete:
- Empty email and password fields.
- Invalid email format and short passwords.
- Successful login followed by a force-close.
- Logout followed by reopening the application.
Also test the interface with TalkBack, a larger font setting and dark mode. Australian users may access the app across a wide range of devices, from compact budget phones to large-screen models, so fixed widths and tiny touch targets can cause avoidable problems.
Turn The Prototype Into A Real App
The demonstration deliberately treats the local preference as proof of login. In a commercial application, the server must authenticate the user and return a protected session token. The app can then remember a limited session indicator, while sensitive credentials remain on the server side.
Avoid placing API keys, passwords or permanent access tokens in plain preferences. Android Keystore can help protect cryptographic keys, and an established identity provider can manage sign-in, password resets and multi-factor authentication. These safeguards matter when an app is published to the Australian Google Play market and used by real customers.
Use Australian locale settings where appropriate, such as en-AU, and check dates, currency and consent wording during testing. A simple SharedPreferences login is an effective learning exercise, provided its limits are clear and the design is upgraded before it handles genuine personal accounts.