Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Data Storage and Access with SharedPreferences for User Preferences

Tech May 13 1
  1. SharedPreferences Usage Example

Workflow:

[Image: flowchart showing saving and reading preferences]

Code Example:

Result:

After entering username and password and clicking login, the information is saved to a SharedPreferences file. When the app is restarted, the data appears in the text fields.

[Image: screenshot showing data loaded]

After saving, you can navigate to data/data/<package-name> in the File Explorer and see an XML file generated under the shared_prefs directory (shown below from a previous device without root):

[Image: shared_prefs directory]

Exporting to desktop reveals the file content:

[Image: XML content]

Implementation:

Layout file activity_main.xml:

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="User Login" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="Enter Username" />

    <EditText
        android:id="@+id/editName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Username" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Enter Password" />

    <EditText
        android:id="@+id/editPassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Password"
        android:inputType="textPassword" />

    <Button
        android:id="@+id/btnLogin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Login" />
</LinearLayout>

Simple SharedPreferences helper class: SharedHelper.java

public class SharedHelper {
    private Context mContext;

    public SharedHelper() {}

    public SharedHelper(Context context) {
        this.mContext = context;
    }

    // Save username and password to SharedPreferences
    public void save(String username, String password) {
        SharedPreferences sp = mContext.getSharedPreferences("mysp", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        editor.putString("username", username);
        editor.putString("password", password);
        editor.apply(); // Use apply() for asynchronous saving
        Toast.makeText(mContext, "Data saved to SharedPreferences", Toast.LENGTH_SHORT).show();
    }

    // Read data from SharedPreferences
    public Map<String, String> read() {
        Map<String, String> data = new HashMap<>();
        SharedPreferences sp = mContext.getSharedPreferences("mysp", Context.MODE_PRIVATE);
        data.put("username", sp.getString("username", ""));
        data.put("password", sp.getString("password", ""));
        return data;
    }
}

Finally, the MainActivity.java:

public class MainActivity extends AppCompatActivity {
    private EditText editName, editPassword;
    private Button btnLogin;
    private String usernameStr, passwordStr;
    private SharedHelper sharedHelper;
    private Context appContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        appContext = getApplicationContext();
        sharedHelper = new SharedHelper(appContext);
        bindViews();
    }

    private void bindViews() {
        editName = findViewById(R.id.editName);
        editPassword = findViewById(R.id.editPassword);
        btnLogin = findViewById(R.id.btnLogin);
        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                usernameStr = editName.getText().toString();
                passwordStr = editPassword.getText().toString();
                sharedHelper.save(usernameStr, passwordStr);
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        Map<String, String> data = sharedHelper.read();
        editName.setText(data.get("username"));
        editPassword.setText(data.get("password"));
    }
}
  1. Reading SharedPreferences from Another Application

Key concept: Obtain the Context of another app. The Context is the interface to access the app's global information, and the unique identifier is the package name. Using createPackageContext, we can get the corresponding Context. Note: Reading another app's SharedPreferences is only possible if the target app's preferences file was created with a readable permission (e.g., MODE_WORLD_READABLE). By default, MODE_PRIVATE is used, so external access is restricted. Typically, sensitive data like password are encrypted anyway. This example is for educational purposes only.

Workflow:

[Image: flowchart showing inter-app preferences access]

Code Example:

Result:

[Image: Toast showing data read from another app]

Implementation:

All logic is inside MainActivity.java. When the button is clicked, it reads the SharedPreferences from the other app and displays the data via Toast:

public class MainActivity extends AppCompatActivity {
    private Context otherContext;
    private SharedPreferences sp;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnShow = findViewById(R.id.btnShow);
        btnShow.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    // Get context of the target app by its package name
                    otherContext = createPackageContext("com.jay.sharedpreferencedemo", Context.CONTEXT_IGNORE_SECURITY);
                } catch (PackageManager.NameNotFoundException e) {
                    e.printStackTrace();
                    return;
                }
                // Obtain SharedPreferences from that context
                sp = otherContext.getSharedPreferences("mysp", Context.MODE_WORLD_READABLE);
                String name = sp.getString("username", "");
                String passwd = sp.getString("password", "");
                Toast.makeText(getApplicationContext(), 
                    "Data from Demo1's SharedPreference:\nUsername: " + name + "\nPassword: " + passwd, 
                    Toast.LENGTH_SHORT).show();
            }
        });
    }
}
  1. Encrypting Important Data with MD5

Storing passwords directly in SharedPreferences is risky if the device is rooted. As responsible developers, we should encrypt sensitive data like passwords. Below is a simple encryption flow and an example using MD5.

3.1 Simple Encryption Flow

Flowchart:

[Image: encryption flow diagram]

Explanation:

  • Step 1: User registers with username and password. After validation (username uniqueness, password length > 6, etc.), the plain password is encrypted locally before submission to the server.
  • Step 2: Server stores the encrypted password (not the plain text) in the database.
  • Step 3: If the app saves credentials to SharedPreferences after registration/login, it must also encrypt the password. Alternatively, if not saving, the plain password is encrypted each time before sending a request.
  • Step 4: Server verifies the account and encrypted password, and upon success, assigns a session identifier (e.g., token) for subsequent requests.

There are many encryption algorithms. This section introduces MD5 and demonstrates its usage.

3.2 Introduction to MD5

1) What is MD5?

MD5 (Message Digest Algorithm 5) is a widely used cryptographic hash function that produces a 128-bit (16-byte) hash value. Its commonly used for data integrity verification. The hash is usually expressed as a 32-digit hexadecimal number. Some implementations show 16-digit hashes, which omit the first and last 8 digits. For example, an online MD5 decoder can check this.

2) Can MD5 be reversed?

MD5 is a one-way function; it cannot be decrypted back to the original data. However, brute-force attacks and rainbow table lookups can find collisions or original strings for simple values.

3) Is an MD5 hash unique?

Not necessarily. While each input produces a unique hash, multiple inputs can theoretically produce the same hash (collision). However, collisions are rare in practice.

3.3 MD5 Encryption Example

Below is a utility class for MD5 hashing:

MD5Util.java

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Util {
    public static String getMD5(String content) {
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.update(content.getBytes());
            return bytesToHex(digest.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder builder = new StringBuilder();
        for (byte b : bytes) {
            builder.append(Integer.toHexString((b >> 4) & 0xf));
            builder.append(Integer.toHexString(b & 0xf));
        }
        return builder.toString();
    }

    // Hash 100 times for extra security
    public static String getMD5x100(String content) {
        String result = content;
        for (int i = 0; i < 100; i++) {
            result = getMD5(result);
        }
        return result;
    }
}

Usage in MainActivity:

Log.e("HeHe", MD5Util.getMD5("hello"));

Logcat output:

5d41402abc4b2a76b9719d911017c592

Decoding this hash online shows it matches "hello". To increase security, we can hash multiple times (e.g., 100 times) to make rainbow table attacks less effective.

  1. SharedPreferences Utility Class

To avoid repeated instantiation, here is a general-purpose utility class inspired by Hongyang's blog.

SPUtils.java

import android.content.Context;
import android.content.SharedPreferences;
import java.util.Map;

public class SPUtils {
    public static final String FILE_NAME = "my_sp";

    /** Save data to SharedPreferences */
    public static void put(Context context, String key, Object value) {
        SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        if (value instanceof Boolean) {
            editor.putBoolean(key, (Boolean) value);
        } else if (value instanceof Float) {
            editor.putFloat(key, (Float) value);
        } else if (value instanceof Integer) {
            editor.putInt(key, (Integer) value);
        } else if (value instanceof Long) {
            editor.putLong(key, (Long) value);
        } else {
            editor.putString(key, (String) value);
        }
        editor.apply();
    }

    /** Get a value by key, with a default value */
    public static Object get(Context context, String key, Object defaultObject) {
        SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
        if (defaultObject instanceof Boolean) {
            return sp.getBoolean(key, (Boolean) defaultObject);
        } else if (defaultObject instanceof Float) {
            return sp.getFloat(key, (Float) defaultObject);
        } else if (defaultObject instanceof Integer) {
            return sp.getInt(key, (Integer) defaultObject);
        } else if (defaultObject instanceof Long) {
            return sp.getLong(key, (Long) defaultObject);
        } else if (defaultObject instanceof String) {
            return sp.getString(key, (String) defaultObject);
        }
        return null;
    }

    /** Remove a key-value pair */
    public static void remove(Context context, String key) {
        SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
        sp.edit().remove(key).apply();
    }

    /** Clear all data */
    public static void clear(Context context) {
        SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
        sp.edit().clear().apply();
    }

    /** Check if a key exists */
    public static boolean contains(Context context, String key) {
        SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
        return sp.contains(key);
    }

    /** Get all key-value pairs */
    public static Map<String, ?> getAll(Context context) {
        SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
        return sp.getAll();
    }
}
  1. Download Source Code

SharedPreferenceDemo.zip: Download SharedPreferenceDemo.zip
SharedPreferenceDemo2.zip: Download SharedPreferenceDemo2.zip
SharedPreferenceDemo3.zip: Download SharedPreferenceDemo3.zip

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.