Thay đổi cấu hình Android

Trong quá trình thực thi, một ứng dụng Android có thể thay đổi cấu hình phụ thuộc vào tương tác người dùng hay sự kiện thiết bị. Một thay đổi phổ biến là xoay màn hình thiết bị Android (tức là chuyển từ chế độ Portrait sang Landscape và ngược lại). Quá trình thay đổi này có thể dẫn đến mất kết nối giữa dữ liệu và giao diện người dùng. Để hiểu rõ hơn chúng ta sẽ xét một ứng dụng Android đơn giản gọi là My ViewModel App với giao diện gồm một TextView và một Button (tập tin activity_main.xml) như sau:

<TextView
  android:id="@+id/txt"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="Hello World!"
  android:textSize="50dp"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintHorizontal_bias="0.496"
  app:layout_constraintLeft_toLeftOf="parent"
  app:layout_constraintRight_toRightOf="parent"
  app:layout_constraintTop_toTopOf="parent"
  app:layout_constraintVertical_bias="0.251" />

<Button
  android:id="@+id/btn"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="Change Name"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toTopOf="parent" />

Nội dung của tập tin MainActivity.java như sau:

public class MainActivity extends AppCompatActivity {

  Button btn;
  TextView txt;
  String myName = "";
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    txt = (TextView)findViewById(R.id.txt);
    btn = (Button)findViewById(R.id.btn);
    txt.setText(myName);
    btn.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
          change_name("Ngoc Minh");
       }
    });
  }
  public void change_name(String newName){
    myName = newName;
    txt.setText(myName);
  }
}

Chúng ta có một biến kiểu StringmyName lưu trữ thông tin hay dữ liệu người dùng và sẽ hiển thị ra TextView nếu người dùng nhấn vào nút Change Name:

Nếu người dùng xoay màn hình sang chế độ Landscape (ngang) dòng chữ Ngoc Minh sẽ biến mất:

Nguyên nhân của việc này là:

  • Dữ liệu được lưu trong biến myName được khai báo trong lớp Activity
  • Khi màn hình thiết bị Android xoay, Activity sẽ được khởi tạo lại với tất cả các thành phần bên trong nó như hình ảnh dưới đây:

Chúng ta để ý, khi thiết bị Android xoay (Activity rotated), Activity sẽ thay đổi trạng thái từ onPause đến onResume và nó sẽ xóa sạch những thông tin cấu hình trước đó (bao gồm cả dữ liệu từ biến myName).

Lớp ViewModel

Năm 2017, Google đã giới thiệu lớp ViewModel với mục đích lưu trữ và giữ cho dữ liệu liên quan đến giao diện người dùng Android không bị mất mát khi thay đổi cấu hình (như minh họa trên). Để sử dụng lớp ViewModel chúng ta cần thực hiện các bước thay đổi trong ứng dụng My ViewModel App như sau:

  • Cấu hình lớp ViewModel trong dependencies trong build.gradle

Thông tin cấu hình về ViewModel có thể lấy từ trang https://developer.android.com/jetpack/androidx/releases/lifecycle#declaring_dependencies (Có thể xem video bên dưới về cách thực hiện)

  • Khai báo lớp kế thừa từ lớp ViewModel (ví dụ MyViewModel) chứa dữ liệu liên quan đến giao diện như sau:

import androidx.lifecycle.ViewModel;

public class MyViewModel extends ViewModel {
  public String myName = "";
}

Để ý rằng, biến myName lúc này được khai báo trong lớp MyViewModel chứ không phải khai báo trực tiếp trong lớp MainActivity (hay giao diện người dùng). Điều này có nghĩa rằng chúng ta đã tách dữ liệu và giao diện độc lập nhau.

  • Vì dữ liệu (lớp MyViewModel) và giao diện (lớp MainActivity) độc lập nhau nên chúng ta cần kết nối hai thành phần này dùng lớp ViewModelProvider
MyViewModel myModel = new ViewModelProvider(this).get(MyViewModel.class);

Từ khóa this chỉ Activity hiện tại (hay fragment sẽ được đề cập trong một bài khác) và tham số của get chính là lớp MyViewModel.

– Sử dụng dữ liệu từ ViewModel trong Activity (hay fragment)


Button btn;
TextView txt;
MyViewModel myModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  txt = (TextView)findViewById(R.id.txt);
  btn = (Button)findViewById(R.id.btn);
  myModel = new ViewModelProvider(this).get(MyViewModel.class);
  txt.setText(myModel.myName);
  btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
      change_name("Ngoc Minh");
    }
  });
 }
public void change_name(String newName){
    myModel.myName = newName;
    txt.setText(myModel.myName);
}

Bây giờ chúng ta thực thi lại ứng dụng My ViewModel App, nhấn Button và xoay màn hình:

Vì dữ liệu (chuỗi Ngoc Minh) được lưu trữ trong lớp ViewModel độc lập với giao diện (Activity) nên sẽ không bị mất mát khi cấu hình giao diện thay đổi. Cách tạo và thực thi ứng dụng có thể tham khảo từ video sau đây:

Mã các tập tin từ chương trình My ViewModel App tham khảo tại https://github.com/TranNgocMinh/Kotlin-and-Android/tree/master/CodeList/CodeList19