web-dev-qa-db-fra.com

Filtre / recherche de bibliothèque de pagination

J'utilise la Android de pagination comme décrit ici: https://developer.Android.com/topic/libraries/architecture/paging.html

Mais j'ai aussi un EditText pour rechercher des utilisateurs par nom.

Comment puis-je filtrer les résultats de la bibliothèque de pagination pour afficher uniquement les utilisateurs correspondants?

31
user3292244

EDIT de 2019: attendez, je pense que vous pourriez être en mesure de résoudre ce problème avec un MediatorLiveData.

Plus précisément Transformations.switchMap et un peu de magie supplémentaire.

J'utilisais actuellement

public void reloadTasks() {
    if(liveResults != null) {
        liveResults.removeObserver(this);
    }
    liveResults = getFilteredResults();
    liveResults.observeForever(this);
}

Mais si vous y réfléchissez, vous devriez pouvoir résoudre ce problème sans utiliser observeForever, surtout si nous considérons que switchMap fait aussi quelque chose de similaire.

Nous avons donc besoin d’un LiveData<SelectedOption> qui est mis en correspondance avec le LiveData<PagedList<T>> dont nous avons besoin.

private MutableLiveData<String> filterText = new MutableLiveData<>();

private final LiveData<List<T>> data;

public MyViewModel() {
    data = Transformations.switchMap(
            filterText,
            (input) -> { 
                if(input == null || input.equals("")) { 
                    return repository.getData(); 
                } else { 
                    return repository.getFilteredData(input); }
                }
            });
  }

  public LiveData<List<T>> getData() {
      return data;
  }

De cette façon, les modifications effectives d'un fichier à un autre sont gérées par un MediatorLiveData. Si nous voulons mettre en cache les LiveDatas, nous pouvons alors effectuer l'instance anonyme que nous passons à la méthode.

    data = Transformations.switchMap(
            filterText, new Function<String, LiveData<List<T>>>() {
                private Map<String, LiveData<List<T>>> cachedLiveData = new HashMap<>();

                @Override
                public LiveData<List<T>> apply(String input) {
                    // ...
                }
            }



RÉPONSES ORIGINALES (elles sont périmées)

EDIT: en fait. Bien que cela ait du sens pour un LiveData<?>, avec Pagination, vous pouvez réellement paramétrer l’usine, puis invalider la source de données et obtenir gratuitement une nouvelle source de données. Sans recréer le titulaire de la requête lui-même.

La méthode mentionnée dans l’autre réponse est donc une meilleure option lorsque vous utilisez la radiomessagerie.


RÉPONSE ORIGINALE:

Vous savez comment vous avez l'adaptateur comme ceci:

public class TaskAdapter
        extends PagedListAdapter<Task, TaskAdapter.ViewHolder> {
    public TaskAdapter() {
        super(Task.DIFF_ITEM_CALLBACK);
    }

Dans le ViewModel, vous configurez une liste en direct paginée et vous l'exposez:

private LiveData<PagedList<Task>> liveResults;

public TaskViewModel() {
    liveResults = new LivePagedListBuilder<>(taskDao.tasksSortedByDate(),
        new PagedList.Config.Builder() //
              .setPageSize(20) //
              .setPrefetchDistance(20) //
              .setEnablePlaceholders(true) //
              .build())
            .setInitialLoadKey(0)
            .build();

Ensuite, vous observez la liste paginée dans ViewModel et définissez-la sur l'adaptateur:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    viewModel.getTasks().observe(this, pagedList -> {
        //noinspection Convert2MethodRef
        taskAdapter.submitList(pagedList); //used to be `setList`
    });

Le problème, c’est que si vous voulez le rendre paramétrique, vous devez remplacer ce qui suit ici et faire en sorte que la vue puisse l’observer:

    liveResults = new LivePagedListBuilder<>(userDao.usersByName(input) // <-- !!

Donc, vous devez remplacer le LiveData. o_o

Dans ce cas, vous pouvez supprimer les observateurs des données LiveData existantes, les remplacer par de nouvelles données LiveData et commencer à les observer.

private void startListening() {
    viewModel.getTasks().observe(this, pagedList -> {
        //noinspection Convert2MethodRef
        taskAdapter.submitList(pagedList); // used to be `setList`
    });
}

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    startListening();
}

@OnTextChanged(R.id.edit_text)
public void onTextChanged(Editable editable) {
    String username = editable.toString();
    replaceSubscription(userName);
}

private void replaceSubscription(String userName) {
    viewModel.replaceSubscription(this, userName);
    startListening();
}

et

public class UserViewModel extends ViewModel {
    private LiveData<PagedList<User>> liveResults;

    private String userName;

    private LiveData<PagedList<User>> createFilteredUsers(String userName) {
       // TODO: handle if `null` and load all data instead
       return new LivePagedListBuilder<>(userDao.usersByName(userName),
            new PagedList.Config.Builder() //
                  .setPageSize(20) //
                  .setPrefetchDistance(20) //
                  .setEnablePlaceholders(true) //
                  .build())
                .setInitialLoadKey(0)
                .build();
    }

    public UserViewModel(UserDao userDao, @Nullable String userName) { // null or restored, from ViewModelProviders.of(Factory)
        liveResults = createFilteredUsers(userName);
    }

    public void replaceSubscription(LifecycleOwner lifecycleOwner, String userName) {
        this.userName = userName;
        liveResults.removeObservers(lifecycleOwner);
        liveResults = createFilteredUsers(userName);
    }
}
33
EpicPandaForce

J'ai utilisé une approche similaire à celle décrite par EpicPandaForce. Pendant que cela fonctionne, cet abonnement/désabonnement semble fastidieux. J'ai commencé à utiliser une autre base de données que Room, il me fallait donc créer mon propre DataSource.Factory. Apparemment, il est possible d'invalider un DataSource en cours et DataSource.Factory crée un nouveau DataSource, c'est là que j'utilise le paramètre de recherche.

Mon DataSource.Factory:

class SweetSearchDataSourceFactory(private val box: Box<SweetDb>) :
DataSource.Factory<Int, SweetUi>() {

var query = ""

override fun create(): DataSource<Int, SweetUi> {
    val lazyList = box.query().contains(SweetDb_.name, query).build().findLazyCached()
    return SweetSearchDataSource(lazyList).map { SweetUi(it) }
}

fun search(text: String) {
    query = text
}
}

J'utilise ObjectBox ici, mais vous pouvez simplement renvoyer votre requête DAO de pièce sur create (je suppose que, comme il s'agit déjà d'un DataSourceFactory, appelez son propre create).

Je ne l'ai pas testé, mais cela pourrait fonctionner:

class SweetSearchDataSourceFactory(private val dao: SweetsDao) :
DataSource.Factory<Int, SweetUi>() {

var query = ""

override fun create(): DataSource<Int, SweetUi> {
    return dao.searchSweets(query).map { SweetUi(it) }.create()
}

fun search(text: String) {
    query = text
}
}

Bien sûr, on peut simplement passer une usine déjà avec la requête de dao.

ViewModel:

class SweetsSearchListViewModel
@Inject constructor(
private val dataSourceFactory: SweetSearchDataSourceFactory
) : BaseViewModel() {

companion object {
    private const val INITIAL_LOAD_KEY = 0
    private const val PAGE_SIZE = 10
    private const val PREFETCH_DISTANCE = 20
}

lateinit var sweets: LiveData<PagedList<SweetUi>>

init {
    val config = PagedList.Config.Builder()
        .setPageSize(PAGE_SIZE)
        .setPrefetchDistance(PREFETCH_DISTANCE)
        .setEnablePlaceholders(true)
        .build()

    sweets = LivePagedListBuilder(dataSourceFactory, config).build()
}

fun searchSweets(text: String) {
    dataSourceFactory.search(text)
    sweets.value?.dataSource?.invalidate()
}
}

Quelle que soit la requête de recherche reçue, appelez simplement searchSweets sur ViewModel. Il définit la requête de recherche dans la fabrique, puis invalide la source de données. À son tour, create est appelé dans la fabrique et une nouvelle instance de DataSource est créée avec une nouvelle requête et transmise à LiveData existant sous le capot.

19
Deividas Strioga