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!

Login page for a sample Android application

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:

New Project screen

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 instance
  • getUserFieldSQLQuery(): Uses the UserDAO() class to fetch the matching inputted username with any records in the user database table and depending on the userFieldType, 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 the KEY_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.