android - Searchable Spinner 与 Realm 相结合

标签 android realm android-spinner

我已经实现了 SearchableSpinner到我的项目。它位于 Fragment 中。

我正在使用 Realm 作为数据库。 在我的 onCreateView 方法中我有这个...

@Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.ncrdocument, container, false);
    realm = Realm.getDefaultInstance();
    documents = new ArrayList<>();
    documents = realm.where(MaterialDoc.class).findAll();
    ArrayAdapter<MaterialDoc> adapter = new ArrayAdapter<>(this.getContext(), android.R.layout.simple_list_item_1, documents);
    matList.setAdapter(adapter);
.
.
.
return view;

数据加载正常,显示正确,但当我尝试搜索微调器时,我的应用程序崩溃并收到此错误。

An exception occured during performFiltering()
java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created
at io.realm.BaseRealm.checkIfValid(BaseRealm.java:383)
at io.realm.MaterialDocRealmProxy.realmGet$document_number(MaterialDocRealmProxy.java:126)
at com.my.application.test.Model.MaterialDoc.getDocumentNumber(MaterialDoc.java:29)
at com.my.application.test.Model.MaterialDoc.toString(MaterialDoc.java:42)
at android.widget.ArrayAdapter$ArrayFilter.performFiltering(ArrayAdapter.java:480)
at android.widget.Filter$RequestHandler.handleMessage(Filter.java:234)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.os.HandlerThread.run(HandlerThread.java:61)
01-09 12:13:06.649 18606-18606/com.my.application.test D/AndroidRuntime: Shutting down VM
01-09 12:13:06.669 18606-18606/com.my.application.test E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.my.application.test, PID: 18606
java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference
at android.widget.ArrayAdapter.getCount(ArrayAdapter.java:330)
at android.widget.AdapterView.checkFocus(AdapterView.java:947)
at android.widget.AdapterView$AdapterDataSetObserver.onInvalidated(AdapterView.java:1070)
at android.widget.AbsListView$AdapterDataSetObserver.onInvalidated(AbsListView.java:8297)
at android.database.DataSetObservable.notifyInvalidated(DataSetObservable.java:50)
at android.widget.BaseAdapter.notifyDataSetInvalidated(BaseAdapter.java:59)
at android.widget.ArrayAdapter$ArrayFilter.publishResults(ArrayAdapter.java:513)
at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6939)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)

最佳答案

@SarathKn 接受的答案根本没有效率,你从 Realm 复制/分离结果,你失去了自动更新和惰性评估,你立即复制了整个结果集。

results.copyFromRealm() 的用法是不受欢迎的,除非您实际上需要 一个非托管对象,通常只有当您需要通过 GSON 发送对象时,或者如果您需要以不可撤销的方式修改对象(保存/取消更改)。

使用 realm.copyFromRealm() 来“避免非法线程访问”是一个错误的答案(除非您 100% 确定这就是您要查找的内容,而且你知道自己在做什么)。


实际问题而不是所说的 hack-fix 是你正在使用 ArrayAdapter 的默认 Filter 实现,它在后台线程上执行过滤,而你实际上需要在 UI 线程上过滤 RealmResults(因为它是您在 UI 线程上显示的结果集)。

如果您查看 ArrayAdapter 的 Filterable 实现的源代码,它是这样的:

@Override
public @NonNull Filter getFilter() {
    if (mFilter == null) {
        mFilter = new ArrayFilter();
    }
    return mFilter;
}

/**
 * <p>An array filter constrains the content of the array adapter with
 * a prefix. Each item that does not start with the supplied prefix
 * is removed from the list.</p>
 */
private class ArrayFilter extends Filter {
    @Override
    protected FilterResults performFiltering(CharSequence prefix) {
        final FilterResults results = new FilterResults();

        if (mOriginalValues == null) {
            synchronized (mLock) {
                mOriginalValues = new ArrayList<>(mObjects);
            }
        }

        if (prefix == null || prefix.length() == 0) {
            final ArrayList<T> list;
            synchronized (mLock) {
                list = new ArrayList<>(mOriginalValues);
            }
            results.values = list;
            results.count = list.size();
        } else {
            final String prefixString = prefix.toString().toLowerCase();

            final ArrayList<T> values;
            synchronized (mLock) {
                values = new ArrayList<>(mOriginalValues);
            }

            final int count = values.size();
            final ArrayList<T> newValues = new ArrayList<>();

            for (int i = 0; i < count; i++) {
                final T value = values.get(i);
                final String valueText = value.toString().toLowerCase();

                // First match against the whole, non-splitted value
                if (valueText.startsWith(prefixString)) {
                    newValues.add(value);
                } else {
                    final String[] words = valueText.split(" ");
                    for (String word : words) {
                        if (word.startsWith(prefixString)) {
                            newValues.add(value);
                            break;
                        }
                    }
                }
            }

            results.values = newValues;
            results.count = newValues.size();
        }

        return results;
    }

    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        //noinspection unchecked
        mObjects = (List<T>) results.values;
        if (results.count > 0) {
            notifyDataSetChanged();
        } else {
            notifyDataSetInvalidated();
        }
    }
}

但是 performFiltering 是在后台线程上执行的,而 publishResults 是在 UI 线程上执行的。

实际上,您应该将此过滤器实现替换为在 UI 线程上执行的 Realm 过滤器。

@Override
public @NonNull Filter getFilter() {
    if (mFilter == null) {
        mFilter = new RealmFilter();
    }
    return mFilter;
}

/**
 * <p>An array filter constrains the content of the array adapter with
 * a prefix. Each item that does not start with the supplied prefix
 * is removed from the list.</p>
 */
private class RealmFilter extends Filter {
    @Override
    protected FilterResults performFiltering(CharSequence prefix) {
        return new FilterResults();
    }

    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        String prefix = constraint.toString().trim();
        //noinspection unchecked
        RealmQuery<MaterialDoc> query = realm.where(MaterialDoc.class);
        if(prefix == null || "".equals(prefix)) {
            /* do nothing */
        } else {
            if(prefix.contains(" ")) {
                String[] words = prefix.split("\\s");
                boolean isFirst = true;
                for(String word : words) {
                    if(!"".equals(word)) {
                        if(isFirst) {
                            isFirst = false;
                        } else {
                            query = query.or();
                        }
                        query = query.beginsWith(/*enter query field name*/, word, Case.INSENSITIVE);
                    }
                }
            } else {
                query = query.beginsWith(/* enter query field name*/, prefix, Case.INSENSITIVE);
            }
        }
        mObjects = query.findAll();
        notifyDataSetChanged();
    }
}

但我已经回答了这个here (对于 RecyclerView here )在链接到 this Filter solution that isn't conceptually wrong 之前从 Realm 使用的角度来看。

关于android - Searchable Spinner 与 Realm 相结合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41546759/

相关文章:

android - 执行过程后,Spinner 对象不会接受第二个输入

android - 在 Spinner 中单击多个下拉列表项

android - 将 Intent 从广播接收器发送到android中正在运行的服务

java - DexArchiveMergerException 程序类型已存在 : a. a.a

安卓蓝牙启用

android - 如何使用 Parcelable 和 onClickListeners?

ios - RLMObject 属性 NSDecimalNumber

android - 更改 Spinner 弹出窗口的背景颜色

ios - 无法在 Realm 中存储 NSDate

android - Realm + RxJava + Android : Saving Realm object using RxJava in Android