QML User Login App with SQLite backend

Qt Quick provides an option for reading and writing SQLite databases using the module “QtQuick.LocalStorage”.  These databases are user-specific and QML-specific, but accessible to all QML applications. They are stored in the Databases subdirectory of QQmlEngine::offlineStoragePath(), currently as SQLite databases. Database connections are automatically closed during Javascript garbage collection.

In this blog post I am going to demonstrate how we can write a simple user based login application using QML which uses LocalStorage to store the user credentials. This demo application has following features.

  • New User Registration
  • User Login
  • Authentication and error handling
  • Password retrieval

This application is spitted into multiple screens and uses StackView show the screens. Following are the list of screens which we will be using in this demo application.

  • LogInPage
  • RegisterScreen
  • PasswordResetPage
  • UserInfoPage

The icons which you can see in this application are created by using “Font Awesome” font. I have used the FontLoader from QML for this purpose. And this font object is used for all the Text items to show their respective icons using Unicode strings.

FontLoader {
        id: fontAwesome
        name: "fontawesome"
        source: "qrc:/fontawesome-webfont.ttf"
    }

In this application I have used a PopUp item to show all the warnings and error messages of this application. This popup is placed at the bottom of the screen. I have also introduced a Timer along this popup to close it automatically after 2 seconds.

    //Popup to show messages or warnings on the bottom position of the screen
    Popup {
        id: popup
        property alias popMessage: message.text
        background: Rectangle {
            implicitWidth: rootWindow.width
            implicitHeight: 60
            color: "#b44"
        }
        y: rootWindow.height
        modal: true
        focus: true
        closePolicy: Popup.CloseOnPressOutside

        Text {
            id: message
            anchors.centerIn: parent
            font.pointSize: 12
            color: "#ffffff"
        }
        onOpened: popupClose.start()
    }

    // Popup will be closed automatically in 2 seconds after its opened
    Timer {
        id: popupClose
        interval: 1000
        onTriggered: popup.close()
    }

I have used a JavScript file “backend.js” to validate the user credentials for login and registration.

In my “main.qml” file I have used a StackView as a container and it loads “LogInPage.qml” as the initial page. And here I also initializes the Database and create a table to store user details. Instance of this database will be used across the application to store/retrieve user data.

Following are the main operation which I am performing on the database.

  • Create database and create a new table for user credentials
  • Insert user credentials in database
  • Retrieve password for the given user for authentication
  • Retrieve password for the given user and hint for password retrieval

Below is my application’s main.qml file.

main.qml:

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.1
import QtQuick.LocalStorage 2.0

import "backend.js" as Backend

ApplicationWindow {
    id: rootWindow
    visible: true
    width: 420
    height: 680
    title: qsTr("Login Demo")
    flags: Qt.FramelessWindowHint

    property color backGroundColor : "#394454"
    property color mainAppColor: "#6fda9c" //"#FA6B65"
    property color mainTextCOlor: "#f0f0f0"
    property var dataBase

    FontLoader {
        id: fontAwesome
        name: "fontawesome"
        source: "qrc:/fontawesome-webfont.ttf"
    }

    // Main stackview
    StackView{
        id: stackView
        focus: true
        anchors.fill: parent
    }

    // After loading show initial Login Page
    Component.onCompleted: {
        stackView.push("qrc:/LogInPage.qml")   //initial page
        dataBase = userDataBase()
        console.log(dataBase.version)
    }

    //Popup to show messages or warnings on the bottom postion of the screen
    Popup {
        id: popup
        property alias popMessage: message.text
        background: Rectangle {
            implicitWidth: rootWindow.width
            implicitHeight: 60
            color: "#b44"
        }

        y: rootWindow.height
        modal: true
        focus: true
        closePolicy: Popup.CloseOnPressOutside

        Text {
            id: message
            anchors.centerIn: parent
            font.pointSize: 12
            color: "#ffffff"
        }
        onOpened: popupClose.start()
    }

    // Popup will be closed automatically in 2 seconds after its opened
    Timer {
        id: popupClose
        interval: 1000
        onTriggered: popup.close()
    }

    // Create and initialize the database
    function userDataBase()
    {
        var db = LocalStorage.openDatabaseSync("UserLoginApp", "1.0", "Login example!", 1000000);
        db.transaction(function(tx) {
            tx.executeSql('CREATE TABLE IF NOT EXISTS UserDetails(username TEXT, password TEXT, hint TEXT)');
        })
        return db;
    }

    // Register New user
    function registerNewUser(uname, pword, pword2, hint)
    {
        var ret  = Backend.validateRegisterCredentials(uname, pword, pword2, hint)
        var message = ""
        switch(ret)
        {
        case 0: message = "Valid details!"
            break;
        case 1: message = "Missing credentials!"
            break;
        case 2: message = "Password does not match!"
            break;
        }

        if(0 !== ret)
        {
            popup.popMessage = message
            popup.open()
            return
        }

        dataBase.transaction(function(tx) {
            var results = tx.executeSql('SELECT password FROM UserDetails WHERE username=?;', uname);
            if(results.rows.length !== 0)
            {
                popup.popMessage = "User already exist!"
                popup.open()
                return
            }
            tx.executeSql('INSERT INTO UserDetails VALUES(?, ?, ?)', [ uname, pword, hint ]);
            showUserInfo(uname) // goto user info page
        })
    }

    // Login users
    function loginUser(uname, pword)
    {
        var ret  = Backend.validateUserCredentials(uname, pword)
        var message = ""
        if(ret)
        {
            message = "Missing credentials!"
            popup.popMessage = message
            popup.open()
            return
        }

        dataBase.transaction(function(tx) {
            var results = tx.executeSql('SELECT password FROM UserDetails WHERE username=?;', uname);
            if(results.rows.length === 0)
            {
                message = "User not registered!"
                popup.popMessage = message
                popup.open()
            }
            else if(results.rows.item(0).password !== pword)
            {
                message = "Invalid credentials!"
                popup.popMessage = message
                popup.open()
            }
            else
            {
                console.log("Login Success!")
                showUserInfo(uname)
            }
        })
    }

    // Retrieve password using password hint
    function retrievePassword(uname, phint)
    {
        var ret  = Backend.validateUserCredentials(uname, phint)
        var message = ""
        var pword = ""
        if(ret)
        {
            message = "Missing credentials!"
            popup.popMessage = message
            popup.open()
            return ""
        }
        
        dataBase.transaction(function(tx) {
            var results = tx.executeSql('SELECT password FROM UserDetails WHERE username=? AND hint=?;', [uname, phint]);
            if(results.rows.length === 0)
            {
                message = "User not found!"
                popup.popMessage = message
                popup.open()
            }
            else
            {
                pword = results.rows.item(0).password
            }
        })
        return pword
    }

    // Show UserInfo page
    function showUserInfo(uname)
    {
        stackView.replace("qrc:/UserInfoPage.qml", {"userName": uname})
    }

    // Logout and show login page
    function logoutSession()
    {
        stackView.replace("qrc:/LogInPage.qml")
    }

    // Show Password reset page
    function forgotPassword()
    {
        stackView.replace("qrc:/PasswordResetPage.qml")
    }
}

 

Below you can see the screenshots from this application.

This slideshow requires JavaScript.

Source code:

https://github.com/arunpkqt/UserLoginApp

16 responses to “QML User Login App with SQLite backend”

  1. The default interface and very useful but it is it is using GPL license, too limit for use code in this demo.

    Like

    1. Thanks for your comment. I have changed the License to MIT.

      Like

  2. Thank you a bunch for sharing this with all folks you really recognise what you’re speaking approximately! Bookmarked. Kindly also talk over with my website =). We may have a hyperlink alternate contract among us

    Like

  3. not able to set username and password icon. is is not displaying,showing wrong sign in app

    Like

    1. Hey Suraj, It might happen that the font-awesome font is not loaded correctly. You could also try replacing it with the latest version of the font from https://fontawesome.com/ and try again.

      Like

  4. How can i add login with facebook feature in it???

    Like

    1. There is no built-in support in Qt or QML.

      You might need to use third party framework. One is Felgo which is officially listed by Facebook as Qt/QML support https://felgo.com/doc/plugin-facebook/

      Like

  5. […] 3. QML User Login App with SQLite backend – Arun’s Qt Corner […]

    Like

  6. How to track the database location file? Can I read and edit the database too?

    Like

    1. hey Syahir,

      The doc says,
      These databases are user-specific and QML-specific, but accessible to all QML applications. They are stored in the Databases subdirectory of QQmlEngine::offlineStoragePath(), currently as SQLite databases.

      Read & Edit is supported by default.
      You can have a look at the example here
      https://doc.qt.io/qt-5/qtquick-localstorage-example.html

      Like

  7. […] 11. QML User Login App with SQLite backend – Arun’s Qt Corner […]

    Like

  8. Why is the border line above my “sign up” not showing?

    Like

    1. Hello,

      What you meant by the borderline above the sign-up button was not clear to me.
      Could you please explain what the issue is?

      Like

      1. When I run it on the emulator, the color of the border line (left and right, bottom) is there; but only the upper side (the side near the Sigin-in button) is not.
        My AVD environment:
        Name: Nexus S API 29
        Resolution: 480×800(hdpi)
        API: 29
        Target: Android 10.0 (Google APIs)

        Like

  9. […] QML User Login App with SQLite backend – Arun’s Qt Corner […]

    Like

Leave a comment