复杂排序用于在 Realm 中进行搜索,多个 RealmResults 的并集

5

我在我的开源Linux命令库项目中用Realm替换了sqlite。目前一切都进行得很顺利,但现在我面临一个问题。

我使用RealmBaseAdapter在ListView中显示所有命令,并带有搜索界面。对于搜索,下面的realm代码会按以下方式排序结果:

查询: test

结果:

  • l2test
  • rctest
  • test
  • testparm

    RealmResults<Command> commands = mRealm.where(Command.class).contains("name", query).findAll();
    mAdapter.updateRealmResults(commands);
    

使用旧的sqlite逻辑时,排序如下:

结果:

  • test
  • testparm
  • l2test
  • rctest

return getReadableDatabase().rawQuery("Select * from " + CommandsDBTableModel.TABLE_COMMANDS + " WHERE " + CommandsDBTableModel.COL_NAME + " LIKE '%" + query + "%' " + "ORDER BY " + CommandsDBTableModel.COL_NAME + " = '" + query + "' DESC," + CommandsDBTableModel.COL_NAME + " LIKE '" + query + "%' DESC", null);

在realm中也可以实现吗? 以下是该项目的链接:https://github.com/SimonSchubert/LinuxCommandBibliotheca


1
我会创建一个自定义适配器,它从两个查询的 Realm 结果中拼接而成,一个是 startsWith(),另一个是“包含但不以此开头”。 - EpicPandaForce
1
谢谢,它正在工作。 - Simon Schubert
2个回答

9

谢谢你们,Mateusz Herych和EpicPandaForce两位帮我解决了很多问题。

这是自定义适配器:

public abstract class RealmMultiAdapter<T extends RealmObject> extends BaseAdapter {

    private final RealmChangeListener<T> realmChangeListener = new RealmChangeListener<T>() {
        @Override
        public void onChange(RealmResults<T> t) {
            notifyDataSetChanged();
        }
    };

    protected LayoutInflater inflater;
    protected List<RealmResults<T>> realmResults;
    protected Context context;

    public RealmMultiAdapter(Context context, List<RealmResults<T>> realmResults, boolean automaticUpdate) {
        if (context == null) {
            throw new IllegalArgumentException("Context cannot be null");
        }
        this.context = context;
        this.realmResults = realmResults;
        this.inflater = LayoutInflater.from(context);
        for(RealmResults<T> results : realmResults) {
            results.addChangeListener(realmChangeListener);
        }
    }

    /**
     * Returns how many items are in the data set.
     *
     * @return count of items.
     */
    @Override
    public int getCount() {
        if (realmResults == null) {
            return 0;
        }
        int count = 0;
        for(RealmResults<T> realmResult : realmResults) {
            count += realmResult.size();
        }
        return count;
    }

    /**
     * Returns the item associated with the specified position.
     *
     * @param i index of item whose data we want.
     * @return the item at the specified position.
     */
    @Override
    public T getItem(int i) {
        if (realmResults == null || realmResults.size()==0) {
            return null;
        }
        int count = 0;
        for(RealmResults<T> realmResult : realmResults) {
            if(i<realmResult.size()+count) {
                return realmResult.get(i-count);
            }
            count += realmResult.size();
        }
        return null;
    }

    /**
     * Returns the current ID for an item. Note that item IDs are not stable so you cannot rely on the item ID being the
     * same after {@link #notifyDataSetChanged()} or {@link #updateRealmResults(List<RealmResults<T>>)} has been called.
     *
     * @param i index of item in the adapter.
     * @return current item ID.
     */
    @Override
    public long getItemId(int i) {
        // TODO: find better solution once we have unique IDs
        return i;
    }

    /**
     * Updates the RealmResults associated to the Adapter. Useful when the query has been changed.
     * If the query does not change you might consider using the automaticUpdate feature.
     *
     * @param queryResults the new RealmResults coming from the new query.
     */
    public void updateRealmResults(List<RealmResults<T>> queryResults) {
        for(RealmResults<T> results : realmResults) {
            if(results.isValid()) {             
                results.removeChangeListener(realmChangeListener);
            }
        }
        this.realmResults = queryResults;
        for(RealmResults<T> results : realmResults) {
            results.addChangeListener(realmChangeListener);
        }
        notifyDataSetChanged();
    }
}

基本上,我用一个RealmResults列表替换了单个的RealmResult,并修改了getItem()和getCount()方法。

    // before
    protected RealmResults<T> realmResults;
    // after
    protected List<RealmResults<T>> realmResults;

这是我更新搜索的方法。
    List<RealmResults<Command>> results = new ArrayList<>();
    results.add(mRealm.where(Command.class).equalTo("name", query).findAll());
    results.add(mRealm.where(Command.class).beginsWith("name", query).notEqualTo("name", query).findAll());
    results.add(mRealm.where(Command.class).contains("name", query).not().beginsWith("name", query).notEqualTo("name", query).findAll());

    mAdapter.updateRealmResults(results);

1
如果您还没有这样做,如果您的“name”属性不是命令的主键,则请确保将@Index注释添加到它上面,以极大地提高查询性能。 - EpicPandaForce
没有,@Index 在我的情况下使用 name 属性是有意义的。我会这样做,谢谢。 - Simon Schubert
@SimonSchubert,你能给我提供一个扩展RealmMultiAdapter的适配器片段吗?我想知道你是如何将realmresult列表用于你的视图的。 - swati

1

我不确定是否有更好/更高效的方法,但你可以尝试进行三个不同的查询,然后将它们的结果连接起来。

    List<Command> commands = new ArrayList<>();

    commands.addAll(realm.where(Command.class)
            .equalTo("name", query)
            .findAll());

    commands.addAll(realm.where(Command.class)
            .beginsWith("name", query)
            .notEqualTo("name", query)
            .findAll());

    commands.addAll(realm.where(Command.class)
            .contains("name", query)
            .not().beginsWith("name", query)
            .findAll());

这显然需要将您的Adapter实现从RealmBaseAdapter更改为更常规的内容。请记住,虽然您的Realm对象驻留在常规列表中,但它们仍保持与您的Realm数据库的连接,因此您不能在ListView显示数据时关闭Realm实例。
更新:如@EpicPandaForce在评论中指出,这种解决方案与Realm的自动更新不兼容。可能会出现某些命令被删除/更改名称,而您的适配器在这种情况下不会反映出来。请确保首先复制您的对象并在您的realm结果上设置更改侦听器。

我认为这不适用于Realm的自动更新。 - EpicPandaForce
@EpicPandaForce 有没有可靠的方法来连接多个 RealmResults? - Mateusz Herych
好主意,我没有想到那个。问题在于Command数据库有大约2400行,我想坚持使用RealmBaseAdapter,因为考虑到性能和内存原因。 - Simon Schubert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接