可搜索Spinner与Realm相结合



我已经在我的项目中实现了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%确定这就是你想要的,并且你知道你在做什么)。


实际问题是,您使用的是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();
}
}
}

但CCD_ 9是在后台线程上执行的,而CCD_。

实际上,您应该用在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();
}
}

但我之前已经在这里(以及这里的RecyclerView)回答了这个问题,链接到这个Filter解决方案,从Realm使用的角度来看,这个解决方案在概念上没有错。

在跨线程处理realmresults时必须小心。领域的结果来自一个线程,而不能在其他线程中使用。因此,尝试获取结果集的副本(它与活动领域实例分离),并将此副本提供给适配器。Realm有copyFromRealm方法来实现这一点。

像这样修改你的代码

Realm realm = Realm.getDefaultInstance();
RealmResults<MaterialDoc> realmResults = realm.where(MaterialDoc.class).findAll();
List<MaterialDoc> documents = realm.copyFromRealm(realmResults);
ArrayAdapter<MaterialDoc> adapter = new ArrayAdapter<>(this.getContext(), android.R.layout.simple_list_item_1, documents);
matList.setAdapter(adapter);

最新更新