Search là chức năng khá phổ biến mà hầu hết các app ngày nay đều có.
Bạn có thể làm tính tăng search bằng rất nhiều cách nhưng hôm nay tôi sẽ hướng dẫn các bạn sử dụng một component có sẵn của android đó là SearchView kết hợp với RxJava để thực hiện và tăng performence. Sẽ rất tuyệt nếu như có web api support tính năng search để thực hành dynamic SearchView.
Dưới đây là một demo về search view cái mà chúng ta sẽ thực hành.

Ở bài này tôi sẽ bỏ qua các phần khai báo và các đoạn boilerpate code khởi tạo và sẽ focus vào phần chính. Các bạn có thể tham khảo đoạn code dưới đây:

 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return true;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                if (!newText.isEmpty()) {
                    adapter.setNamesList(namesAPI.searchForName(query));
                    adapter.notifyDataSetChanged();
                }
                return true;
            }
        });

Đơn giản là chúng ta chỉ cần sử dụng và goi api namesAPI.searchForName(query). Tuy nhiên nếu để ý các bạn sẽ thấy rằng việc gọi api như trên lại khá nhiều sau mỗi lần gõ text api sẽ được gọi lặp lại. Cho nên làm như vậy lại là không tốt trong trường hợp này. Tôi sẽ thêm phần hiển thị số lần api được gọi để các bạn có thể thấy rõ hơn.

  @Override
            public boolean onQueryTextChange(String newText) {
                if (!newText.isEmpty()) {
                    adapter.setNamesList(namesAPI.searchForName(query));
                    adapter.notifyDataSetChanged();
                    apiCallsTextView.setText("API CALLS: " + apiCalls++);
                }
                return true;
            }

Quá nhiều đúng không, mỗi lần text trong SearchView’s EditText được thay đổi thì api cũng sẽ được gọi trong khi lại không hề cần thiết. Vậy nên để giảm thiểu số lần gọi api thì chúng ta sẽ sử dụng RxJava để tối ưu hơn.

  • Đầu tiên về phương trâm tối ưu : Chúng ta sẽ tối ưu bằng cách giảm thiểu số lần gọi api được cho là không cần thiết. “Không cần thiết” ở đây có nghĩa là nếu như khoảng thời gian gõ 2 kí tự liền nhau của user quá ngắn thì sẽ ko cần gọi api. Cụ thể là 300ms đây là khoảng thời gian được cho là hợp lý. Và kết quả mong muốn sẽ như sau:

  • Cách thực hiện với “Reactive”:
    Sau khi thêm RxJava và RxAndroid vào và cấu hình gradle chúng ta sẽ thêm 1 class là RxSearch với static method thực hiện việc search fromSearchView() cái mà sẽ tham chiếu tới SearchView của chúng ta và trả về 1 tập kết quả Observable<String>.

public class RxSearch {

    public static Observable<String> fromSearchView(@NonNull final SearchView searchView) {
        final BehaviorSubject<String> subject = BehaviorSubject.create("");

        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {

            @Override
            public boolean onQueryTextSubmit(String query) {
                subject.onCompleted();
                return true;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                if (!newText.isEmpty()) {
                    subject.onNext(newText);
                }
                return true;
            }
        });

        return subject;
    }
}

BehaviourSubject<String> được hiểu như là cầu nối giữa Subscriber và Obserable. Trong trường hợp này chúng ta sẽ sử dụng nó để phát hành (emit) các query liên tục thông qua subject.onNext(newText).

Đây chính là các query được emit lên trong trường hợp xấu nhất.
Và bây giờ là phần cuối cùng, chúng ta sẽ điều khiển việc emit query có nghĩa là sẽ sử dụng một thời gian hợp lý giữa mỗi lần user gõ thêm 1 kí tự mới. Như ở trên tôi đã nói chúng ta se sử dụng con số 300ms, bạn hoàn toàn có thể sử dụng con số khác nếu cảm thấy phù hợp với ứng dụng của mình. Và chúng ta cần sử dụng debounce() như sau:

RxSearch.fromSearchView(searchView)
                .debounce(300, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(query -> {
                    adapter.setNamesList(namesAPI.searchForName(query));
                    adapter.notifyDataSetChanged();
                    apiCallsTextView.setText("API CALLS: " + apiCalls++);
                });

Ngoài ra chúng ta có thể sử dụng thêm Filter để chỉ định rằng chỉ emit query trong trường hợp lenght của query string là lớn hơn 1 chẳng hạn:

RxSearch.fromSearchView(searchView)
                .debounce(300, TimeUnit.MILLISECONDS)
                .filter(item -> item.length() > 1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(query -> {
                    adapter.setNamesList(namesAPI.searchForName(query));
                    adapter.notifyDataSetChanged();
                    apiCallsTextView.setText("API CALLS: " + apiCalls++);
                });

Và kết quả sẽ như thế này:

Ok That’s all. Thank you.
Reference: https://medium.com/@matdziu/using-rxjava-in-searchview-f1d1d5dcb8b7

  • Happy coding –