การทำระบบ Login ด้วย SQLite

การทำระบบ Login ด้วย SQLite Cover Image

บทความสอนเขียนแอพ Android บทความนี้ ขอนำเสนอ การทำระบบ Login ด้วย SQLite ซึ่งเป็นความบทที่ 2 ใน 4 เกี่ยวกับการสร้างระบบ Login บน Android สำหรับรายละเอียด บทอื่นๆ ก็มีดังนี้ครับ

ในบทความนี้จะคล้ายๆกับบทความที่แล้ว การทำระบบ Login ด้วย SharedPreferences จะแตกต่างกันที่บทความที่แล้วใช้การเก็บข้อมูลในรูปแบบ SharedPreferences แต่ว่าบทความนี้เปลี่ยนเป็นรูปแบบเก็บข้อมูลลงฐานข้อมูลด้วย SQLite แทน

บทความนี้จะไม่อธิบายพวกพื้นฐานอะไรมากนะครับ หากไม่เข้าใจอะไร ก็สามารถอ่านเพิ่มเติมได้ตามลิงค์นี้

Overview

ภาพรวมของระบบล็อคอินด้วย SQLite จะคล้ายๆกับบทความเก่าเลย แต่จะแตกต่างกันที่ บทความนี้เพิ่มในส่วนของ Change Password เข้าไปด้วย ทำให้เมื่อมองดูการทำงานของแอพแล้ว มันจะไปสัมพันธ์กับฐานข้อมูลเป็นแบบนี้

  • การ Login ก็คือการ query
  • การ register ก็คืการ insert
  • การเปลี่ยนพาสเวิร์ด ก็คือการ update

แบบ Mockup คร่าวๆ ก็จะเป็นแบบนี้

Mockup

  • มีหน้าล็อคอิน สำหรับ ใส่ username, password เพื่อเข้าระบบ
  • มีหน้า register สำหรับลงทะเบียน กรณีที่ไม่มี username
  • หน้าหลัก หลักจากที่ล็อคอินได้เรียบร้อยแล้ว จะมีปุ่มให้กดเปลี่ยนพาสเวิร์ดด้วย

เริ่มต้นสร้างโปรเจ็ค

บทความนี้จะใช้โปรเจ็คจากบทความที่แล้วเลยนะครับ ฉะนั้น ใช้โค๊ดเดิมได้เลย สามารถโหลดได้จาก Github นี้ครับ

หรือจะ clone ก็ตามนี้

แบบ HTTPS

git clone https://github.com/Devahoy/android-login-example.git
git checkout part1

แบบ SSH

git clone [email protected]:Devahoy/android-login-example.git
git checkout part1

โค๊ดจากบทความที่แล้ว branch คือ part1 นะครับ ถ้า Part2 คือบทความนี้ เอาไว้ดูโค๊ดของ part2 เมื่อจบบทความนี้จะดีกว่านะครับ

เมื่อได้โค๊ดมาแล้ว ก็มาต่อกันเลย

สร้าง UserManagerHelper

ทำการสร้างอินเตอร์เฟซชื่อ UserManagerHelper ขึ้นมา ดังนี้

package com.devahoy.sample.login;

public interface UserManagerHelper {

    public static final String DATABASE_NAME = "ahoy_login";
    public static final int DATABASE_VERSION = 1;

    /**
     * ทำการเซฟข้อมูล User ลงฐานข้อมูล
     * @param user
     * @return หากบันทึกสำเร็จจะส่งค่า row ID กลับมา ถ้ามี error จะส่ง -1
     */
    public long registerUser(User user);

    /**
     * ทำการเช็ค User ว่าล็อคอินด้วย username และ password <br />
     * ถูกต้องตรงกับในฐานข้อมูลหรือไม่ (มันก็คือการ query sqlite นั่นเอง) <br />
     * หาก query ด้วย username, password แล้วมีข้อมูล แสดงว่า ล็อคอินถูกต้อง
     * @param user
     * @return - หากตรง ก็ส่งค่าเป็น user นั้นๆกลับไป หากไม่ตรงก็ส่ง null
     */
    public User checkUserLogin(User user);

    /**
     * สำหรับเปลี่ยน password โดยทำการ query หาข้อมูล username, password ก่อน <br />
     * จากนั้นถึง update โดยเปลี่ยน password ใหม่แทน
     * @param user
     * @return - ส่งค่า จำนวนแถวที่มีการ update
     */
    public int changePassword(User user);

}

จากอินเตอร์เฟซด้านบน ผมได้เขียน Javadoc รายละเอียดไว้ด้านบนแล้ว ซึ่งทั้งสามเมธอด ประกอบไปด้วย

  • registerUser() : เอาไว้สำหรับบันทึกข้อมูลลง Database (insert database)
  • checkUserLogin() : เอาไว้ query ข้อมูล ว่ามี user นี้หรือไม่ (query database)
  • changePassword() : เอาไว้สำหรับเปลี่ยนพาสเวิร์ด (update database)

สร้างคลาส User

สำหรับตัวอย่างนี้ก็ทำการสร้างคลาส model User แบบง่ายๆละกัน คือไม่มีอะไรมาก user แค่มี username และ password เท่านั้น จะได้ดังนี้

package com.devahoy.sample.login.model;

import android.provider.BaseColumns;

public class User {

    public static final String TABLE = "user";

    public class Column {
        public static final String ID = BaseColumns._ID;
        public static final String USERNAME = "username";
        public static final String PASSWORD = "password";
    }

    private int id;
    private String username;
    private String password;

    // Constructor
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public User() {

    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

แก้ไขคลาส UserManager

ปรับเปลี่ยนคลาส UserManager ใหม่ โดยลบของเก่าทิ้งให้หมด แล้วแก้ไข ใหม่ เป็นแบบนี้

package com.devahoy.sample.login;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class UserManager extends SQLiteOpenHelper implements UserManagerHelper {

    public UserManager(Context context) {
        super(context, UserManagerHelper.DATABASE_NAME, null, UserManagerHelper.DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    @Override
    public long registerUser(User user) {
        return 0;
    }

    @Override
    public User checkUserLogin(User user) {
        return null;
    }

    @Override
    public int changePassword(User user) {
        return 0;
    }
}

จากโค๊ดด้านบน ทำการ extends คลาส SQLiteOpenHelper และ implement อินเตอร์เฟซ UserManagerHelper ที่ได้สร้างไว้ก่อนหน้านี้ ต่อมาก็ override เมธอด และสร้าง constructor

เพิ่ม global member นี้ลงไป เอาไว้ใช้สำหรับ Debug และ read/write database

public class UserManager extends SQLiteOpenHelper implements UserManagerHelper {
    public static final String TAG = UserManager.class.getSimpleName();
    private SQLiteDatabase mDatabase;

...
}

ต่อมาที่เมธอด onCreate() ให้ทำการสร้าง Table ขึ้นมาด้วยคำสั่งนี้

@Override
public void onCreate(SQLiteDatabase db) {
    String CREATE_TABLE_USER = String.format("CREATE TABLE %s " +
                    "(%s INTEGER PRIMARY KEY AUTOINCREMENT, %s TEXT, %s TEXT)" ,
            User.TABLE,
            User.Column.ID,
            User.Column.USERNAME,
            User.Column.PASSWORD
    );

    db.execSQL(CREATE_TABLE_USER);

    Log.i(TAG, CREATE_TABLE_USER);
}

และเมธอด onUpgrade() จะได้แบบนี้

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    String DROP_USER = "DROP TABLE IF EXISTS " + UserManagerHelper.DATABASE_VERSION;

    db.execSQL(DROP_USER);

    Log.i(TAG, DROP_USER);
    onCreate(mDatabase);
}

เมธอด registerUser()

ยังอยู่ที่คลาส UserManagerHelper เหมือนเดิม ต่อมา ทำการแก้ไขเมธอด registerUser โดยส่งออปเจ็ค user มา จากนั้นใช้ ContentValues เพื่อ insert ข้อมูลลงฐานข้อมูล จะได้แบบนี้

@Override
public long registerUser(User user) {

    mDatabase = this.getWritableDatabase();

    ContentValues values = new ContentValues();
    values.put(User.Column.USERNAME, user.getUsername());
    values.put(User.Column.PASSWORD, user.getPassword());

    long result = mDatabase.insert(User.TABLE, null, values);
    mDatabase.close();

    return result;
}

เมธอด checkUserLogin()

เมธอดนี้ ก็เช่นเดียวกัน คือรับพารามิเตอร์มาเป็น ออปเจ็ค user จากนั้น เราจะใช้ user นี้แหละ ทำการเช็คโดยการ query ใน database ว่า username และ password ที่เราได้เนี่ย กับในฐานข้อมูล มันตรงกันไหม ถ้ามีข้อมูลแสดงว่าถูกต้อง ก็จะส่งออปเจ็คกลับไป ถ้าไม่ตรง ก็จะส่งค่า null โค๊ดที่ได้ก็ประมาณนี้

@Override
public User checkUserLogin(User user) {

    mDatabase = this.getReadableDatabase();

    Cursor cursor = mDatabase.query(User.TABLE,
            null,
            User.Column.USERNAME + " = ? AND " +
                    User.Column.PASSWORD + " = ?",
            new String[]{user.getUsername(), user.getPassword()},
            null,
            null,
            null);

    User currentUser = new User();

    if (cursor != null) {
        if (cursor.moveToFirst()) {
            currentUser.setId((int) cursor.getLong(0));
            currentUser.setUsername(cursor.getString(1));
            currentUser.setPassword(cursor.getString(2));
            mDatabase.close();
            return currentUser;
        }
    }

    return null;
}

เมธอด changePassword()

เมธอดนี้คือทำการเปลี่ยน password ของยูเซอร์นั้นๆ โดยการใช้การ update ฐานข้อมูล โค๊ดก็จะเป็นดังนี้

@Override
public int changePassword(User user) {

    mDatabase = this.getWritableDatabase();

    ContentValues values = new ContentValues();
    values.put(User.Column.USERNAME, user.getUsername());
    values.put(User.Column.PASSWORD, user.getPassword());

    int row = mDatabase.update(User.TABLE,
            values,
            User.Column.ID + " = ?",
            new String[] {String.valueOf(user.getId())});

    mDatabase.close();
    return row;
}

เรียบร้อย ตอนนี้เราก็สร้างทั้งอินเตอร์เฟซ UserMangerHelper, คลาส User และคลาส UserManager ไว้เรียบร้อยแล้ว ต่อไปก็ถึงเวลาเอาเมธอดต่างๆที่สร้างไว้ ไปใช้กันแล้ว

แก้ไขหน้า LoginActivity

เริ่มด้วยการแก้ไขหน้า Login จากทีแรก ใช้การเช็คแบบ SharedPreferences ด้วยเมธอด checkLoginValidate เป็นแบบการใช้ SQLite ด้วย UserManager ที่ได้แก้ไขใหม่แล้ว แบบนี้

package com.devahoy.sample.login;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.devahoy.sample.login.model.User;
import com.devahoy.sample.login.utils.UserManager;


public class LoginActivity extends ActionBarActivity {

    private Button mLogin;
    private EditText mUsername;
    private EditText mPassword;
    private TextView mRegister;
    private Context mContext;

    private UserManager mManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_login);

        mManager = new UserManager(this);

        mContext = this;

        mLogin = (Button) findViewById(R.id.button_login);
        mUsername = (EditText) findViewById(R.id.username);
        mPassword = (EditText) findViewById(R.id.password);
        mRegister = (TextView) findViewById(R.id.register);

        mLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                checkLogin();
            }
        });

        mRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(mContext, RegisterActivity.class);
                startActivity(intent);
            }
        });
    }

    private void checkLogin() {
        String username = mUsername.getText().toString().trim().toLowerCase();
        String password = mPassword.getText().toString().trim();

        User user = new User(username, password);

        User validateUser = mManager.checkUserLogin(user);

        if (null == validateUser) {
            String message = getString(R.string.login_error_message);
            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
        } else {
            Intent intent = new Intent(mContext, MainActivity.class);
            intent.putExtra(User.Column.USERNAME, validateUser.getUsername());
            intent.putExtra(User.Column.ID, validateUser.getId());
            startActivity(intent);
            finish();
        }
    }
}

โดยเมื่อเช็คแล้ว ว่าล็อคอินได้ถูกต้อง ก็จะทำการส่งค่า username ไปด้วย เพื่อเอาไว้แสดง Hello ที่หน้า main แล้วก็ค่า id กรณีที่เอาไว้เปลี่ยน password

แก้ไขหน้า RegisterActivity

ต่อมาทำการแก้ไขหน้า RegisterActivity นิดหน่อย โดยทำการปรับเปลี่ยนแก้ไข เป็น

User user = new User(username, password);
long rowId = mManager.registerUser(user);

if (rowId == -1) {

} else {

}

โค๊ดออกมาเป็นแบบนี้

package com.devahoy.sample.login;

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.devahoy.sample.login.model.User;
import com.devahoy.sample.login.utils.UserManager;

public class RegisterActivity extends ActionBarActivity {

    private EditText mUsername;
    private EditText mPassword;
    private EditText mConfirmPassword;
    private Button mRegister;

    private Context mContext;
    private UserManager mManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);

        mManager = new UserManager(this);
        mContext = this;

        mUsername = (EditText) findViewById(R.id.username);
        mPassword = (EditText) findViewById(R.id.password);
        mConfirmPassword = (EditText) findViewById(R.id.confirm_password);
        mRegister = (Button) findViewById(R.id.button_register);

        mRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String username = mUsername.getText().toString().trim().toLowerCase();
                String password = mPassword.getText().toString();
                String confirmPassword = mConfirmPassword.getText().toString();

                if (password.equals(confirmPassword)) {
                    User user = new User(username, password);
                    long rowId = mManager.registerUser(user);

                    if (rowId == -1) {
                        String message = getString(R.string.register_error_message);
                        Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
                    } else {
                        String message = getString(R.string.register_success);
                        Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
                        finish();
                    }

                } else {
                    String message = getString(R.string.register_password_error);
                    Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
                }

            }
        });
    }
}

MainActivity

สุดท้าย MainActivity เราจะเพิ่มนิดหน่อย จากทีแรก แค่โชว์โลโก้และมี Welcome Text คราวนี้เราจะเพิ่มชื่อล็อคอินของ user และ ปุ่มสำหรับเปลี่ยน พาสเวิร์ดเข้าไปด้วย ฉะนั้น ก็เลยต้องแก้ไขไฟล์ activity_main.xml และ MainActivity.java เป็นแบบนี้

ไฟล์ activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:gravity="center">

    <ImageView
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:src="@drawable/ic_launcher"
        android:id="@+id/logo"
        android:layout_gravity="center"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/say_hi"
        android:textSize="24sp"
        android:layout_marginTop="16dp"
        android:layout_gravity="center"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/welcome_message"
        android:id="@+id/welcome"
        android:textSize="32sp"
        android:layout_gravity="center"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/change_password"
        android:id="@+id/change_password"
        android:layout_marginTop="32dp"
        android:layout_gravity="center"/>
</LinearLayout>

ไฟล์ MainActivity.java

package com.devahoy.sample.login;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.devahoy.sample.login.model.User;
import com.devahoy.sample.login.utils.UserManager;

public class MainActivity extends ActionBarActivity {

    Button mChangePassword;
    TextView mUsername;
    private UserManager mManager;
    User mUser;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mManager = new UserManager(this);
        mUser = new User();

        mChangePassword = (Button) findViewById(R.id.change_password);
        mUsername = (TextView) findViewById(R.id.say_hi);

        Bundle args = getIntent().getExtras();

        if (null == args) {
            Toast.makeText(this, getString(R.string.welcome_error_message),
                    Toast.LENGTH_SHORT).show();
            finish();
        } else {
            mUsername.setText(getString(R.string.say_hi) + " " +
                    args.getString(User.Column.USERNAME));

            mUser.setId(args.getInt(User.Column.ID));
            mUser.setUsername(args.getString(User.Column.USERNAME));
        }

        mChangePassword.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showDialogPassword();
            }
        });
    }

    private void showDialogPassword() {

    }

    private void goToLogin() {
        Intent intent = new Intent(this, LoginActivity.class);
        startActivity(intent);
        finish();
    }
}

จากโค๊ด MainActivity ด้านบน เริ่มแรก ก็ทำการรับค่า Intent ที่ถูกส่งมาจากหน้า Login คือ username และ id จากนั้นก็เซตค่าให้แสดงว่า “Hi + ชื่อ” ส่วนปุ่ม Change Password ก็ setOnClickListener() ให้มัน เพื่อเมื่อกดแล้ว ก็จะโชว์ Dialog ขึ้นมาให้กดเปลี่ยนพาสเวิร์ด

โชว์ Dialog changePassword()

เมื่อกดปุ่ม Change Password ก็จะโชว์ Dialog ขึ้นมา ฉะนั้น ก็จะใช้แค่ Dialog แบบ Custom xml ธรรมดา

ให้ทำการสร้างไฟล์ xml เซฟไว้ที่ res/layout/dialog.xml ไฟล์มีแค่ EditText อันเดียว แบบนี้

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <EditText
        android:id="@+id/password"
        android:inputType="textPassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"
        android:layout_marginBottom="16dp"
        android:hint="@string/new_password"/>
</LinearLayout>

ต่อมา ที่เมธอด showDialogPassowrd() ที่ประกาศโครงเปล่าๆไว้ ก็เพิ่มโค๊ดนี้ลงไป

    AlertDialog.Builder builder =
            new AlertDialog.Builder(MainActivity.this);
    LayoutInflater inflater = getLayoutInflater();
    View view = inflater.inflate(R.layout.dialog, null);

    final EditText newPassword = (EditText) view.findViewById(R.id.password);
    builder.setView(view);

    builder.setPositiveButton(getString(android.R.string.ok),
            new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            String password = newPassword.getText().toString();
            if(!TextUtils.isEmpty(password)) {
                mUser.setPassword(password);
                mManager.changePassword(mUser);
                Toast.makeText(getApplicationContext(),
                        getString(R.string.change_password_message),
                        Toast.LENGTH_SHORT).show();
                goToLogin();
            }
        }
    });
    builder.setNegativeButton(getString(android.R.string.cancel), null);
    builder.show();

หากใครทำ Dialog ไม่เป็น สามารถอ่านเพิ่มเติมได้จากบทความนี้ การสร้าง Dialog บน Android

โค๊ดด้านบน เป็นการโชว์ Dialog ธรรมดา เมื่อยูเซอร์เปลี่ยนพาสเวิร์ด ก็จะทำการ update ฐานข้อมูล จากนั้นก็กลับไปหน้าล็อคอินใหม่ อีกครั้ง เป็นอันเรียบร้อย

สรุป

สำหรับตัวอย่างนี้ วิธีการ Login ด้วยการเก็บข้อมูลด้วย SQLite มันจะมีข้อดีกว่า SharedPreferences คือ เราสามารถเพิ่ม User ได้หลายคน สามารถเปลี่ยนแปลงค่าได้ สามารถค้นหาข้อมูลโดยไม่ต้องเปรียบเทียบ Key-Values ตัวอย่างนี้ก็เป็นแบบคร่าวๆนะครับ ใครจะนำไปประยุกต์แก้ไข ดัดแปลงก็ตามสบายเลย อ้อ สำหรับ source code ดูได้จาก Github เลยครับ

Source Code

หรือใคร clone โปรเจ็คมาแล้ว ก็ดูที่ branch part2 ได้เลยครับ

สำหรับวิธี Login ด้วยการเก็บข้อมูลแบบ SharedPreferences แบบตัวอย่างข้างต้นนั้นมีข้อดีคือ ความสะดวก รวดเร็ว แต่มีข้อเสียคือ เก็บ password เป็น plain text ใน xml ไฟล์ และข้อเสียอีกข้อคือไม่สามารถ ค้นหา username และ password ได้ ทำได้เพียงแค่หาข้อมูลโดยระบุ key เท่านั้น และที่สำคัญ สามารถลงทะเบียนได้แค่ไอดีเดียว เพราะเมื่อมีการลงทะเบียน user ใหม่ มันก็จะไปเซฟทับกับ key ใน SharedPreferences เดิม

Chai

Chai Phonbopit : Developer แห่งหนึ่ง • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Node.js, JavaScript และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football

บทความล่าสุด