Android

Fragment trong Android

(Tạm nghĩ – thực tế không chính xác) Nếu “bỗng chốc thu bé lại” một Application xuống “chỉ bằng một” Activity, thì khi “hạ cấp” một Activity xuống theo hướng đó, ta sẽ có Fragment. Vậy, Fragment là gì và tầm quan trọng của nó là như thế nào?

android-fragments

1. Fragment là gì?

A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running (sort of like a “sub activity” that you can reuse in different activities).

Có thể nghĩ một Fragment là một Activity con trong một Activity. Mỗi Fragment có cách thức hoạt động giống như một Activity, và nếu trong một Activity có nhiều Fragment thì các Fragment sẽ hoạt động tương đối độc lập với nhau.

Vậy tại sao lại xuất hiện Fragment, trong khi Activity đã làm quá tốt mọi ý tưởng của người viết code? Nó có mặt trên Trái Đất với mục đích gì?

2. Sự ra đời và tầm quan trọng của Fragment.

Ban đầu thì cũng như ‘iPhone OS’, Android chỉ hoạt động trên các thiết bị cầm “vừa bàn tay” – tức handsets hay nói cụ thể hơn là điện thoại di động thông minh. Lúc này, do màn hình các handsets khá nhỏ (chỉ trong khoảng 3 inch và Dell Streak 5 inch trông khá “dị hợm” mặc dù bây giờ thì S8 đã có kích thước nhỏ nhất là 5.8 inch), thì Activity đã là quá đủ để thiết kế giao diện ứng dụng. Quá đủ là như thế nào thì tôi không cần nói, bạn cũng biết.

Nhưng tới năm 2010, Andy Rubin lại một lần nữa bị Steve Jobs và Scott Forstall bỏ xa với một cú đấm ngàn cân có tên là iPad. Có lẽ điều làm iPad thành công không hẳn là nó có một màn hình cảm ứng “chần dần” (tới 9.7 inch) mà là cách Apple và các Apple developers tối ưu hóa giao diện ứng dụng của mình cho cái màn hình đó. Giao diện đa cột trên một màn hình ra đời.

ipad-settings

Giao diện này tận dụng được toàn bộ diện tích của màn hình lớn. Bạn thử nghĩ nếu ứng dụng Settings bên trên chỉ có một cột duy nhất như trên iPhone, và orientation là landscape thì tự dưng màn hình sẽ có đầy các khoảng trắng rất vô ích. Vì trên máy tính bảng có màn hình lớn nên người dùng có xu hướng dùng cả hai tay để thao tác, dẫn tới giao diện một cột truyền thống không những gây lãng phí không gian mà còn gây khó khăn, bất tiện cho người dùng khi thao tác, cũng như nếu mang những hoạt ảnh chuyển cảnh của iPhone lên iPad, thì người dùng sẽ phải “đảo mắt” quan sát trên phạm vi rất rộng. Nói đông nói tây, tóm lại, giao diện một cột truyền thống không thực sự thích hợp trên iPad nói riêng hay các thiết bị có màn hình lớn (kể cả máy tính) nói chung.

Sẽ là vừa vặn, không có một lời quá đáng nào khi nói rằng Android phải chạy theo iOS vào thời điểm lúc đó, thậm chí là có phần học đòi. Các nhà sản xuất điện thoại lúc đó như Acer hay Asus lập tức ra mắt các máy tính bảng chạy Android 2.3 mà không cho Google kịp có cơ hội viết lại Android cho phù hợp với trải nghiệm màn hình lớn. Tại thời điểm đó, nếu như bạn có suy nghĩ “máy tính bảng – đúng hơn là iPad – chỉ là cái điện thoại – hay iPhone – với cái màn hình lớn” thì khi trải nghiệm với iPad, bạn sẽ thay đổi suy nghĩ, nhưng nếu bạn cầm một cái máy tính bảng Android của Asus thì bạn sẽ cảm thấy bản thân đúng. Vì thật vậy, Android 2.3 trên một thiết bị có màn hình 8 inch là một mớ hỗ lốn.

Để bắt kịp với xu thế cũng như sức ép từ phía các OEM, Google đã phải xây dựng một phiên bản Android đặc thù cho tablet – Android 3.0 HoneyComb – với một loạt các thư viện mới, mà chủ yếu, là Fragment. Khi đem áp dụng vào thực tế, thì nếu so lại với giao diện iPad bên trên, thì mỗi Fragment sẽ là một cột trong một Activity lớn. Tại sao lại viết thêm Fragment thay vì viết lại Activity để có thể “thu bé nó lại”? Đơn giản thôi, nếu làm vậy thì thứ nhất, mọi thứ không logic cho lắm, và thứ hai là sẽ cần phải viết lại toàn bộ Android.

Và nguồn gốc của sự ra đời của Fragment là như vậy đó. Tuy nhiên, không dừng lại ở đó, Fragment đã phát triển rất nhanh và vượt cả ý tưởng ban đầu chỉ nhằm để tối ưu giao diện cho Activity. Thực sự ở hiện tại, nếu bạn code Android giỏi thì bạn không làm nhiều Activity, mà là làm nhiều Fragment. Theo kinh nghiệm của tôi, làm nhiều Fragment trên một Activity cho dù là trên handsets cũng thoải mái hơn nhiều so với làm nhiều Activity. Có lẽ đã hiểu rõ ưu điểm của Fragment, ngay khi Android 4.0 Ice Cream Sandwich ra đời, thì thư viện support cho Fragment cũng xuất hiện và hỗ trợ ngược lại tới tận API4, tức Android 1.6, để các developers trước là không cần viết lại ứng dụng cho Android 2.3 về trước và 3.0 về sau, mặt khác để họ có thể tận dụng sức mạnh của Fragment trên các phiên bản Android cũ hơn.

Phàm là vậy nhưng sự thật rất “đắng lòng” là việc tối ưu hóa ứng dụng cho máy tính bảng Android thực sự là không phổ biến, và nếu so với iPad thì Android tablet kém xa mấy cây số. Rất nhiều, rất nhiều những ứng dụng mà lập trình viên cho dù có sử dụng Fragment nhưng lại không tối ưu hóa thành giao diện nhiều cột cho máy tính bảng. Ngay cả Hangouts của Google mặc dù có giao diện iPad nhưng lại không có giao diện tablet cho Android.

3. Sử dụng Fragment trong ứng dụng.

Một điều vô cùng quan trọng là Fragment không bao giờ tách khỏi Activity. Muốn dùng Fragment thì bạn phải đặt hoặc gọi (đối với DialogFragment) từ Activity. Một điều quan trọng khác là một Fragment không phải cứ nhất thiết chỉ nằm trong một Activity duy nhất. Do đó, ngoài việc dùng Fragment cho giao diện nhiều cột trên tablet, bạn còn có thể tái sử dụng Fragment cho nhiều Activity khác nhau, và thậm chí là cho cả các mục đích khác nhau nữa.

Có hai cách để đặt/gọi Fragment từ Activity. Một là bạn đặt “cứng” trong file contentView XML của activity với thẻ <fragment>, ví dụ:

Tuy nhiên, cách đặt “cứng” này có rất nhiều hạn chế. Một là việc xoay màn hình sẽ refresh lại toàn bộ giao diện, kể cả contentView của các Fragment bên trong, mà không xét tới Bundle savedInstanceState của onCreate(Bundle savedInstanceState) của Activity. Hai là bạn không thể replace Fragment được. Do đó, bạn chỉ nên tìm hiểu cái bên trên ở mức độ “cho biết”, còn thực tế thì cho dù Activity của bạn chỉ có hai cột và bạn không replace hay remove Fragment nào thì cũng nên dùng cách bên dưới, đó là quản lí các Fragment trong runtime.

Giả sử tôi cũng đang làm ứng dụng với giao diện như bên trên, do đó file contentView của tôi có hai cột:

Nói thêm: Ở đây thì bạn dùng Layout nào để cho Fragment “nhập” vào cũng được, như RelativeLayout hay LinearLayout, nhưng vì ưu điểm tốn ít thời gian để render các children của FrameLayout nên tôi thường dùng FrameLayout trừ trường hợp cụ thể phải chọn cái khác.

Quay sang Activity, tôi có các dòng code bên dưới. Lưu ý là tôi đang dùng các framework classes chứ không phải các support classes trong thư viện appcompat-v7. Về vấn đề đó thì tôi sẽ giới thiệu trong các bài viết sau.

Để bắt đầu load Fragment, trước tiên bạn phải gọi một instance của FragmentTransaction. Bạn không bao giờ được khởi tạo instance của class này mà phải bắt buộc gọi từ FragmentManager#beginTransaction(). Và mọi hoạt động quản lí Fragment như add, remove, replace, hide, show… đều được thực hiện thông qua FragmentTransaction. Xong thì bạn gọi hàm commit() hoặc commitNow(). Một lưu ý nữa là mỗi transaction chỉ được commit một lần nên bạn tuyệt đối không bao giờ đặt instance của FragmentTransation làm field member. Bạn dùng tới đâu thì lại tạo instance mới thông qua FragmentManager.

4. Vòng đời của Fragment.

Tương tự như Activity thì Fragment cũng có vòng đời. Cá nhân tôi không thích dùng chữ “vòng” mà thích chữ “dòng” hơn, vì chữ “dòng” thể hiện được sự “lạc trôi” của các Fragment qua các life methods onCreate(Bundle), onAttach(Context), onCreateView(LayoutInflater, Bundle), onStart(), onResume(), onPause(), onStop(), onDetach() và onDestroy(). Ở đây tôi sẽ chỉ giới thiệu một số methods quan trọng nhất mà bạn cần nắm, các methods khác, bạn có thể tham khảo tại https://developer.android.com/guide/components/fragments.html#Lifecycle

Khi Fragment instance được khởi tạo qua hàm constructor – tức là new Fragment() – hoặc newInstance() – tùy trường hợp bạn đặt – thì Fragment chưa trải qua bất kì life methods nào. Do đó nếu bạn đang lầm tưởng là khi tạo xong instance, chẳng hạn mFragmentOne = new FragmentOne() là Fragment sẽ chạy onCreate(Bundle) thì bạn đang nhầm.

Khi lần đầu tiên một Fragment không null được gọi từ FragmentTransaction thì nó mới trải qua hàm onAttach(Context), và chỉ chạy qua đó đúng một lần cho tới khi nào onDetach được gọi. Tham số Context context ở đây chính là giá trị mà getContext sẽ trả về, còn getActivity chỉ là việc return nó sau khi cast nó về thành Activity (FragmentActivity đối với android.support.v4.Fragment) cũng như nếu Activity implements Fragment interface (tôi sẽ nói trong các bài sau) thì bạn sẽ tiến hành cast cái context này thành một instance của interface kia. Ngoài ra, nếu Fragment vẫn đang trong trạng thái không null thì nó không bao giờ chạy lại onAttach nữa. Sau đó, nó sẽ “lạc trôi” sang onCreate(Bundle).

onCreate(Bundle) cũng chỉ được gọi đúng một lần cho tới khi nào onDestroy được gọi. Mặc dù cũng là onCreate(Bundle) nhưng nó không giống onCreate(Bundle) của Activity đây, vì bạn không chỉ định contentView cho Fragment ở đây mà sẽ phải chờ cho nó trôi tiếp tới onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState).

onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) là điểm chốt bắt đầu các life methods khi Fragment bắt đầu bước vào trạng thái foreground – tức là lúc bạn có thể “nhìn thấy” nó. Ở đây, bạn sẽ chỉ định contentView cho toàn bộ Fragment (tức giống setContentView trong Activity). Ở đây, để chỉ định contentView, thì bạn phải lấy cái inflater có sẵn ở phần tham số đem inflate cái layout mà bạn muốn. Giá trị mà hàm này return sẽ là contentView của Fragment. Ví dụ:

Một lần nữa, giá trị mà hàm này return là cái contentView của bạn. Vì vậy, thay vì bạn return cái contentView bên trên mà return một cái ListView nào đó thì cái ListView sẽ trở thành contentView chứ không phải là cái @layout/fragment_one của bạn. Xong method này thì Fragment sẽ trôi tiếp tới onStart() và onResume(). Tiếp theo, khi nó vào trạng thái pause rồi stop thì nó sẽ trôi tới onPause và onStop. Bốn phương thức này giống như Activity nên thôi sẽ chỉ “để đấy và không nói gì thêm”.

Đối nghịch với onCreateView là onDestroyView, và rồi nó sẽ tiếp tục trôi tới onDestroy và onDetach. Ở đây, tôi muốn lưu ý onDetach(): Nó được gọi khi Fragment bắt đầu được tách khỏi Activity, do đó, nó sẽ trả biến context trong onAttach(Context context) thành null.

5. Phối hợp với Activity life cycle:

“Dòng đời” của Fragment được gắn chặt của “dòng đời” của Activity vì Fragment luôn nằm trong Activity. Do đó, bạn cần điều phối life cycle của cả hai sao cho “nhịp nhàng”. Nói lí thuyết cho vui là như vậy. Nhưng thực tế thì có thể xuất hiện một số vấn đề gây khó chịu cho bạn, chẳng hạn như khi một Dialog xuất hiện, thì cả Activity và Fragment liên quan đều gọi onPause() và Fragment sẽ được gọi trước. Do đó, nếu bạn có code onPause trong cả Activity và Fragment thì phải xử lí sao cho tránh được các xung đột về dữ liệu.

6. Thư viện support về Fragment:

Fragment được đưa vào thư viện framework từ Android 3.0, nên nếu bạn đặt minSdk là ICS về sau (thường nên là JB) thì bạn có thể thoải mái dùng thư viện framework cho các fragment, nếu MainActivity của bạn extends android.app.Activity. Tuy nhiên, nếu MainActivity của bạn extends android.support.v7.app.AppCompatActivity, thì bạn buộc phải sử dụng thư viện support cho Fragment, vì lí do android.support.v7.app.AppCompatActivity yêu cầu android.support.v4.app.Fragment (chứ không còn là android.app.Fragment nữa) để thực hiện các callbacks. Lúc này, thay vì gọi android.app.FragmentManager manager = getFragmentManager(), bạn gọi hàm android.support.v4.app.FragmentManager manager = getSupportFragmentManager(). Vì các class trong thư viện fragment support đều resemble các class trong thư viện fragment framework nên các tên methods đều giống nhau nên bạn sẽ không “cảm thấy bỡ ngỡ” chút nào đâu.

Trong các bài tiếp theo, tôi sẽ giới thiệu cách tương tác giữa các Fragment-Activity từ đó suy ra Fragment-Fragment trên cùng một Activity, cũng như cách tối ưu giao diện cho tablet.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.