Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Using AndroidViewModel to Access Application Context and Persist Data with SharedPreferences

Tech May 9 4

This example demonstrates a basic counter application that retains its state across process death by leveraging AndroidViewModel, SavedStateHandle, and SharedPreferences.

AndroidViewModel extends the base ViewModel class and provides access to the application context via getApplication(). This allows safe access to global resources such as strings, shared preferences, or system services without leaking activity contxets.

The implementation uses:

  • SavedStateHandle to retain UI-related data during configuration changes.
  • LiveData to observe changes and automatically update the UI.
  • SharedPreferences for persistent storage—ensuring data survives app restarts or device reboots.

MyViewModel.java

package com.example.viewmodelshp;

import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.SavedStateHandle;

public class CounterViewModel extends AndroidViewModel {
    private final SavedStateHandle savedState;
    private final String dataKey;
    private final String prefsName;

    public CounterViewModel(@NonNull Application app, SavedStateHandle handle) {
        super(app);
        this.savedState = handle;
        this.dataKey = app.getResources().getString(R.string.data_key);
        this.prefsName = app.getResources().getString(R.string.shp_name);

        if (!savedState.contains(dataKey)) {
            restoreFromDisk();
        }
    }

    public LiveData<Integer> getCurrentValue() {
        return savedState.getLiveData(dataKey);
    }

    private void restoreFromDisk() {
        SharedPreferences prefs = getApplication().getSharedPreferences(prefsName, Context.MODE_PRIVATE);
        int savedValue = prefs.getInt(dataKey, 0);
        savedState.set(dataKey, savedValue);
    }

    public void persistToDisk() {
        Integer current = getCurrentValue().getValue();
        if (current == null) current = 0;

        SharedPreferences prefs = getApplication().getSharedPreferences(prefsName, Context.MODE_PRIVATE);
        prefs.edit().putInt(dataKey, current).apply();
    }

    public void increment(int delta) {
        Integer current = getCurrentValue().getValue();
        savedState.set(dataKey, (current == null ? 0 : current) + delta);
    }
}

MainActivity.java

package com.example.viewmodelshp;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.SavedStateViewModelFactory;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;

import com.example.viewmodelshp.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    private CounterViewModel viewModel;
    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        viewModel = new ViewModelProvider(
            this,
            new SavedStateViewModelFactory(getApplication(), this)
        ).get(CounterViewModel.class);

        binding.setViewModel(viewModel);
        binding.setLifecycleOwner(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        viewModel.persistToDisk();
    }
}

String Resources (res/values/strings.xml)

<resources>
    <string name="app_name">ViewModelSHP</string>
    <string name="button_plus">+</string>
    <string name="button_minus">-</string>
    <string name="data_key">counter_value</string>
    <string name="shp_name">app_preferences</string>
</resources>

Data Binding Layout (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="viewModel"
            type="com.example.viewmodelshp.CounterViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(viewModel.currentValue)}"
            android:textSize="30sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.3"
            tools:text="0" />

        <Button
            android:id="@+id/button_minus"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{() -> viewModel.increment(-1)}"
            android:text="@string/button_minus"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.76"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.48" />

        <Button
            android:id="@+id/button_plus"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{() -> viewModel.increment(1)}"
            android:text="@string/button_plus"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.24"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.48" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

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.