Android Mastery by Dan Tech
Android Mastery by Dan Tech

Android Lifecycle: Sự khác biệt giữa các cách khởi chạy Fragment trong ứng dụng Android

Tiếp nối bài viết về Android Lifecycle. Trong bài này mình sẽ viết chi tiết, kỹ hơn về sự khác biệt giữa các kiểu launch Fragment trong một ứng dụng Android.

Ôn lại kiến thức Fragment

Fragment là gì

Fragment là đơn vị thể hiện giao diện trong một ứng dụng Android. Một Android Activity có thể chứa được nhiều Fragment, trong mỗi Fragment cũng có thể chứa nhiều Fragment con bên trong nó.

Một điều đặc biệt ở Fragment là ta có thể sử dụng Fragment như là 1 Layout View – điều đó có nghĩa là trên 1 màn hình tại 1 thời điểm có thể có nhiều hơn 1 Fragment được hiển thị và điều này được hệ điều hành Android cho phép. Tuy nhiên vì vấn đề về hiệu năng của Fragment không tối ưu cho việc này nên cách dùng nhiều Fragment được cùng hiển thị trên 1 màn hình Activity không được khuyến khích từ cộng đồng.

Quản lý Fragment trong Activity

Trong Android ta có thể quản lý Fragment thông qua class AppCompatActivity, đây là 1 class kế thừa từ FragmentActivity. Khi đọc sâu vào code bên trong của FragmentActivity bạn sẽ thấy một hàm get tên là supportFragmentManager. AppCompatActivity / FragmentActivity sẽ dùng supportFragmentManager này để quản lý vòng đời của các Fragment con bên trong nó.

Quản lý Fragment trong Fragment

Nhìn 1 chút qua class Fragment của chúng ta. Trong class Fragment sẽ có 2 FragmentManager, 1 cái là getParentFragmentManager và cái còn lại là getChildFragmentManager. Hãy để mình giải thích cho bạn điểm khác biệt giữa 2 FragmentManager này.

  • getParentFragmentManager: Đây là Fragment Manager mà Fragment hiện tại thuộc về. 1 cách dễ hiểu thì trong trường hợp Fragment được show từ Activity thì đây chính là supportFragmentManager của Activity đó hiện tại.
  • getChildFragmentManager: Đây là Fragment Manager mà Fragment tự tạo ra để quản lý các Fragment con bên trong nó.

FAQ:

  1. Giả sử trường hợp FragmentA sử dụng getParentFragmentManager để show một FragmentB. Thì lúc này getParentFragmentManager của FragmentB là gì?
  2. Giả sử trường hợp FragmentA sử dụng getChildFragmentManager để show một FragmentB. Thì lúc này getParentFragmentManager của FragmentB là gì?

Hãy nhớ những kiến thức này nhé! Ngày nay ta có sự giúp sức của Jetpack Navigation Component trong việc quản lý navigation giữa nhiều Fragment, tuy nhiên am hiểu các hoạt động của supportFragmentManager là cần thiết để bạn nắm rõ hệ thống hơn, rất hữu ích cho việc Debug.

Các hàm của FragmentManager dùng để launch một Fragment

Hàm beginTransaction.add()

Có rất nhiều hàm add trong fragmentManager.beginTransaction, điều đó khiến cho việc học Fragment trở nên khó khăn vì người học không thể biết hết cách dùng của mỗi hàm bên trong chúng. Hàm add mình đã nói đến ở đây là

@NonNull
public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment) {
    doAddOp(containerViewId, fragment, null, OP_ADD);
    return this;
}

Hàm nhận vào một containerViewId và một Fragment object.

containerViewId là View Id đã được định nghĩa trước đó trong file Layout XML (của Fragment, hoặc FragmentActivity layout). Mục đích truyền View Id này để FragmentManager tìm đến đúng ViewId đó và ‘add’ Fragment object lên trên View Id đã tìm được.

Việc sử dụng hàm add thú vị ở chỗ. Nếu containerViewId là một layout rỗng, nó sẽ add Fragment object lên trên cùng, nhưng nếu containerViewId đã có nội dung bên trong nó, hoặc thậm chí đã có Fragment bên trong thì ta vẫn có thể add được. Và Fragment được add vào sau sẽ được ở trên cùng. Hãy tham khảo source code của mình tại GitHub Repo này và tự tùy biến theo ý của bạn nhé.

Hàm beginTransaction.replace()

Tương tự như hàm add, đây là hàm replace tôi đang nói đến

@NonNull
public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment,
        @Nullable String tag)  {
    if (containerViewId == 0) {
        throw new IllegalArgumentException("Must use non-zero containerViewId");
    }
    doAddOp(containerViewId, fragment, tag, OP_REPLACE);
    return this;
}

Hàm cũng nhận vào một containerViewIdFragment object để thể hiện một Fragment.

Về yêu cầu của containerViewId và Fragment sẽ hoàn toàn tương tự như hàm add. Tuy nhiên ở đây vì là hàm replace nên FragmentManager sẽ thay thế (replace) Fragment hiện đang có trong containerViewId bằng Fragment mới. Điều này có nghĩa là các Fragment đã có sẵn trong containerViewId sẽ bị remove ra khỏi và thay thế bằng Fragment mới.

Bạn hãy thử trải nghiệm bằng cách checkout GitHub Repo này của mình về và chạy các ví dụ nhé. Đảm bảo sẽ rất thú vị đấy!

Lưu trạng thái backstack trong FragmentManager

Một vấn đề mà newbie gặp phải là khi ta đã thành thạo và hiểu rõ được hàm add, replace bên trong FragmentManager rồi. Giờ làm cách nào để có thể quản lý chúng trong một Backstack.

Backstack ở đây được hiểu là một Stack quản lý các thứ tự trạng thái. Mô hình này được ứng dụng giúp chúng ta quản lý các màn hình một cách dễ dàng. Backstack giúp ghi nhớ các màn hình được show, và recover lại chúng trong các trường hợp cần thiết.

Trường hợp cụ thể:

App show MainActivity, sau đó add FragmentA vào MainActivity. Lúc này User đang ở FragmentA, sau đó User thực hiện một thao tác để chuyển tiếp sang FragmentB để đọc một vài thông tin. Sau khi sử dụng xong FragmentB, User có nhu cầu back lại FragmentA. Vậy lập trình viên cần làm gì để show lại FragmentA cho User?

Để xử lý cho trường hợp này ta không thể gọi hàm add, hoặc replace một lần nữa để show mới FragmentA. Bởi vì trạng thái của FragmentA trước đó chưa được ta lưu lại, việc show một FragmentA mới sẽ yêu cầu thêm logic để restore lại trạng thái của FragmentA trước đó cho đúng.

Các lập trình viên Android của Google họ hiểu được nhu cầu này nên đã trang bị cho FragmentManager một hàm tên là addToBackStack. Hàm addToBackStack sẽ lưu các dữ liệu của các Fragment được add vào một containerViewId và tự động restore lại trong các trường hợp cần thiết (Các trường hợp: pop Fragment Backstack, User sử dụng back button, hoặc activity re-created)

Các hàm beginTransaction# remove, hide, show, …

Bản chất bên trong của FragmentManager là một cấu trúc dữ liệu phức tạp giúp lập trình viên có thể thêm, xóa, thay thế, ẩn, hiện các Fragment bên trong Activity hoặc Fragment … Nên các lập trình viên Android của Google đã hỗ trợ đầy đủ các hàm để chúng ta có thể tự tay quản lý các Fragment của mình thông qua FragmentManager. Tuy nhiên ở góc độ làm nghề, mình cũng hiếm khi phải thuộc lòng hết tất cả các hàm này và vận dụng chúng. Cái chúng ta cần là hiểu được cách hoạt động tổng quát và biết cách dùng khi cần thiết. Nếu bạn có nhu cầu đi sâu hơn về các hàm remove, hide, show, … hãy tìm đến document của Google để đọc được nhiều hơn. Trong phạm vi bài viết này mình sẽ không nêu thêm ví dụ cho các hàm này.

Cách hình dung và hiểu Fragment Transaction

Nếu bạn đọc vào source code của FragmentManager sẽ thấy hàm beginTransaction

@NonNull
public FragmentTransaction beginTransaction() {
    return new BackStackRecord(this);
}

Ở đây có thể hiểu một Transaction chính là 1 BackstackRecord (BackstackRecord kế thừa class FragmentTransaction), và BackstackRecord này được FragmentManager quản lý. Bên trong BackstackRecord sẽ chứa các cấu trúc dữ liệu để quản lý mỗi action tương tác lên FragmentManager.

private final FragmentFactory mFragmentFactory;
private final ClassLoader mClassLoader;

ArrayList<Op> mOps = new ArrayList<>();
int mEnterAnim;
int mExitAnim;
int mPopEnterAnim;
int mPopExitAnim;
int mTransition;
boolean mAddToBackStack;
boolean mAllowAddToBackStack = true;
@Nullable String mName;

int mBreadCrumbTitleRes;
CharSequence mBreadCrumbTitleText;
int mBreadCrumbShortTitleRes;
CharSequence mBreadCrumbShortTitleText;

ArrayList<String> mSharedElementSourceNames;
ArrayList<String> mSharedElementTargetNames;
boolean mReorderingAllowed = false;

ArrayList<Runnable> mCommitRunnables;

Các cấu trúc dữ liệu sử dụng trong FragmentTransaction để quản lý action trong FragmentManager.

Hiểu được điều này bạn sẽ dễ dàng hơn trong việc đọc code dự án có liên quan đến xử lý Fragment thông qua FragmentManager.

Chúc các bạn thành công.