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

Published on
Android
2014/06/android-login-activity-with-sharedpreferences
Discord

บทความสอนเขียนแอพ Android บทความนี้ ขอนำเสนอ การทำระบบ Login ด้วย SharedPreferences ซึ่งบทความนี้เป็นบทความ 1 ใน 4 บทความในซีรีย์ การสร้างระบบ Login บน Android ที่ประกอบไปด้วย

ก่อนอื่นเลย สำหรับบทความนี้คือ การ Login และเก็บข้อมูลด้วย SharedPreferences ฉะนั้นหากใครยังไม่รู้จักว่า SharedPreferences คืออะไร ให้อ่านบทความนี้ประกอบครับ เซฟข้อมูลด้วย SharedPreferences

Overview

มาดูภาพรวมของระบบนี้กันก่อนนะครับ ระบบก็ไม่มีอะไรซับซ้อนมาก ดูภาพด้านล่างประกอบได้เลย

Overview

จะมีด้วยกันอยู่ 3 หน้า คือ หน้าล็อคอิน หน้าสมัครสมาชิก และหน้าหลัก เมื่อล็อคอินได้เรียบร้อยแล้ว

สำหรับภาพรวมของระบบ จะใช้อันนี้กับบทความอื่นด้วยนะครับ การทำงานเหมือนกันหมด ต่างกันแค่วิธีการเก็บข้อมูลเท่านั้นเอง

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

ให้ทำการสร้างโปรเจ็คขึ้นมาเลยครับ ในบทความผมใช้ Android Studio หากใครใช้ Eclipse ก็ปรับตัวเรื่องหน้าตาอาจจะไม่เหมือนในบทความเอานะครับ แต่ยังไงพวกโค๊ดและ xml ก็ต้องมีเหมือนกันอยู่แล้ว ฉะนั้น IDE มันคงไม่ใช่ปัญหาซักเท่าไหร่

ใครสร้างไม่เป็นแนะนำให้อ่านนี้ก่อนละกัน หรือเสิชหาในเนตครับ มีเพียบ อย่าบอกนะว่าหาไม่เจอ :D

มาเริ่มกันเลยดีกว่า

สร้างหน้า Login

ทำการสร้างหน้าล็อคอินขึ้นมาก่อนอันดับแรก เริ่มต้นสร้างไฟล์เลเอาท์ก่อน ตั้งชื่อว่า activity_login.xml เซฟไว้ใน res/layout/

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

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:background="#ff3eaff9"
    android:paddingLeft="16dp"
    android:paddingRight="16dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="280dp"
        android:orientation="vertical"
        android:padding="16dp"
        android:gravity="center"
        android:layout_centerInParent="true"
        android:background="#ffffffff">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/login_title"
            android:textSize="24sp"
            android:textColor="#ff3eaff9"
            android:layout_marginBottom="16dp"/>

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="16dp"
            android:id="@+id/username"
            android:hint="@string/username_title"/>

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="16dp"
            android:id="@+id/password"
            android:inputType="textPassword"
            android:hint="@string/password_title"/>
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/button_login"
            android:background="#ff3eaff9"
            android:textColor="#ffffff"
            android:text="@string/button_login"
            android:textSize="18sp"
            android:layout_marginTop="16dp"/>

    </LinearLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/register"
        android:text="@string/register"
        android:textColor="#ffffff"
        android:layout_centerInParent="true"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_marginBottom="16dp"/>

</RelativeLayout>

หน้าตาเลเอาท์เราจะได้ประมาณนี้

Login

ส่วนไฟล์ res/values/strings.xml ที่ใช้ในเลเอาท์นี้ ก็มีประมาณนี้

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

    <string name="app_name">DevahoyLogin</string>
    <string name="login_title">Devahoy</string>
    <string name="username_title">Username</string>
    <string name="password_title">Password</string>
    <string name="button_login">Login</string>
    <string name="register">New User? Register here</string>
    <string name="login_error_message">Username or Password is incorrect.</string>

</resources>

ต่อมาสร้างคลาส LoginActivity.java ขึ้นมา จะได้โค๊ดแบบนี้

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;


public class LoginActivity extends ActionBarActivity {

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

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

        setContentView(R.layout.activity_login);

        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() {

    }
}

จากโค๊ดด้านบน ไม่มีอะไรมาก ทำการเชื่อม View ในคลาสกับ View ที่สร้างไว้ใน xml จากนั้นปุ่ม Button สำหรับ Login ก็ทำการ setListener ให้มัน จากนั้นก็เรียกเมธอด checkLogin() ซึ่งเมธอดนี้เดี๋ยวจะเขียนทีหลังนะครับ ตอนนี้ประกาศเป็น no body ไปก่อน

ส่วน TextView สำหรับ Register ก็ setListener เช่นกัน ส่วนนี้เมื่อกดก็จะไปเปิด Activity ใหม่ ซึ่งเป็นหน้า RegisterActivity เราจะสร้างคลาสนี้ในสเตปถัดไป ตอนนี้ปล่อย error ไว้ก่อน

สร้าง UserManager

่ต่อมาในส่วนของ Logic สำหรับ Login เอาไว้เช็ค username และ password รวมถึงเซฟลง SharedPreferences เราจะทำในส่วนของคลาสนี้ ฉะนั้นไฟล์ในคลาสนี้จะได้ประมาณนี้ ทำการสร้างคลาส UserManager.java ขึ้นมาแบบนี้

package com.devahoy.sample.login;

import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;

public class UserManager {

    /**
     * KEY_PREFS ไว้สำหรับเป็น key ของ SharedPreferences
     */
    private final String KEY_PREFS = "prefs_user";

    /**
     * ชื่อ key ที่ไว้เซฟ username ใน SharedPreferences
     */
    private final String KEY_USERNAME = "username";

    /**
     * ชื่อ key ที่ไว้เซฟ password ใน SharedPreferences.
     */
    private final String KEY_PASSWORD = "password";


    private SharedPreferences mPrefs;
    private SharedPreferences.Editor mEditor;

    /**
     * รับค่า Context เพื่อเอาไว้ใช้สำหรับ getSharedPreferences
     * @param context
     */
    public UserManager(Context context) {
        mPrefs = context.getSharedPreferences(KEY_PREFS, Context.MODE_PRIVATE);
        mEditor = mPrefs.edit();
    }

    /**
     * ทำการเช็ค Username กับ Password ใน SharedPreferences<br />
     * โดยเงื่อนไข EditText ของ Username และ password ต้องไม่เป็นค่าว่าง <br />
     * และค่าที่ได้ ต้องตรงกับใน SharedPreferences
     * @param username - username จาก EditText ที่ใส่
     * @param password - password จาก EditText ที่ใส่
     * @return หากใส่ข้อมูล ให้ส่งค่ากลับเป็น true, ถ้าใส่ผิดก็ส่ง false
     */
    public boolean checkLoginValidate(String username, String password) {
        String realUsername = mPrefs.getString(KEY_USERNAME, "");
        String realPassword = mPrefs.getString(KEY_PASSWORD, "");

        if ( (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) &&
                username.equals(realUsername) &&
                password.equals(realPassword)) {
            return true;
        }
        return false;
    }

    /**
     * เมธอดสำหรับลงทะเบียนสมาชิกใหม่ โดยส่งค่า username และ password มา<br />
     * จากนั้นจะเซฟลง SharedPreferences
     * @param username - username จาก EditText ที่ใส่
     * @param password - password จาก EditText ที่ใส่
     * @return ส่งค่ากลับไปเป็น false หากลงทะเบียนไม่สำเร็จ <br />
     * เป็น true หากลงทะเบียนสำเร็จ
     */
    public boolean registerUser(String username, String password) {

        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
            return false;
        }

        mEditor.putString(KEY_USERNAME, username);
        mEditor.putString(KEY_PASSWORD, password);
        return mEditor.commit();
    }
}

ในส่วน UserManager ผมได้ทำการคอมเม้น อธิบายรายละเอียดไว้แล้ว ฉะนั้นก็ข้ามกลับไปที่ไฟล์ LoginActivity แล้วก็มาเขียนโค๊ดตรงเมธอด checkLogin() ที่ได้สร้างไว้ก่อนหน้านี้กันครับ

ที่ไฟล์ LoginActivity.java ทำการสร้าง new instance ของ UserManager ขึ้นมา จากนั้น ก็เปลี่ยนเมธอด checkLogin() เป็นแบบนี้

public class LoginActivity extends ActionBarActivity {

    private UserManager mManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        mManager  = new UserManager(this);

    // ...
    }

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

        boolean isSuccess = mManager.checkLoginValidate(username, password);

        if (isSuccess) {
            Intent intent = new Intent(mContext, MainActivity.class);
            startActivity(intent);
        } else {
            String message = getString(R.string.login_error_message);
            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
        }
    }
}

ด้านบน ที่เมธอด `checkLogin()` เป็นการรับค่าจาก EditText ที่กรอก มาเช็คว่าค่าตรงกับที่เซฟใน SharedPreference หรือไม่ ถ้าตรง ก็ลอคอินสำเร็จ ไปเปิดคลาส `MainActivity.java` (ยังไม่ได้สร้างคลาสนี้) หากพาสเวิร์ดไม่ตรง ก็ขึ้นโชว์ error.


## สร้างหน้า Register

ต่อมาสร้างหน้า Register สำหรับหน้านี้เอาไว้ลงทะเบียน username และ password กรณีที่ยังไม่ได้บันทึกข้อมูลไว้ใน SharedPreference เริ่มแรก ก็สร้างเลเอาท์เลย ชื่อว่า `activity_register.xml` เซฟไว้ใน `res/layout/`

```xml

จะได้หน้าเลเอาท์พื้นๆ แบบนี้

Register

อัพเดทไฟล์ res/values/strings.xml

<string name="password_confirm_title">Confirm Password</string>
<string name="register_error_message">Register failed, Try again!</string>

ต่อมาสร้างคลาส RegisterActivity.java และเพิ่มโค๊ดด้านล่างนี้ลงไป

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;

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)) {
                    boolean isSuccess = mManager.registerUser(username, password);

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

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

            }
        });
    }

}

จากโค๊ดด้านบน เมื่อกดปุ่ม Register ก็จะทำการเช็ค password 2 ช่อง ว่าตรงกันหรือไม่ ถ้าตรง ก็จะทำการบันทึกข้อมูลลง SharedPreferences หากไม่สามารถบันทึกข้อมูลได้ ก็จะโชว์ error

ไม่ได้มีการเช็ค validate อะไรทั้งสิ้นนะครับ เช็คแค่ไม่ใช่ค่าว่างเฉยๆ จะใส่พาสเวิร์ดกี่ตัวก็ได้

อัพเดทไฟล์ strings.xml ใหม่ โดยเพิ่ม String เป็นแบบนี้

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

    <string name="app_name">DevahoyLogin</string>
    <string name="login_title">Devahoy</string>
    <string name="username_title">Username</string>
    <string name="password_title">Password</string>
    <string name="password_confirm_title">Confirm Password</string>
    <string name="button_login">Login</string>
    <string name="button_register">Register</string>
    <string name="register">New User? Register here</string>

    <string name="login_error_message">Username or Password is incorrect.</string>
    <string name="register_error_message">Register failed, Try again!</string>
    <string name="register_password_error">Password does not match</string>
    <string name="register_success">Register Successful</string>

    <string name="welcome_message">Welcome to Devahoy</string>

</resources>

สร้างหน้า Main

ต่อมาหน้า Main เอาไว้เวลาทำการล็อคอินถูกต้อง ก็จะเข้ามาหน้านี้ แสดงแค่โลโก้และข้อความเท่านั้นครับ เลเอาท์และไฟล์ 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:text="@string/welcome_message"
        android:id="@+id/welcome"
        android:textSize="32sp"
        android:layout_gravity="center"/>
</LinearLayout>

หน้าตาก็บ้านๆ แบบนี้ ไ่ม่มีอะไรมาก แค่โชว์โลโก้เว็บกับข้อความซะหน่อย

Main

ไฟล์ MainActivity.java

package com.devahoy.sample.login;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;

public class MainActivity extends ActionBarActivity {

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

สุดท้ายไฟล์ AndroidManifest.xml อย่าลืมทำการประกาศ Activity ทั้งหมดที่เราใช้ด้วย จะได้ดังนี้

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.devahoy.sample.login" >

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".LoginActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".RegisterActivity"
                  android:label="@string/app_name"/>

        <activity android:name=".MainActivity"
                  android:label="@string/app_name" />
    </application>

</manifest>

สรุป

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

สำหรับตัวโปรเจ็ค เอาไปลอง import ได้ครับ (เป็นโปรเจ็ค gradle นะครับ)

Buy Me A Coffee
Authors
Discord