Chào mừng các bạn đã đến với bài học Android thứ 31, bài học về Fragment (phần tiếp theo). Đây là bài học trong chuỗi bài viết về lập trình ứng dụng Android bằng Java của Yellow Code Books.
Thông qua bài mở màn về Fragment hôm trước, bạn đã biết được Fragment là gì, tại sao phải dùng đến Fragment, và đã biết cách tạo một Frament như thế nào.
Sang bài học hôm nay, mình dự định sẽ nói về các cách để hiển thị Fragment lên Activity một cách linh động như đã hứa. Nhưng thiết nghĩ với việc tiếp cận “sơ sài” với các dòng code của FirstFragment hôm trước, thì sẽ rất khó để có thể vận dụng vào Activity của bài này. Và còn SecondFragment vẫn chưa được tạo nữa chứ. Thế nên mình quyết định sẽ dành thêm một bài nữa để nói rõ về các dòng code quan trọng khi bạn tạo mới các Fragment theo wizard ở bài trước. Đó là các dòng code được phát sinh theo hai check chọn Include fragment factory methods? và Include interface callbacks?.
Nào mời bạn cùng quay lại với FirstFragment để xem các dòng code quan trọng này nhé.
Hoàn Chỉnh FirstFragment
Vâng, FirstFragment đã được tạo ở bài trước. Tuy nhiên, như mình có nói, hôm trước chúng ta chỉ mới xây dựng giao diện và sự kiện click cho hai Button để mà làm quen với Fragment là gì mà thôi, nên kiến thức về Fragment đó còn sơ sài lắm.
Hôm nay chúng ta sẽ hoàn thiện FirstFragment, sao cho Fragment này có thể nói cho MainActivity (mình dự định Activity này sẽ chứa cả hai Fragment mà chúng ta đang xây dựng) biết Button nào đang được nhấn. Sau đó MainActivity sẽ tự nó báo cho SecondFragment biết nên hiển thị nội dung gì.
Và ở bài học trước, khi tạo mới FirstFragment bằng wizard, bạn có nhớ rằng chúng ta chỉ check chọn vào Include interface callbacks?. Check chọn này giúp Fragment khi đó được tạo ra với các dòng code sẵn sàng cho việc thông báo ngược lại cho Activity chứa nó biết các sự kiện tương ứng, chính là các sự kiện nhấn vào các Button.
Làm Quen Với Các Dòng Code Interface Callback
Dưới đây là các dòng code liên quan đến interface callbacks được tạo ra sẵn, mình tô sáng chúng cho bạn dễ nhìn.
Bạn có thể thấy, code tự phát sinh sẵn cho bạn một interface có tên OnFragmentInteractionListener (khối code số 5). Interface này có định nghĩa sẵn một phương thức trừu tượng có tên onFragmentInteraction(). Dĩ nhiên đây là các code mẫu nên tên của chúng đều mang ý nghĩa chung chung, bạn có thể thay đổi tên, hoặc thêm vào các phương thức tùy ý. Mỗi phương thức bên trong interface này sẽ đại diện cho một hành động nào đó mà bạn muốn thông báo ra bên ngoài.
Khối code số 1 sẽ giúp định nghĩa một thuộc tính là kiểu interface vừa được nói ở trên. Thuộc tính này sẽ được khởi tạo ở khối code số 3 khi mà Fragment được gắn vào Activity. Chính Activity chứa đựng Fragment này sẽ phải implement interface này. Đến bài học sau bạn sẽ hiểu rõ hơn.
Khối code số 2 là nơi mà sự kiện Button được click (khối code này sẽ không còn hữu dụng khi bạn thực hành qua mục sau, tuy nhiên bạn cũng nên biết việc lắng nghe sự kiện click và hành xử của phương thức này sẽ diễn ra như thế nào). Tại đây, phương thức trừu tượng trong mListener sẽ được gọi. Một lần nữa, Chính Activity chứa đựng Fragment này và đang implement phương thức trừu tượng này sẽ phải định nghĩa các hành động cụ thể. Bài sau bạn sẽ thấy Activity biết được các lời gọi này từ Fragment như thế nào.
Khối code số 4 sẽ hủy interface này, tức sẽ không còn gọi đến Activity nữa khi mà Fragment này bị gỡ khỏi Activity. Bạn sẽ được nắm rõ hơn sự kiện onDetach() này ở bài học về vòng đời của Fragment.
Hoàn Thiện Code Cho FirstFragment
Nếu những dẫn giải của mình về code cho FirstFragment trên kia lan man và lu bu quá thì bạn đừng vội nản nhé. Bạn sẽ quen dần với các dòng code này thôi.
Để hoàn thiện cho FirstFragment. Đầu tiên, mình muốn interface của nó phải rõ nghĩa một chút. Mình đổi tên của interface ở khối code số 5 thành OnFirstFragmentListener. Phương thức trừu tượng của nó mình cũng đổi tên luôn thành onItemPressed(), và sửa tham số truyền vào là một nội dung String nào đó. Sau này, MainActivity sẽ implement listener này và phương thức này sẽ lấy String được truyền vào này để gởi qua SecondFragment.
public interface OnFirstFragmentListener { void onItemPressed(String content); }
Vì bạn đổi tên cho listener, nên những chỗ nào có sử dụng listener này bạn đều phải thay thế bằng cái tên mới hết nhé.
Sự thay đổi tiếp theo, mình sẽ không cần khối code số 2 nữa, vì chúng ta đã xây dựng sự kiện click cho hai Button từ bài hôm trước rồi. Như vậy khối code này sẽ bị xóa. Và chúng ta cũng phải thay các dòng hiển thị thông báo dạng Toast hôm trước ở các sự kiện click của Button thành các lời gọi như sau.
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_first, container, false); Button btnItem1 = (Button) view.findViewById(R.id.btnItem1); Button btnItem2 = (Button) view.findViewById(R.id.btnItem2); btnItem1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mListener != null) { mListener.onItemPressed("This is a content when Button 1 click"); } } }); btnItem2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mListener != null) { mListener.onItemPressed("Hey, this is a Button 2 content"); } } }); return view; }
Và đây là toàn bộ code hoàn chỉnh của FirstFragment.
public class FirstFragment extends Fragment { private OnFirstFragmentListener mListener; public FirstFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_first, container, false); Button btnItem1 = (Button) view.findViewById(R.id.btnItem1); Button btnItem2 = (Button) view.findViewById(R.id.btnItem2); btnItem1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mListener != null) { mListener.onItemPressed("This is a content when Button 1 click"); } } }); btnItem2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mListener != null) { mListener.onItemPressed("Hey, this is a Button 2 content"); } } }); return view; } @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof OnFirstFragmentListener) { mListener = (OnFirstFragmentListener) context; } else { throw new RuntimeException(context.toString() + " must implement OnFragmentInteractionListener"); } } @Override public void onDetach() { super.onDetach(); mListener = null; } public interface OnFirstFragmentListener { void onItemPressed(String content); } }
Tạo Mới SecondFragment
Thông qua FirstFragment, bạn đã làm quen được cách mà một Fragment thông báo ngược cho Activity biết các sự kiện xảy ra bên trong đó thông qua một interface callback như thế nào.
Giờ thì bạn hãy tự vận dụng các bước tạo FirstFragment để tạo thêm một Fragment nữa nhé. Fragment này có tên là SecondFragment. Lần này thì bạn hãy đảm bảo check chọn Include fragment factory methods? được check mà thôi. Check chọn này cho phép SecondFragment được tạo ra sau đó có chứa các dòng code nhận dữ liệu từ Activity truyền vào khi khởi tạo.
Tạo Giao Diện Cho SecondFragment
Bạn hãy mở file giao diện của SecondFragment lên, đó chính là file fragment_second.xml. Chúng ta chỉ việc thêm một TextView để hiển thị nội dung nào đó cho Fragment này mà thôi.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#CFCFCF" android:padding="8dp" tools:context="com.yellowcode.tournote.SecondFragment"> <TextView android:id="@+id/tvContent" android:layout_width="match_parent" android:layout_height="match_parent" android:text="Content" /> </FrameLayout>
Làm Quen Với Các Dòng Code Factory Method
Giờ thì qua file java của SecondFragment. Và đây là các dòng code liên quan đến check chọn Include fragment factory methods? được mình cho sáng lên như sau.
Đầu tiên, khối code số 1 là các dòng khai báo các key dùng cho Bundle. Nếu bạn chưa biết rõ Bundle là gì thì có thể xem lại mục này. Cơ bản thì Bundle giúp truyền nhận dữ liệu từ ngoài vào Activity và Fragment.
Khối code số 2 khai báo các thuộc tính mParam1 và mParam2, đây chính là các value chứa đựng các giá trị lấy ra từ Bundle.
Phương thức newInstance() ở khối code số 3 sẽ là đầu vào dữ liệu cho Fragment. Ở nơi nào đó khi gọi đến phương thức static này sẽ phải truyền dữ liệu vào theo các tham số param1 và param2. Bên trong phương thức này sẽ tự động tạo ra Bundle, rồi tạo ra các bộ key/value chính là các param vừa nhận, rồi nhét vào Bundle, rồi gửi qua Fragment được khởi tạo ngay chính trong phương thức này luôn, đó chính là SecondFragment. Cách mà phương thức này hoạt động nằm trong một dạng code chuẩn có cái tên là Factory.
Đến phương thức onCreate() ở khối code số 4 thì các bộ key/value sẽ được lấy ra dùng thông qua Bundle đã được “đóng gói” ở newInstance() trên kia và truyền qua.
Như bạn cũng đã làm quen ở FirstFragment, đây cũng chỉ là các code mẫu được đặt những cái tên khá chung chung. Nên chúng ta sẽ phải thay đổi một chút SecondFragment như mục tiếp theo sau.
Hoàn Thiện Code Cho SecondFragment
Do SecondFragment chỉ cần một nội dung đưa vào để hiển thị lên TextView, nên chúng ta chỉ cần một bộ key/value để lấy thông tin text này là đủ.
Ở khối code số 1 ta chỉ cần định nghĩa một key.
private static final String ARG_CONTENT = "content";
Tương tự thì khối code số 2 cũng chỉ cần định nghĩa một value.
private String mContent;
Khối code số 3 khi này chỉ cần truyền một tham số String vào phương thức. Và đóng gói một bộ key/value vào Bundle rồi truyền qua Fragment vừa được tạo mà thôi.
public static SecondFragment newInstance(String content) { SecondFragment fragment = new SecondFragment(); Bundle args = new Bundle(); args.putString(ARG_CONTENT, content); fragment.setArguments(args); return fragment; }
Và cuối cùng khối code số 4 cũng được điều chỉnh tương tự.
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mContent = getArguments().getString(ARG_CONTENT); } }
Chúng ta cũng sẽ hoàn thiện phương thức onCreateView() ở SecondFragment này sao cho có thể hiển thị nội dung lên TextView. Bạn xem.
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_second, container, false); TextView tvContent = (TextView) view.findViewById(R.id.tvContent); if (!TextUtils.isEmpty(mContent)) { tvContent.setText(mContent); } return view; }
Đây là toàn bộ code hoàn chỉnh của SecondFragment.
public class SecondFragment extends Fragment { private static final String ARG_CONTENT = "content"; private String mContent; public SecondFragment() { // Required empty public constructor } public static SecondFragment newInstance(String content) { SecondFragment fragment = new SecondFragment(); Bundle args = new Bundle(); args.putString(ARG_CONTENT, content); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mContent = getArguments().getString(ARG_CONTENT); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_second, container, false); TextView tvContent = (TextView) view.findViewById(R.id.tvContent); if (!TextUtils.isEmpty(mContent)) { tvContent.setText(mContent); } return view; } }
Như vậy là chúng ta vừa mới hoàn thành xong hai Fragment. Và đã đặt tên chúng là FirstFragment và SecondFragment. Hai Fragment này được tạo ra với hai check chọn khác nhau ở wizard. Và bạn cũng đã làm quen với các code mẫu phát sinh đối với từng check chọn này. Dĩ nhiên nếu bạn tạo mới một Fragment nào đó mà cả hai check chọn này đều được chọn, thì code phát sinh sau đó sẽ bao gồm tất cả những gì mà chúng ta làm quen trong bài học hôm nay. Và nếu như bạn không tạo Fragment bằng wizard, thì sẽ không có các code mẫu như bài học, thì bạn vẫn có thể hoàn toàn áp dụng các dòng code của bài học hôm nay vào Fragment của riêng bạn. Đối với mình, các dòng code được tạo sẵn này rất tiện lợi và thú vị. Các bạn sẽ nắm rõ hơn ý nghĩa của các dòng code của bài hôm nay ở bài học sau thôi.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao bên dưới mỗi bài nếu thấy thích.
– Comment bên dưới mỗi bài nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
Bài Kế Tiếp
Sau khi đã có đủ các Fragment hoàn chỉnh ở bài hôm nay, bài sau chúng ta sẽ xem có bao nhiêu cách để hiển thị chúng lên Activity, cách trao đổi dữ liệu giữa chúng như thế nào, và cách Activity quản lý động các Fragment như thế nào nhé.
Bài viết rất chi tiết và rất có tâm nha bạn
cảm ơn bạn rất nhiều
Sau khi đọc bài mà vẫn còn mơ hồ thì các bạn có thể xem clip này. Cực kỳ dễ hiểu luôn:
Bác ơi, phiên bản mới của AS không còn thấy các checkbox include interface callbacks? khi tạo nữa. Sao lại vậy nhỉ?
Vì các interface callbacks giờ đã trở thành một kỹ thuật lỗi thời rồi bạn :))
Chúng ta có thể tạo các callbacks này một cách thủ công, nhưng các công cụ hỗ trợ tạo code đã bỏ đi. Bây giờ bạn có thể dùng ViewModel và LiveData để thay thế cho các cách truyền nhận dữ liệu truyền thống bằng callbacks giữa các Fragment.