Complex sorting for search in realm, union of multiple RealmResults

前端 未结 2 506
广开言路
广开言路 2020-12-06 07:51

I replaced sqlite with realm in my open source Linux Command Library project. Everything went fine so far, but now I\'m facing a problem.

I\'m using a RealmBaseAdapt

相关标签:
2条回答
  • 2020-12-06 08:23

    Thank you guys, you both helped me alot to solve the problem. @Mateusz Herych @EpicPandaForce

    Here is the custom adapter:

    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();
        }
    }
    

    Basically I replaced the single RealmResult with a list of RealmResults and modified the getItem() and getCount() method.

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

    And this is how I update the search

        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);
    
    0 讨论(0)
  • 2020-12-06 08:33

    Not sure if there isn't a better/more-performant way for that but you can try making 3 different queries and then concatenating their results.

        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());
    

    This obviously involves changing your Adapter implementation from RealmBaseAdapter to something more regular. Please keep in mind that while your Realm objects reside in a regular list, they still keep a connection to your Realm database, so you can't close your realm instance while your ListView is displaying data.

    Update: As @EpicPandaForce pointed out in comments, this solution doesn't work well with Realm's autoupdating. It may appear that some Commands are removed / have changed names and this won't be reflected by your Adapter in that case. Make sure to copy your objects first and setup a change listener on your realm results.

    0 讨论(0)
提交回复
热议问题