Как реализовать фильтр для RecyclerView, заполненный из Firestore? - PullRequest
0 голосов
/ 25 января 2019

Фильтрация в реальном времени RecyclingView, заполненный из Firestore путем замены адаптера обновленным запросом, показывает действительно низкую производительность.

Для фильтрации RecyclerView в моем приложении в режиме реального времени (onQueryTextChange) Я использую пользовательский адаптер, расширяющий FirestoreRecyclerAdapter, и каждый раз, когда мне нужно отфильтровать данные, я создаю новый объект Query, создаю новый FirestoreRecyclerOptions объект затем создайте свой собственный объект адаптера, где я передаю FirestoreRecyclerOptions объект конструктору FirestoreRecyclerAdapter и, наконец, я вызываю swapAdapter на моем RecyclerView. Проблема с этим подходом состоит в том, что он приводит к низкой производительности. Я вижу, что представление мигает каждый раз, когда запрос обновляется.

DocumentSearchActivity.java

package com.example.testapp;

import android.app.Activity;
import android.app.SearchManager;
import android.arch.lifecycle.LifecycleOwner;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.LayoutAnimationController;
import android.view.inputmethod.InputMethodManager;
import android.widget.SearchView;
import android.widget.TextView;

import com.example.testapp.adapter.ItemAdapter;
import com.example.testapp.model.Item;
import com.firebase.ui.firestore.FirestoreRecyclerOptions;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;

public class DocumentSearchActivity extends AppCompatActivity {
    private static final String TAG = DocumentSearchActivity.class.getSimpleName();
    private RecyclerView mRecyclerView;
    private ItemAdapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;
    private TextView mNoResultsTextView;
    private SearchView mSearchView;
    private FirestoreQuery mQuery;
    private Toolbar mToolbar;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_document_search);
        mToolbar = findViewById(R.id.app_bar);
        setSupportActionBar(mToolbar);
        getSupportActionBar().setDisplayShowTitleEnabled(false);
        mRecyclerView = findViewById(R.id.recycler_view);
        mSearchView = findViewById(R.id.search_bar);
        mNoResultsTextView = findViewById(R.id.result);
        mNoResultsTextView.setVisibility(View.GONE);
        if(getIntent().hasExtra(MainActivity.QUERY_EXTRA)){
            mSearchView.setQuery(getIntent().getStringExtra(MainActivity.QUERY_EXTRA), true);
            mSearchView.requestFocus();
        }

        mQuery = new FirestoreQuery(this, "");
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);

        Intent intent = getIntent();
        if(Intent.ACTION_SEARCH.equals(intent.getAction())) {
            mQuery.setQueryString(intent.getStringExtra(SearchManager.QUERY));
        }

        mAdapter = new ItemAdapter(mQuery.getOptions());
        mRecyclerView.setAdapter(mAdapter);
        mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return true;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                mAdapter = new ItemAdapter(mQuery.setQueryString(newText).getOptions());
                mRecyclerView.swapAdapter(mAdapter, true);
                mAdapter.notifyDataSetChanged();
                mAdapter.startListening();
                return true;
            }
        });
    }

    private void runLayoutAnimation(final RecyclerView recyclerView) {
        final Context context = recyclerView.getContext();
        final LayoutAnimationController controller =
                AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_fall_down);

        recyclerView.setLayoutAnimation(controller);
        recyclerView.getAdapter().notifyDataSetChanged();
        recyclerView.scheduleLayoutAnimation();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.actions_document_search_activity, menu);
        return super.onCreateOptionsMenu(menu);
    }

    private class FirestoreQuery {
        private LifecycleOwner mLifecycleOwner;
        private String mQueryString;
        private Query mQuery;
        private FirestoreRecyclerOptions<Item> mOptions;

        public FirestoreQuery(LifecycleOwner lifecycleOwner, String query) {
            mLifecycleOwner = lifecycleOwner;
            setQueryString(query);
        }

        public FirestoreQuery setQueryString(String queryString) {
            if(queryString.length() != 0)
                this.mQueryString = queryString.substring(0,1).toUpperCase() + queryString.substring(1).toLowerCase();
            else
                this.mQueryString = queryString;
            updateQuery();
            updateOptions();
            return this;
        }

        private void updateOptions() {
            mOptions = new FirestoreRecyclerOptions.Builder<Item>()
                    .setQuery(mQuery, Item.class)
                    .setLifecycleOwner(mLifecycleOwner)
                    .build();
        }

        private void updateQuery() {
            if(mQueryString.isEmpty()){
                this.mQuery = FirebaseFirestore.getInstance()
                        .collection("tubes_test")
                        .orderBy("number");
            } else {
                StringBuilder query_lower = new StringBuilder(mQueryString.length());
                query_lower.append(mQueryString);
                query_lower.setCharAt(mQueryString.length() - 1, (char) (query_lower.charAt(mQueryString.length() - 1) + 1));
                if (mQueryString.matches("^[0-9]{1,3}[A-Z]*$")) {
                    this.mQuery = FirebaseFirestore.getInstance()
                            .collection("tubes_test")
                            .whereGreaterThanOrEqualTo("number", mQueryString)
                            .whereLessThan("number", query_lower.toString());
                } else {
                    this.mQuery = FirebaseFirestore.getInstance()
                            .collection("tubes_test")
                            .whereGreaterThanOrEqualTo("name", mQueryString)
                            .whereLessThan("name", query_lower.toString());
                }
            }
        }

        public FirestoreRecyclerOptions<Item> getOptions() {
            return mOptions;
        }

    }
}

ItemAdapter.java

package com.example.testapp.adapter;

import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import com.example.testapp.DocumentViewerActivity;
import com.example.testapp.R;
import com.example.testapp.model.Item;
import com.firebase.ui.firestore.FirestoreRecyclerAdapter;
import com.firebase.ui.firestore.FirestoreRecyclerOptions;

public class ItemAdapter extends FirestoreRecyclerAdapter<Item, ItemAdapter.ItemHolder> {

    public ItemAdapter(@NonNull FirestoreRecyclerOptions<Item> options) {
        super(options);
    }

    @Override
    protected void onBindViewHolder(@NonNull ItemHolder holder, int position, @NonNull Item item) {
        final String tubeNumber = item.getNumber();
        holder.mNumberTextView.setText(item.getNumber());
        holder.mNameTextView.setText(item.getName());
        holder.mFormulaTextView.setText(item.getFormula());
        holder.mRangeTextView.setText(item.getRange());
        holder.mInfoButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(v.getContext(), DocumentViewerActivity.class);
                intent.putExtra("number", tubeNumber);
                intent.putExtra("doctype", "info");
                v.getContext().startActivity(intent);
            }
        });
        holder.mManualButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(v.getContext(), DocumentViewerActivity.class);
                intent.putExtra("number", tubeNumber);
                intent.putExtra("doctype", "manual");
                v.getContext().startActivity(intent);
            }
        });
    }

    @NonNull
    @Override
    public ItemHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        Context context = viewGroup.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);

        View tubeView = inflater.inflate(R.layout.item_tube, viewGroup, false);

        return new ItemHolder(tubeView);
    }

    public static class ItemHolder extends RecyclerView.ViewHolder {
        public TextView mNumberTextView;
        public TextView mNameTextView;
        public TextView mFormulaTextView;
        public TextView mRangeTextView;
        public Button mInfoButton;
        public Button mManualButton;

        public ItemHolder(View itemView){
            super(itemView);

            mNumberTextView = itemView.findViewById(R.id.number);
            mNameTextView = itemView.findViewById(R.id.name);
            mFormulaTextView = itemView.findViewById(R.id.formula);
            mRangeTextView = itemView.findViewById(R.id.range);
            mInfoButton = itemView.findViewById(R.id.info_btn);
            mManualButton = itemView.findViewById(R.id.manual_btn);
        }
    }
}

Интересно, есть ли лучший способ решить эту проблему? Кроме того, я не хочу терять функциональность Firestore, такую ​​как обновления данных в реальном времени и автономный доступ к базе данных. Я думал об использовании фильтруемого Списка в моем адаптере, вопрос в том, как мне синхронизировать его с Firestore. Пожалуйста, поделитесь своими взглядами и рекомендациями.

1 Ответ

0 голосов
/ 26 января 2019

Вот решение, которое сработало для меня.По сути, я создал настраиваемый фильтруемый адаптер, который заполняется данными из Firestore только один раз.

CustomItemAdapter.java

package com.example.testapp.adapter;

import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;

import com.example.testapp.DocumentViewerActivity;
import com.example.testapp.R;
import com.example.testapp.model.Item;

import java.util.ArrayList;
import java.util.List;

public class CustomItemAdapter extends RecyclerView.Adapter<CustomItemAdapter.ItemHolder> implements Filterable {

    private List<Item> mTubeList;
    private List<Item> mTubeListFiltered;

    public CustomItemAdapter(List<Item> tubeList){
        mTubeList = tubeList;
        mTubeListFiltered = tubeList;
    }

    @NonNull
    @Override
    public ItemHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View tubeView = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.item_tube, viewGroup, false);
        return new ItemHolder(tubeView);
    }

    @Override
    public void onBindViewHolder(@NonNull ItemHolder holder, int i) {
        final Item item = mTubeListFiltered.get(i);
        //Setting up view
    }

    @Override
    public int getItemCount() {
        return mTubeListFiltered.size();
    }

    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                String pattern = constraint.toString().toLowerCase();
                if(pattern.isEmpty()){
                    mTubeListFiltered = mTubeList;
                } else {
                    List<Item> filteredList = new ArrayList<>();
                    for(Item tube: mTubeList){
                        if(tube.getNumber().toLowerCase().contains(pattern) || tube.getName().toLowerCase().contains(pattern)) {
                            filteredList.add(tube);
                        }
                    }
                    mTubeListFiltered = filteredList;
                }

                FilterResults filterResults = new FilterResults();
                filterResults.values = mTubeListFiltered;
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                mTubeListFiltered = (ArrayList<Item>) results.values;
                notifyDataSetChanged();
            }
        };
    }

    public static class ItemHolder extends RecyclerView.ViewHolder {
        //ViewHolder's code
    }
}

Фрагмент из действия:

        Query query = FirebaseFirestore.getInstance()
                .collection("test")
                .orderBy("number");
        query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
            @Override
            public void onComplete(@NonNull Task<QuerySnapshot> task) {
                mAdapter = new CustomItemAdapter(task.getResult().toObjects(Item.class));
                mRecyclerView.setAdapter(mAdapter);
            }
        });
        mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return true;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                mAdapter.getFilter().filter(newText);
                return true;
            }
        });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...