- Published on
How to implement Login/Registration features in Android! Part 1
Even if you have very little experience with using mobile apps, you know the importance of a good authentication system. As mostly authentication systems use the traditional login and registration page system, we will be detailing how to implement a login & registration system for Android applications in this blog post!
Figure 1: Login page for a sample Android application
Setup
First we will need Android Studio installed, click here is get it if you don't already have it installed. Then create a new project with an Empty Activity as the starter template like so:
Figure 2: New Project screen
Once our Android application is setup, we will need the JBCrypt dependency to handle the password hashing functionality and the Android Room dependencies to establish a persistence layer between the application and the database. Therefore we need to add the following dependencies to the build.gradle
file:
dependencies {
...
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// optional - RxJava2 support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - RxJava3 support for Room
implementation "androidx.room:room-rxjava3:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"
// optional - Paging 3 Integration
implementation "androidx.room:room-paging:2.4.0-beta01"
implementation "org.mindrot:jbcrypt:0.4"
}
After that, sync the project with the Gradle files.
In order to use this login/registration system there are some foundational elements that need to be in place prior to setting it up. One of those elements is the User
class so let's code that out now by creating a new package called models
, adding a class called User
to it then adding the following code:
@Entity(tableName="user")
public class User {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name="user_id")
@NonNull
private int userId;
@ColumnInfo(name="username")
@NonNull
private String username;
@ColumnInfo(name="email")
@NonNull
private String email;
@ColumnInfo(name="first_name")
@NonNull
private String firstName;
@ColumnInfo(name="last_name")
@NonNull
private String lastName;
@ColumnInfo(name="password")
@NonNull
private String password;
@ColumnInfo(name="password_salt")
@NonNull
private String passwordSalt;
public User() {
}
}
The @Entity tag indicates a Room entity as a class. The autogenerate
option in the @PrimaryKey()
annotation indicates that Room should increment the object IDs by 1, based on the highest ID that is currently persisted. Then there is the name
option in the @Column()
tag, which specifies the name of the table column that is to be mapped to the corresponding class field. Finally, the @NonNull
annotation indicates that the linked table column cannot contain a null value.
Next, we can move on to building the UserDAO
class. First let's create a package called DAO
in the source folder, then create an interface called UserDAO
. Thirdly, we can add the following code to that file:
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import com.coding.informer.androidloginregistrationexample.models.User;
import java.util.List;
@Dao
public interface UserDAO {
@Query("SELECT * FROM user")
List<User> all();
@Query("SELECT * FROM user WHERE user_id in (:findUserId)")
User findById(int findUserId);
@Query("SELECT * FROM user WHERE username in (:username)")
User findByUsername(String username);
@Insert
void insertALL(User... users);
@Insert
void insertUser(User user);
@Delete
void deleleUser(User user);
}
The @Dao
annotation indicates that this interface is to be used as a Data Access Object(for more information on DAOs check out the Android Developers Documentation). The @Query
annotation marks methods contained in DAO annotated classes as query methods. Likewise the @Insert
annotation marks methods as being capable of inserting objects to database tables. The same applies to the @Delete
annotation with the exception of deleting specific objects from database tables.
Next we build out the AppDatabase
class, which plays a crucial role in being able to connect from our application to the SQLite database. First create a new package called database
, after which a new class called AppDatabase
should be created. Add the following code to the AppDatabase
class:
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import com.coding.informer.androidloginregistrationexample.R;
import com.coding.informer.androidloginregistrationexample.daos.UserDAO;
import com.coding.informer.androidloginregistrationexample.models.User;
import com.fstyle.library.helper.AssetSQLiteOpenHelperFactory;
import static com.coding.informer.androidloginregistrationexample.database.migrations.Migrations.MIGRATION_1_2;
@Database(entities={User.class}, version=1)
abstract public class AppDatabase extends RoomDatabase {
public abstract UserDAO getUserDAO();
private RoomDatabase createDatabase(Context context) {
Builder builder = Room.databaseBuilder(
context, AppDatabase.class,
context.getString(R.string.database_name)
);
return builder.openHelperFactory(new AssetSQLiteOpenHelperFactory())
.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2).build();
}
}
The class defined above is marked as a RoomDatabase
instance because of the @Database()
annotation(for more info on this annotation check out this Android Developers Documentation page). Notice that the UserDAO()
class is used as an attribute in this abstract class because after establishing the database connection the only way of interacting with the user
database table would be through the UserDAO
class.
After defining the AppDatabase
class, we'll need to create some migrations to be used by that class. Therefore, create a new package called migrations
within the database
package. Then create a Migrations
class inside the newly created package. Finally, add the following migration to that class:
public class Migrations {
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `user` (`user_id` INTEGER NOT NULL, "
+ "`username` TEXT NOT NULL, `email` TEXT NOT NULL, " +
"`first_name` TEXT NOT NULL, `last_name` TEXT NOT NULL," +
"`password` TEXT NOT NULL, `password_salt` TEXT NOT NULL, PRIMARY KEY(`user_id`))");
}
};
}
Next, we need to implement several helper classes to assist with the login and registration functionality. First, create a new package called helpers
and create the following classes inside them: DatabaseHelper
, Functions
, SessionManager
. Now we can add the code for these class by using the following code blocks:
// DatabaseHelper
public class DatabaseHelper extends SQLiteOpenHelper {
private static AppDatabase INSTANCE;
private Cursor cursor = null;
private static String DATABASE_NAME = "example_db";
private static String DATABASE_VERSION = "1";
@Override
public void onCreate(SQLiteDatabase db){
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
}
public DatabaseHelper(Context context){
super(context, DATABASE_NAME, null, Integer.parseInt(DATABASE_VERSION));
}
public static AppDatabase getDatabase(Context context){
if(INSTANCE == null){
INSTANCE = createDatabase(context);
}
return INSTANCE;
}
public static AppDatabase createDatabase(Context context){
AsyncTask.execute(() -> {
AppDatabase db = Room.databaseBuilder(context, AppDatabase.class, "example_db")
.addMigrations(MIGRATION_1_2).build();
});
RoomDatabase.Builder<AppDatabase> builder =
Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class,
context.getString(R.string.database_name));
return (builder.openHelperFactory(new AssetSQLiteOpenHelperFactory())
.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2).build());
}
public static String getUserFieldSQLQuery(Context context, UserFieldType userFieldType, String inputUsername) {
AppDatabase appDatabase = getDatabase(context);
User foundUser = appDatabase.getUserDAO().findByUsername(inputUsername);
if(foundUser != null) {
switch (userFieldType) {
case PASSWORD:
Toast.makeText(context, foundUser.toString(), Toast.LENGTH_LONG);
return foundUser.getPassword();
case PASSWORD_SALT:
Toast.makeText(context, foundUser.toString(), Toast.LENGTH_LONG);
return foundUser.getPasswordSalt();
}
}
Toast.makeText(
context,
"An user with that username was not found in the database!",
Toast.LENGTH_LONG);
return null;
}
}
// Functions
public class Functions {
public static DialogFragment showProgressDialog(Context context, String title){
FragmentManager fm = ((AppCompatActivity)context).getSupportFragmentManager();
DialogFragment newFragment = ProgressBarDialog.newInstance(title);
newFragment.show(fm, "dialog");
return newFragment;
}
}
// SessionManager
public class SessionManager {
private SharedPreferences pref;
private SharedPreferences.Editor editor;
private String KEY_IS_LOGGEDIN = "isLoggedIn";
private String PREF_NAME = "AndroidLogin";
private int PRIVATE_MODE = 0;
public SessionManager(Context context){
pref = context.getSharedPreferences(PREF_NAME, PRIVATE_MODE);
editor = pref.edit();
}
public void setLogin(boolean isLoggedIn){
editor.putBoolean(KEY_IS_LOGGEDIN, isLoggedIn);
editor.commit();
Log.d(TAG, "User login session modified");
}
public boolean isLoggedIn(){
return pref.getBoolean(KEY_IS_LOGGEDIN, false);
}
}
Here is the link to this package on it's GitHub repository link if you need to check out the actual source code.
Now we'll give a brief run down of the important sections of each of the files listed above:
DatabaseHelper
createDatabase()
: Uses a worker thread to create an instance of AppDatabase. It is supplied the database name, database class, the necessary migrations and finally builds the AppDatabase instancegetUserFieldSQLQuery()
: Uses theUserDAO()
class to fetch the matching inputted username with any records in theuser
database table and depending on theuserFieldType
, it outputs either the password or the password salt values of found database entry. This is will be used for validating login password values
Functions
showProgressDialog()
: Is used to display a loading screen in between when the user clicks the 'Submit' button and the validation results are shown as Toast messages
SessionManager
setLogin()
: Used to change theKEY_IS_LOGGED
flag variable's value so that the user's login status(meaning: are they logged in or not) can be easily obtained
Ok, now we need to implement the ProgressBarDialog
fragment and the UserFieldType
enum. To do so, create a new package called widgets
and add a class called ProgressBarDialog
. Next, create a new class called UserFieldType
in the models
package. Finally add the following code to the classes we just created:
// ProgressBarDialog
public class ProgressBarDialog extends DialogFragment {
private Bundle bundle;
public ProgressBarDialog(){
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bundle = getArguments();
}
public static ProgressBarDialog newInstance(String title){
ProgressBarDialog myFragment = new ProgressBarDialog();
Bundle args = new Bundle();
args.putString("title", title);
myFragment.setArguments(args);
return myFragment;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
return inflater.inflate(R.layout.dialog_progress, container, false);
}
}
// UserFieldType
public enum UserFieldType {
USERNAME,
EMAIL,
FIRST_NAME,
LAST_NAME,
PASSWORD,
PASSWORD_SALT
}
The newInstance()
method in the ProgressBarDialog
class manages the creation of the DialogFragment
that will display a loading screen for a brief moment after the user clicks the 'Submit' button. Successively, the types in the UserFieldType
enum comprise all the field values in the User
class.
If you made it this far congrats! 👏We have now laid out the ground work for the login/registration functionality.
For the sake of brevity, we will be continue what we have done so far in Part 2 of this blog post series. Till next time!👋
Conclusion
Thanks again for reading this blog post series. If you need access to the source code for this application you can access it by visiting it's GitHub link.
Well that's it for this post! Thanks for following along in this article and if you have any questions or concerns please feel free to post a comment in this post and I will get back to you when I find the time.
If you found this article helpful please share it and make sure to follow me on Twitter and GitHub, connect with me on LinkedIn and subscribe to my YouTube channel.