Ajax

Tự tạo callback trong lập trình

callback

Với bài viết trước, chúng tôi hi vọng bạn đã có cái nhìn tổng thể và hiểu được ý nghĩa của các callbacks. Lần này, chúng ta sẽ tiến hành thực hiện tạo một callback theo ý của mình. Tôi sẽ trình bày việc tạo callback trong Java/Android và Javascript qua một vài ví dụ cụ thể. Đối với các ngôn ngữ khác, bạn chỉ cần áp dụng hướng tư duy tương tự và chỉ phải thay đổi syntax cũng như tên hàm cho phù hợp với phong cách của ngôn ngữ đó.

1. Tạo callback trong Java/Android:

Callback trong Java bao giờ cũng cần một interface hoặc chí ít là một class được implements interface đó. Trong bài viết này, tôi sẽ tập trung vào asynchronous callback và callback-related callback (callback liên quan tới event) vì chúng có nhiều ý nghĩa hơn so với synchronous callback.

1.1. Callback-related callbacks:

Cái tên nói lên tất cả, các callbacks này sẽ được gọi liên quan tới một callback khác, chẳng hạn như khi bạn click vào một button nào đó. Ở đây, tôi sẽ trình bày về việc bắt sự kiện click trên RecyclerView, vì tôi nhận thấy nhiều bạn hay thắc mắc chỗ này, cũng như thể hiện luôn tư duy của mình để các bạn có thể tham khảo qua. Tất nhiên, bạn có thể dùng RecyclerView.OnItemTouchListener, tuy nhiên cá nhân tôi “thích” hướng làm này hơn, vì khi bạn tự dựng các methods, bạn sẽ dễ dàng đưa thêm vào cũng như bỏ đi các params/arguments vào callback mà bạn muốn, hơn là phải gò bó theo những cái có sẵn.

Tôi không nói việc cấu hình RecyclerView ra sao, vì bài viết này không nhằm tới hướng dẫn về RecyclerView, cũng như tôi mặc định bạn đã có những kiến thức cần thiết về nó. Tôi giả sử ArrayList nguồn để đổ vào RecyclerView chứa Object, tức ArrayList<Object>. Trong class RecyclerViewHolder extends RecyclerView.ViewHolder, bạn thường làm như sau để gán event onClick cho itemView:

Bây giờ, tôi tạo một callback interface có tên là OnRecyclerViewItemClickListener có nội dung như bên dưới. Và đây, thưa các bạn, đây chính là cái callback mà chúng ta tạo:

Tiếp theo, tôi cho cái MainActivity implements cái interface bên trên, và thêm body cho onItemClick với method handleItemClick mang tính minh họa. Do đó, bạn vui lòng đừng hỏi tôi “Sao em làm như hướng dẫn mà nó báo đỏ cái handleItemClick, kì cục vậy!”.

Bây giờ, tôi tiến hành điều chỉnh cái class RecyclerViewHolder như bên dưới, để áp dụng cái callback interface mà tôi vừa làm. Đương nhiên, để đem cái mListener ra dùng thì phải có cái setter tương ứng cho nó. Không thôi thì nó sẽ null, và bạn sẽ thấy cái bảng dừng quen thuộc với cái Exception phổ biến bậc nhất là NullPointerException.

Okie dokie, code đã thành hình rồi. Tuy nhiên, vấn đề đặt ra ở đây là mListener lấy ở đâu ra mà truyền vô RecyclerViewHolder? Bạn đâu gọi trực tiếp RecyclerViewHolder từ MainActivity được đâu! Mà bạn sẽ truyền qua Adapter của RecyclerView, ở đây là class RecyclerViewAdapter:

Và cuối cùng, bạn định nghĩa lại mRecyclerViewAdapter trong MainActivity trong onCreate:

Rồi, vậy là xong. Cái callback của bạn chính là onItemClick(RecyclerView.ViewHolder, int position) đã được tạo thành công. Callback này hoàn toàn dựa vào callback nguồn là onClick(View v), tức “a callback fires another callback”.

1.2. Asynchronous callback.

Ở đây tôi mặc định là bạn đã nắm các kiến thức về asynchronous operations rồi, nên tôi thiết lập callback dựa vào một background AsyncTask. Nếu bạn chưa rõ về async ops, hãy tham khảo bài viết trước.

Ý tưởng chung là sẽ viết một CountDownTimer, với param truyền vào là một số nguyên chỉ số giây. Và sau đúng bấy nhiêu đó giây, thì ứng dụng sẽ Toast ra báo là bấy nhiêu giây đó đã trôi qua. Bạn có thể dùng Thread, Handler hay bất kì phương án nào cũng được. Ở đây tôi dùng AsyncTask cho dễ, cũng như cung cấp một số kinh nghiệm sử dụng AsyncTask của mình cho các bạn vẫn còn rối rắm về nó tham khảo qua. Đầu tiên, tôi tạo một class CountDownTimer extends AsyncTask:

Rồi, bây giờ cho chạy trong MainActivity là ngon cơm. Đương nhiên, MainActivity nên implements cái CountDownTimer.Listener sẽ dễ nhìn hơn.

Vậy là tôi đã hướng dẫn các bạn tự tạo các callbacks theo ý bạn với nội dung mà bạn muốn. Bạn thấy đó, nghe qua thì thấy quá là “đao to búa lớn”, nhưng thực tế, khi làm rồi thì thấy nó vô cùng đơn giản. Sự phức tạp là dành cho việc triển khai body của các callbacks chứ không phải là khởi tạo chúng.

2. Callback trong Javascript:

Trong Javascript thì các callback có dạng function(arg1, arg2, arg3, …, argN) và nằm trong một method khác. Chẳng hạn:

Trước hết, đối với các bạn mới làm quen với JS, thì bạn sẽ thấy hàng hà sa số các function con bên trong function – và chúng là callback đó, và các function con có chứa các arguments khác nhau. Chẳng hạn như bên trên, inner callback chỉ chứa một arg duy nhất là event. Còn đối với forEach, thì callback function bên trong chứa 3 args là item, index, arrayItself.  Còn với setTimeout, thì callback của nó chẳng chứa arg nào cả. Bạn có thể sẽ bị rối rắm, “What the phosphate! Sao function chỗ này lại khác function chỗ kia?”. Nếu bạn có thắc mắc đó, thì hãy nhìn lên bên trên Java kìa. Callback của Java là một interface chứa (wrap) một method bên trong và bạn truyền một instance của interface đó làm tham số, interface đóng vai trò là một “thùng chứa” – wrapper. Còn với Javascript, coi như bạn phải truyền đích thân cái method luôn. Và nếu bạn không đặt riêng một biến chứa method/function kia, thì bạn chỉ cần dùng từ khóa function mang tính anonymous, tức là “không cần đặt tên đặt tuổi gì” cho cái method đó, mà truyền nội dung bạn muốn vào mà thôi. Do đó, đừng để cái từ khóa function kia làm phân tán sự chú ý, còn việc có bao nhiêu args trong callback và mỗi cái arg có ý nghĩa gì là do bản thân cái method bên ngoài bạn đang dùng nó quy định.

Quay trờ lại việc tạo callback, trước hết tôi tạo một callback mang tính “móc nối”, tức là “a callback fires another callback”. Ý tưởng là sẽ thao tác với mỗi đối tượng trong một array nguồn ra sau một khoảng thời gian nhất định. Function này sẽ gồm 3 params: cái đầu tiên là array nguồn, thứ hai là khoảng thời gian tính bằng giây, và cái tiếp theo là hành động mà bạn muốn. Function đó sẽ có body như sau:

Bấy giờ, giả sử tôi chỉ đơn giản là muốn in ra các item của array nguồn là [‘Hello’, ‘World’, 21, 35, 69, true, false, true] sau 5 seconds thì tôi gọi hàm doSomethingWithArrayMember như sau:

Như vậy, ý tưởng tạo callback của JS chỉ đơn giản là bạn đặt tên một arg trong method (thường tôi đặt là callback cho dễ), và để thể hiện việc gọi nó ra dưới dạng method, bạn chỉ cần thêm cặp ngoặc tròn ( ) vào là được, bên trong chứa các tham số mà bạn muốn, và các tham số đó chính là các tham số trong từ khóa function(…) để bạn gọi callback mà thôi. Chẳng hạn, với callback(item, index) thì chúng sẽ tương ứng với function(item, index).

Và tôi mong các bạn đã có được ý tưởng tạo các callback trong Java/Android và Javascript, đồng thời cũng có ý tưởng chung để tạo các callback trong bất kì ngôn ngữ nào cho bất kì nền tảng đích nào. Hẹn gặp lại các bạn trong bài viết về Promise trong Javascript.

5 thoughts on “Tự tạo callback trong lập trình”

  1. anh cho em hỏi việc dùng tạo anonymous class từ interface như này
    btnExample.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    // Do something here
    }
    });
    và implalement interface như trong bài, thì khi nào nên dùng cách nào vậy. Vì em thấy bản chất cũng chỉ là @Override cái phương thức cần triển khai?

    1. Cái nào cũng dẫn tới La Mã với cùng kết quả thôi. Nhưng việc implements interface để dùng gói nhiều method vào thì cần kiểm tra xem ai gọi tới method đó mới được. Vd:
      class MainActivity extends Activity implements View.OnClickListener {
      void onCreate(Bundle b) {
      super.onCreate(b);
      setContentView(contentView);
      View view1 = findViewById(viewId1);
      view1.setOnClickListener(this);
      View view2 = findViewById(viewId2);
      view2.setOnClickListener(this);
      }

      void onClick(View v) {
      int id = v.getId();
      if (id == viewId1) { /* code onClick của view1 */ }
      else if (id == viewId2) { /* onClick của view2 */ }
      }
      }

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.