這篇文章將說明在 APS 架構下,利用 SharedPreferences 取代傳統的暫存資料結構(如 ArrayList 或 HashMap),
達成「資料永久保存」的功能,確保 App 關閉後資料仍存在。
只需將上一篇教學裡的LoginData換成SharedPrefsManager就可以了
整個專案分為三個主要部分:
這樣的分層能符合 APS 架構原則,使資料處理與畫面顯示分離。
此 Activity 提供三個輸入欄位:
並具備兩個主要功能:
註冊功能
登入功能
同時預設建立一組管理員帳號(admin@gmail.com / 123456)方便測試。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
bindUI();
}
protected void bindUI() {
saveButton = findViewById(R.id.main_save_btn);
loginButton = findViewById(R.id.main_login_btn);
emailEditText = findViewById(R.id.main_email_et);
usernameEditText = findViewById(R.id.main_username_et);
passwordEditText = findViewById(R.id.main_password_et);
prefsManager = new SharedPrefsManager(this);
// 如果資料庫為空則預設加入 admin 帳號
if (prefsManager.getUserList().length() == 0) {
JSONObject admin = new JSONObject();
try {
admin.put("username", "admin");
admin.put("email", "admin@gmail.com");
admin.put("password", "123456");
prefsManager.saveUser(admin);
} catch (JSONException e) {
e.printStackTrace();
}
}
TextWatcher watcher = new TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { check(); }
@Override public void afterTextChanged(Editable s) {}
};
emailEditText.addTextChangedListener(watcher);
usernameEditText.addTextChangedListener(watcher);
passwordEditText.addTextChangedListener(watcher);
check();
saveButton.setOnClickListener(this::onSaveButtonClick);
loginButton.setOnClickListener(this::onLoginButtonClick);
}
private void clear() {
emailEditText.setText("");
usernameEditText.setText("");
passwordEditText.setText("");
}
private void check() {
String username = usernameEditText.getText().toString();
String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();
boolean isAllFilled = !email.isEmpty() && !password.isEmpty() && !username.isEmpty();
boolean isEmail = Patterns.EMAIL_ADDRESS.matcher(email).matches();
boolean isPassword = Pattern.matches("\\d{6,}", password);
emailEditText.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
if (!isEmail && !email.isEmpty()) {
emailEditText.setError("電子郵件格式錯誤");
} else {
emailEditText.setError(null);
}
}
});
passwordEditText.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
if (!isPassword && !password.isEmpty()) {
passwordEditText.setError("密碼需至少6碼數字");
} else {
passwordEditText.setError(null);
}
}
});
saveButton.setEnabled(isAllFilled && isEmail && isPassword);
loginButton.setEnabled(isAllFilled && isEmail && isPassword);
}
private void onSaveButtonClick(View view) {
String username = usernameEditText.getText().toString();
String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();
//呼叫在SharedPrefsManager已經寫好的prefsManager.userExists()檢查是否存在相同的用戶名稱或email
if (prefsManager.userExists(username, email)) {
Toast.makeText(this, "用戶或 Email 已存在", Toast.LENGTH_SHORT).show();
} else {
JSONObject newUser = new JSONObject();
try {
newUser.put("username", username);
newUser.put("email", email);
newUser.put("password", password);
prefsManager.saveUser(newUser);
Toast.makeText(this, "已儲存", Toast.LENGTH_SHORT).show();
clear();
} catch (JSONException e) {
e.printStackTrace();
}
}
}
private void onLoginButtonClick(View view) {
String username = usernameEditText.getText().toString();
String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();
check();
if (prefsManager.isLoginValid(username, email, password)) {
Intent intent = new Intent(this, HomeActivity.class);
intent.putExtra("username", username);
startActivity(intent);
Toast.makeText(this, "Hello, " + username, Toast.LENGTH_SHORT).show();
clear();
} else {
Toast.makeText(this, "登入失敗", Toast.LENGTH_SHORT).show();
}
}
登入成功後會跳轉到 HomeActivity。
此畫面透過 SharedPrefsManager 讀取資料,並根據使用者角色顯示不同內容:
這樣的設計可以模擬簡單的權限系統,讓管理員與一般使用者看到的資訊有所區別。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_home);
bindUI();
}
protected void bindUI() {
getdataButton = findViewById(R.id.home_getdata_btn);
backButton = findViewById(R.id.home_back_btn);
showdataTextView = findViewById(R.id.home_showdata_tv);
backButton.setOnClickListener(v -> finish());
//將點擊事件獨立出去
getdataButton.setOnClickListener(this::onGetDataButtonClick);
}
protected void onGetDataButtonClick(View view) {
Intent intent = getIntent();
String username = intent.getStringExtra("username");
SharedPrefsManager prefsManager = new SharedPrefsManager(this);
//取得所有使用者的資料
JSONArray users = prefsManager.getUserList();
StringBuilder sb = new StringBuilder();
//如果使用者名稱為admin則顯示所有使用者的資料
if ("admin".equals(username)) {
// 顯示所有使用者
for (int i = 0; i < users.length(); i++) {
//從user中取出第 i 筆資料,並轉成 JSONObject 物件。
//JSONObject:用來表示一組鍵值資料(Key-Value)的類別
JSONObject user = users.optJSONObject(i);
if (user != null) {
sb.append(String.format("帳號%d:\n用戶名:%s\n電子郵件:%s\n密碼:%s\n\n",
i + 1,
//取出這個使用者的「用戶名」欄位(key 為 "username")
user.optString("username"),
user.optString("email"),
user.optString("password")));
}
}
} else {
// 顯示自己
boolean found = false;
for (int i = 0; i < users.length(); i++) {
JSONObject user = users.optJSONObject(i);
//如果user != null且和尋找到的這筆資料的username相同,則顯示此筆資料
if (user != null && username.equals(user.optString("username"))) {
sb.append(String.format("用戶名:%s\n電子郵件:%s\n密碼:%s\n",
user.optString("username"),
user.optString("email"),
user.optString("password")));
//表示找到了,停止迴圈
found = true;
break;
}
}
//沒找到(found = false)
if (!found) sb.append("找不到使用者資料");
}
//設置顯示的資料
showdataTextView.setText(sb.toString());
}
SharedPrefsManager 是專門管理 SharedPreferences 操作的類別。
它提供四項核心功能:
使用 JSON 儲存多筆資料的方式簡潔且直觀,適合簡易登入系統。
public class SharedPrefsManager {
private static final String PREF_NAME = "UserPrefs"; //在 Android 裡建立 SharedPreferences 時指定的檔案名稱
private static final String KEY_USERS = "users"; //存放「所有用戶資料」這筆資料在 SharedPreferences 裡的 key(鍵名)
private SharedPreferences prefs; //用來操作 SharedPreferences 的物件
// 建構子,傳入 Context 取得 SharedPreferences
public SharedPrefsManager(Context context) {
// Android 系統取得名為 PREF_NAME的 SharedPreferences 物件
//Context.MODE_PRIVATE:表示這份 SharedPreferences 只能被本 App 存取,不會跟其他 App 共享。
prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
// getUserList()被呼叫則取得所有用戶資料(JSONArray 格式)
public JSONArray getUserList() {
String json = prefs.getString(KEY_USERS, "[]"); // 沒資料預設回傳空陣列
try {
return new JSONArray(json); // 成功時把字串 json 解析成 JSONArray並回傳
} catch (JSONException e) {
return new JSONArray(); // 格式錯誤時回傳空陣列
}
}
// 儲存一個新用戶(加入陣列並覆蓋原本資料)
public void saveUser(JSONObject user) {
JSONArray users = getUserList(); // 先取得現有所有用戶
users.put(user); // 新用戶加進陣列
prefs.edit().putString(KEY_USERS, users.toString()).apply(); // 轉字串存回 SharedPreferences
}
// 檢查是否已有相同 username 或 email 的用戶
public boolean userExists(String username, String email) {
//取得所有用戶資料
JSONArray users = getUserList();
//迴圈從第一個開始檢查
for (int i = 0; i < users.length(); i++) {
JSONObject u = users.optJSONObject(i);
// 若 username 或 email 有重複就回傳 true
if (u != null && (username.equals(u.optString("username")) || email.equals(u.optString("email")))) {
return true;
}
}
return false; // 沒有重複回傳 false
}
// 檢查使用者登入資料是否正確(需 username、email、password 全部符合才算成功)
public boolean isLoginValid(String username, String email, String password) {
JSONArray users = getUserList();
for (int i = 0; i < users.length(); i++) {
JSONObject u = users.optJSONObject(i);
if (u != null &&
username.equals(u.optString("username")) &&
email.equals(u.optString("email")) &&
password.equals(u.optString("password"))) {
return true; // 找到完全符合的用戶回傳 true
}
}
return false; // 沒有找到符合的回傳 false
}
// 取得所有用戶資料(和 getUserList 一樣)
public JSONArray getAllUsers() {
return getUserList();
}
// 依照 username 取得單一用戶的資料(找不到就回傳 null)
public JSONObject getUser(String username) {
JSONArray users = getUserList();
for (int i = 0; i < users.length(); i++) {
JSONObject u = users.optJSONObject(i);
if (username.equals(u.optString("username"))) return u;
}
return null;
}
// 清空 SharedPreferences 所有資料
public void clear() {
prefs.edit().clear().apply();
}
}