Tuesday, November 11, 2025

Android java: using DataLive for orientation change

I test it in Android 10 xiaomi mia2

What will happen when orientation change e.g. screen change or language change?

the object in activity will be destroyed. here is the sequence:

  • after orientation change
    V  onPause
    V  onStop
    V  onDestrouy
  • after flusing all data
    V  onCreate
    V  onCreate myClass :null
    V  MyClass constructor
    V  onCreateView myClass.getRandom
    V  onStart
    V  onResume 

At xiomi mia2, override public void onConfigurationChanged(Configuration newConfig) method did not called at all during screen orientation change, not appearred in logcat.

Base on the android behave, these are the solution can be used to keep the object durin configuration change:

  1. Local persistence to handle process death for complex or large data. Persistent local storage includes databases or DataStore.
  2. Retained objects such as ViewModel instances to handle UI-related state in memory while the user is actively using the app.
  3. Saved instance state to handle system-initiated process death and keep transient state that depends on user input or navigation. 

ViewModel is recommended used for today an future, These are some options:

  1. ViewModel    Holds UI data & logic    Core architecture component
  2. ComputableLiveData (Deprecated)
  3. MediatorLiveData    Combines multiple LiveData sources    Merging streams
  4. MutableLiveData    Observable, mutable data    UI updates
  5. SavingStateLiveData    LiveData with saved-state persistence    Restore after process death 

Comparison between AndroidViewModel vss ViewModel

AndroidViewModel require Context for DB, prefs, etc. gemini: Avoid if possible. Only use when you absolutely need Context. violates a core principle of good architecture: separation of concerns and testability.
ViewModel UI logic without needing Context Network requirement may use this, it does not need context

NOTE: Don’t store Activity or Fragment in VieModel nor AndroidViewModel! 

These are the java code for education, first text using direct access to object in activity, and second text using live data and AndroidViewModel. this code used AndroidViewModel, because I need application's context to access sqlite.

-- MyClass.java

package com.dedetok.testdataorientation;

import android.content.Context;
import android.util.Log;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;

import java.util.Random;

public class MyClass {
    // dummy
    Context context;

    boolean isCreated = false;
    int randomNumber=-1;

    public MyClass(Context context) {
        this.context = context;
        Log.v("deddetok", "MyClass constructor"); // debug

    }

    public String getRandom() {
        if (!isCreated) {
            Random random = new Random();
            randomNumber = random.nextInt(100);
            isCreated = true;
        }
        return String.valueOf(randomNumber);
    }

    public LiveData<String> getRandomLive() {
        if (!isCreated) {
            Random random = new Random();
            randomNumber = random.nextInt(100);
            isCreated = true;
        }
        MutableLiveData<String> returnValue = new MutableLiveData<>();
        returnValue.setValue(String.valueOf(randomNumber));

        return returnValue;
    }
}

-- MyAndroidViewModel

package com.dedetok.testdataorientation;

import android.app.Application;

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

public class MyAndroidViewModel extends AndroidViewModel {
    MyClass myClass;

    public MyAndroidViewModel(@NonNull Application application) {
        super(application);
        myClass = new MyClass(application);
    }

    public LiveData<String> getData() {
        return myClass.getRandomLive();
    }
}

-- MainActivity.java

package com.dedetok.testdataorientation;

import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.lifecycle.ViewModelProvider;

public class MainActivity extends AppCompatActivity {

    MyClass myClass = null;

    AppCompatTextView myTextView1, myTextView2;

    MyAndroidViewModel myAndroidViewModel;

    /* to preserve myclass
     * 1. Local persistence to handle process death for complex or large data. Persistent local storage includes databases or DataStore.
     * 2. Retained objects such as ViewModel instances to handle UI-related state in memory while the user is actively using the app.
     * 3. Saved instance state to handle system-initiated process death and keep transient state that depends on user input or navigation.
     */

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        Log.v("dedetok", "onCreate"); // debug


        myTextView1 = findViewById(R.id.textView1);
        myTextView2 = findViewById(R.id.textView2);

        Log.v("dedetok", "onCreate myClass :"+myClass); //
        // myTextView1
        if (myClass==null) {
            myClass = new MyClass(this);
        }
        myTextView1.setText(myClass.getRandom()); // work, created in memory

        // ✅ Get ViewModel (it survives rotation)
        // myTextView2
        myAndroidViewModel = new ViewModelProvider(this).get(MyAndroidViewModel.class);
        myAndroidViewModel.getData().observe(this, value -> {
            // 'value' is a plain String here
            myTextView2.setText(value);
        }); //

    }

    @Nullable
    @Override
    public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
        Log.v("dedetok", "onCreateView"); // debug
        //Log.v("dedetok", "onCreateView myClass.getRandom"); // debug
        //myTextView.setText(myClass.getRandom()); // crash textview not ready
        return super.onCreateView(parent, name, context, attrs);
    }

    /*
     * ## activity life cycle 2 ##
     */
    @Override
    protected void onStart() {
        super.onStart();
        Log.v("dedetok", "onCreateView myClass.getRandom"); // debug
        //myTextView1.setText(myClass.getRandom()); // work, view has been created
        myAndroidViewModel.getData().observe(this, value -> {
            // 'value' is a plain String here
            myTextView2.setText(value);
        }); // this is fine place

        Log.v("dedetok", "onStart"); // debug

    }

    /*
     * ## activity life cycle 3 ##
     */
    @Override
    protected void onResume() {
        super.onResume();

        Log.v("dedetok", "onResume"); // debug


    }

    /*
     * ## activity life cycle 4 ##
     */
    @Override
    protected void onPause() {
        Log.v("dedetok", "onPause"); // debug
        super.onPause();

    }

    /*
     * ## activity life cycle 5 ##
     */
    @Override
    protected void onStop() {
        Log.v("dedetok", "onStop"); // debug
        super.onStop();

    }

    /*
     * ## activity life cycle 6 ##
     */
    @Override
    protected void onDestroy() {
        Log.v("dedetok", "onDestrouy"); // debug

        super.onDestroy();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.v("dedetok", "onConfigurationChanged"); // debug
    }
}

-- activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:minHeight="50dp"
        android:textSize="24sp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        />
    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView2"
        android:minHeight="50dp"
        android:textSize="24sp"
        app:layout_constraintTop_toBottomOf="@+id/textView1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        />
</androidx.constraintlayout.widget.ConstraintLayout>

Some of parts of this content generated by ChatGPT and Gemini with some modification.