【问题标题】:Complex sorting for search in realm, union of multiple RealmResults领域搜索的复杂排序,多个领域结果的联合
【发布时间】:2016-01-24 11:17:08
【问题描述】:

我在我的开源 Linux 命令库项目中将 sqlite 替换为 realm。到目前为止一切都很好,但现在我遇到了问题。

我正在使用 RealmBaseAdapter 在带有搜索界面的 ListView 中显示所有命令。对于搜索,下面狙击的领域会按如下顺序排列结果:

查询: 测试

结果:

  • l2测试
  • rc测试
  • 测试
  • 测试参数

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

使用旧的 sqlite 逻辑,顺序是这样的:

结果:

  • 测试
  • 测试参数
  • l2测试
  • rc测试

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

【问题讨论】:

  • 我会创建一个自定义适配器,它从两个查询的 Realm 结果拼凑起来,一个是startsWith(),另一个是“包含但不以开头”。
  • 谢谢,它正在工作。

标签: android sqlite realm


【解决方案1】:

谢谢你们,你们帮我解决了很多问题。 @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);

【讨论】:

  • 如果你没有,如果你的name属性不是命令的主键,那么一定要给它加上@Index注解,以大大提高查询性能。
  • 不,我没有,@Index name 属性在我的情况下是有意义的。我会这样做的,谢谢。
  • @SimonSchubert 能否提供扩展 RealmMultiAdapter 的适配器的 sn-p。我想知道您是如何将 realmresult 列表用于您的视图的。
【解决方案2】:

不确定是否没有更好/性能更高的方法,但您可以尝试进行 3 个不同的查询,然后连接它们的结果。

    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 显示数据时关闭您的领域实例。

更新:正如@EpicPandaForce 在 cmets 中指出的那样,此解决方案不适用于 Realm 的自动更新。似乎某些命令已被删除/已更改名称,在这种情况下,您的适配器不会反映这一点。确保首先复制您的对象并在您的领域结果上设置更改侦听器。

【讨论】:

  • 我认为这不适用于领域的自动更新。
  • @EpicPandaForce 有什么可靠的方法来连接多个 RealmResults?
  • 好主意,我没想到。问题是命令数据库有大约 2400 行,我想坚持使用 RealmBaseAdapter。由于性能和内存的原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-08
  • 1970-01-01
相关资源
最近更新 更多