1

编辑:真正的问题是 submitList() 没有触发 recyclerView 中的 onCreateViewHolder()。

解决方案:

我看到人们一直有这个问题。android DiffUtils 不适用于 List 的相同实例,如果传递了 List 的相同实例,Adapter 将忽略它,即使它的内容不同,这意味着每次提交都需要一个新的 List。

因此,如果您的组件正在更改列表的内部状态,最好new ArrayList(toSubmit);在提交之前创建它的副本。

问题

内部MutableLiveData正在ViewModel更新,但未.observe()触发通知适配器的 。

我正在尝试显示来自Firebase数据库的数据,并且我想使用一个HandlerThread引用一个孩子并添加一个childEventListener以使片段之间的事情变得更容易。

但是即使没有 HandlerThread,这个问题仍然存在,即使我将整个 firebase 引用放在 Fragment 中,ViewModel 仍然不会触发.observe()

public class CategoryListViewModel2 extends ViewModel {

    private static final String TAG = "CategoryListViewModel";
    private MutableLiveData<List<CategoryListItem>> mData;
    private ValueSetter mValueSetter;

    public CategoryListViewModel2() {
        mValueSetter = new ValueSetter();
        mData = new MutableLiveData<>();
    }

    public MutableLiveData<List<CategoryListItem>> getData() {
        return mData;
    }

    public void getCategories(DataSnapshot snapshot) {
        Log.d(TAG, "getCategories: ");
        mValueSetter.populateCategories(snapshot, mData);

        Log.d(TAG, "getCategories: checkSize is: " + checkSize());
        if (checkSize()) {
            Log.d(TAG, "getCategories: mData is: " + mData.getValue().get(5));
        }
    }

    private boolean checkSize() {
        return mData.getValue().size() > 5;
    }
}

checksize()方法告诉我MutableLiveData,尺寸确实在增加。

ValueSetter是一个类,它获取snapShot它在 List 中的位置,然后设置MutableLiveDataValueStter 的代码的新值是:

public class ValueSetter {
    private static final String TAG = "ValueSetter";

    private List<CategoryListItem> items;

    public ValueSetter() {
        items = new ArrayList<>();
    }

    public void populateCategories(DataSnapshot snapshot, MutableLiveData<List<CategoryListItem>> mData) {    
        CategoryListItem item = snapshot.getValue(CategoryListItem.class);
        items.add(item);

        mData.setValue(items);
}

我的 FragmentHandlerThread从 Main Activity 检索初始化,然后继续执行通常的操作:Databindings,设置RecyclerView,创建 de Adapter 并设置它,然后ViewModel通过工厂调用其构造函数,然后ViewModel提交列表,以防万一通知适配器,但即使这样也不起作用......

public class CatalogFragment extends Fragment {
    
        private static final String TAG = "CatalogFragmentRecycler";
        
        private DatabaseHandlerThread mDatabaseHandlerThread;
        private CategoryListViewModel2 mViewModel2;
    
        public CatalogFragment() {
            Log.d(TAG, "CatalogFragment: ");
        }
    
        @Override
        public void onAttach(@NonNull Context context) {
            super.onAttach(context);
            Log.d(TAG, "onAttach: ");
            if (context instanceof MainActivity) {
                mDatabaseHandlerThread =((MainActivity)context).getDatabaseHandlerThread();
            }
        }
    
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    
            FragmentCatalogBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_catalog,container,false);
    
            binding.categoriesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
            binding.categoriesRecyclerView.setHasFixedSize(true);
    
            final CategoriesAdapter adapter = new CategoriesAdapter();
            binding.categoriesRecyclerView.setAdapter(adapter);
    
            ViewModelFactory factory = new ViewModelFactory();
            mViewModel2 = ViewModelProviders.of(this, factory).get(CategoryListViewModel2.class);
            mViewModel2.getData().observe(this, categoryListItems -> {
    
                adapter.submitList(categoryListItems);
                adapter.notifyDataSetChanged();
  binding.categoriesRecyclerView.smoothScrollToPosition(adapter.getItemCount());
            });
    
            return binding.getRoot();
        }
    
        @Override
        public void onStart() {
            super.onStart();
            Log.d(TAG, "onStart: ");
    
            mDatabaseHandlerThread.sendMsg("C", dataSnapshot -> mViewModel2.getCategories(dataSnapshot));
    }

SmoothScrollToPosition() 是唯一的代码片段,它在一定程度上解决了我的 recyclerView 及其适配器未得到通知的问题,问题是这个片段只解决了应用程序第一次启动时的问题,但是如果添加了一个孩子recyclerView 拒绝更新。

如果我删除了 smoothScrollToPosition() 代码行,recyclerView 什么也不会显示,它只会在我用手指按字面触摸它后填充列表。

在 onStart 上,您可以看到我发送的消息,其中包含子“C”的引用,以及负责数据快照的 ViewModel。

因为我以前做过,所以我知道为了让 viewmodel 能够更改视图,它必须在创建视图的同一线程中完成,这意味着 onChildEventListener 在 Ui 线程上运行我的自定义侦听器运行OnUiThread()。

@Override
public void handleMessage(Message msg) {
    Log.d(TAG, "handleMessage: ");
    while (!transfer) {
        try {
            wait();
        } catch (InterruptedException e)  {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
    Log.d(TAG, "handleMessage: ");
    mReference = mReference.child(child);
    Log.d(TAG, "handleMessage: reference is: " + 
    mReference.toString());
    transfer = false;
    mReference.addChildEventListener(firebaseListener());
    transfer = true;
    //mReference.removeEventListener(firebaseListener());
}

这是 My 中的处理程序HandlerThread,该removeEventListener代码段已被注释,以便在添加子项时仍会监听事件。

如果将侦听器的整个数据库引用放置在片段内,则 ViewModel 拒绝触发 .observe()。当我使用相同的:mViewModel2.getCategories(dataSnapshot)从内部ChildEventListener()

public class CategoriesAdapter extends ListAdapter<CategoryListItem, 
CategoriesAdapter.CategoriesHolder> {

    private static final String TAG = "CategoriesAdapter";

    private LayoutInflater mLayoutInflater;

    public CategoriesAdapter() {
        super(DIFF_CALLBACK);
    }

    private static final DiffUtil.ItemCallback<CategoryListItem> 
DIFF_CALLBACK = new DiffUtil.ItemCallback<CategoryListItem>() {
        @Override
        public boolean areItemsTheSame(@NonNull CategoryListItem oldItem, 
@NonNull CategoryListItem newItem) {
            return oldItem.getS() == newItem.getS();
        }

        @SuppressLint("DiffUtilEquals")
        @Override
        public boolean areContentsTheSame(@NonNull CategoryListItem 
oldItem, @NonNull CategoryListItem newItem) {
            Log.d(TAG, "areContentsTheSame: " + newItem.toString());
            return oldItem.getS().equals(newItem.getS());
        }
    };

    @NonNull
    @Override
    public CategoriesHolder onCreateViewHolder(@NonNull ViewGroup parent, 
int viewType) {
        Log.d(TAG, "onCreateViewHolder: ");
        if (mLayoutInflater == null) {
            mLayoutInflater = LayoutInflater.from(parent.getContext());
            Log.d(TAG, "onCreateViewHolder: inflater is: " + 
mLayoutInflater.toString());
        }

        Log.d(TAG, "onCreateViewHolder: inflater is: " + mLayoutInflater);

        CategoryListItemBinding binding = 
DataBindingUtil.inflate(mLayoutInflater,
                R.layout.category_list_item,
                parent,
                false);
        return new CategoriesHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull CategoriesHolder holder, int 
position) {
        CategoryListItem currentitem = getItem(position);
        holder.setData(currentitem);
    }

    class CategoriesHolder extends RecyclerView.ViewHolder {
        private CategoryListItemBinding binding;

        public CategoriesHolder(@NonNull CategoryListItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        void setData (CategoryListItem item) {
            binding.setCategoryListItem(item);
        }
    }
}

SingetS()是快照中在该位置检索到的唯一字段。

4

1 回答 1

1

经过更多测试,似乎 .observe() 确实被调用了,问题是 .submtList() 没有触发 onCreateViewHolder()

不是一个真正的答案,但经过一些阅读后,它似乎与它与 Room 以外的东西不兼容有关。

https://stackoverflow.com/a/50062174/11214643

至少该答案设法访问了 Diff_Callback,但 onCreateViewHolder() 仍然没有响应...我还尝试在重写的 submitList() 方法中放置一个 notifyDataSetChaged(),但什么也没发生。

我还尝试在 .observe() 侦听器中放置一个 adapter.notifyDataSetChanged ,但它没有用......

最后,真正的答案是将两者结合起来:

adapter.notifyDataSetChanged();
recyclerView.smoothScrollToPosition(adapter.getItemCount())

在 .observe() 中,在 submitList() 之后。

此解决方案在创建时填充 recyclerView 并在添加子时填充,现在鉴于对此问题的少数请求,我想这与我当前用于测试的版本(棉花糖)有关。

现在最大的笑话......在阅读了我上面自己的问题后,似乎我写了我的问题(代码),其中有正确的答案,但在我的 IDE 中,adapter.notifyDataSetChanged(); 总是被评论,或者在我遇到这个问题的 3 天里,我从来没有尝试同时使用这两种方法。

大家保重,我要睡觉了。

编辑:

好吧,距离我最初的问题和第一个答案已经过去了一段时间......我刚刚意识到,毕竟我学到了一些东西,我会尽量简化为什么一切都基本上是错误的......

首先这个:

解析(我认为这不是正确的术语)我认为可以在适配器内部完成的 ValueSetter 中的快照列表,我后来开发了一个 fori 比较器,它根据它的 getKey() 或快照中的任何值来解析快照。 ..当然在给定的孩子。

从理论上讲,如果您使用 DataBind,您甚至可以解析 XML 中的值...我不知道这样做会有多好,但我认为如果在 ViewModel 中执行之后代码会变得更清晰。

第二:

后来我发现 handlerTHread 中的信号量毫无意义,没有按预期工作,你需要的是这个https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

这将帮助您开发一个重新路由器,该路由器使用 Firebase 数据库的不同路径或查询填充单个列表。

另外,我听说有人说在 LiveData 中放置逻辑是不好的,但是因为我喜欢包装有用的东西(不知道你是否可以称之为严格封装)连接 FirebaseListener 的所有逻辑最好在 LiveData 中完成这里是一个好示例https://firebase.googleblog.com/2017/12/using-android-architecture-components.html

但请注意:您需要非常小心两件事:

了解 Observers 堆栈,这就是 hasObserver() 方法有用的地方,当重新创建 Fragment 并再次请求 LiveData 从新的 Firebase 路径获取信息时,您将需要它。

第二:FirebaseListeners - 正如方法的名称所说 (addEventListener()) 已添加且未设置,这意味着如果您再次添加...您将以两个侦听器结束并且您将下载数据两次...非常危险...使用命名变量来定义侦听器,并且不要使用在恢复状态时触发的匿名初始化程序。

还有,极其重要。我提供的 LiveData 链接上未显示某些内容...

getValue.add(myItem) 不会触发观察者。

它只会在极其特殊的情况下触发它,比如如果你在 LiveData 的 onActive() 上调用的可运行文件中调用它。

正确的方法......例如,如果你想替换里面的东西,是:

private void replace2List(@NonNull DataSnapshot dataSnapshot) {

    if (getValue()!= null) {
        for (DataSnapshot mDataSnapshot: ((List<DataSnapshot>) getValue())
             ) {
            if (dataSnapshot.getKey().equals(mDataSnapshot.getKey()) && dataSnapshot.getValue() != mDataSnapshot.getValue()){
            int i = getValue().indexOf(mDataSnapshot);
                List<DataSnapshot> snapshots = (List<DataSnapshot>)getValue();
                snapshots.set(i,dataSnapshot);
                setValue(snapshots);
            }
        }
    }
}

最后是解决创建这篇文章的问题的东西:

适配器内部:

@Override
public void submitList(@Nullable List<DataSnapshot> list) {
    Log.d(TAG, "submitList: list is: " + list.toString());

    notifyDataSetChanged();
    super.submitList(list);
}

如果您将 notifyDatasetChanged() 放在超级下方,它将不起作用!

在 RecyclerView 的父 Fragment 中:

@Override
            public void onChanged(List<DataSnapshot> snapshots) {
                adapter.submitList(snapshots);
                binding.itineraryRecyclerView.smoothScrollToPosition(0);
            }

这是次要措施,以防万一!!使用你的 RecyclerView 不会失败。

0 used of you want to go to the top of the list...adapter.getItemCount() 如果你想去最后一项。

为什么会这样??我认为这与片段中的片段有关......

于 2019-08-02T20:44:57.617 回答