【问题标题】:Why LiveData setValue or PostValue triggers onChange just once in the view?为什么 LiveData setValue 或 PostValue 在视图中只触发一次 onChange?
【发布时间】:2018-06-21 06:56:14
【问题描述】:

LiveData setValue 应该触发了 Activity 中的 onChanged 方法,但是它只在第一次调用,当我尝试进行分页后,它会中断并且不再调用 onChanged,尽管我的响应确实成功并且我在日志中看到它。 setValue/postValue 有什么问题?它是一个错误吗?我应该自己实现观察者模式吗?那么使用 LiveData 有什么意义呢?我的寻呼仅在这 2-3 天之内不起作用.....

  1. MainActivity 类

     public class MainActivity extends AppCompatActivity 
     private MutableLiveData<List<Photo>> mLivePhotos;
     // some code...
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
        mLivePhotos = loadData();
        mLivePhotos.observe(this, photos -> {
            Log.d(TAG, "onChanged!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
            mProgressBar.setVisibility(View.GONE);
            mPhotos = photos;
            if (mIsInitialCall) {
                initiateAdapter();
                mIsInitialCall = false;
            } else {
                mAdapter.updateList(mPhotos.subList(mPageNumber, mPageNumber + 10));
            }
        });
    
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                int lastPosition = 
    mLayoutManager.findLastCompletelyVisibleItemPosition();
                Log.d(TAG, "onScrolled - lastPosition: " + lastPosition);
                if (lastPosition == mLayoutManager.getItemCount() - 1) {
                    Log.d(TAG, "onScrolled - End of list?");
                    loadData();
                }
            }
        });
    }
    
    private MutableLiveData<List<Photo>> loadData() {
        Log.d(TAG, "loadData");
        if (mArticleViewModel == null) return null;
        mPageNumber += 10;
        mProgressBar.setVisibility(View.VISIBLE);
        return mArticleViewModel.loadPhotos();
    }
    
  2. 视图模型

    public class ArticleViewModel extends ViewModel {
        private MutableLiveData<List<Photo>> photos;
        private ArticleRepository articleRepository;
    
        public MutableLiveData<List<Photo>> loadPhotos() {
            Log.d(TAG, "getArticleList");
            //TODO; add Dagger 2
            articleRepository = new ArticleRepository();
            photos = articleRepository.getPhotos();
            return photos;
        }
    
  3. 存储库

    public class ArticleRepository {
    
        public MutableLiveData<List<Photo>> getPhotos() {
            final MutableLiveData<List<Photo>> result = new MutableLiveData<>();
            Log.d(TAG, "getResults");
            ApiService.getService().getPhotos().enqueue(new Callback<List<Photo>>() {
            @Override
            public void onResponse(Call<List<Photo>> call, Response<List<Photo>> response) {
                Log.d(TAG, "onResponse");
                if (response.isSuccessful()) {
                    Log.d(TAG, "isSuccessful");
                    result.postValue(response.body());
                }
            }
    
            @Override
            public void onFailure(Call<List<Photo>> call, Throwable t) {
                Log.d(TAG, "onFailure: " + t.getMessage() + "\n" +  t.getStackTrace());
            }
        });
        return result;
    }
    

【问题讨论】:

    标签: java android kotlin android-livedata android-paging


    【解决方案1】:

    Activity 不应该有任何 MutablieLiveData 成员变量,它应该在 ViewModel 中。

    它只在第一次工作的原因,是因为你第一次观察到的东西它通知为改变,但是因为你的安排不正确,它永远不会再次更新。也就是说,由于 ArticleRepository 在您的 ViewModel 中再次使用一组新的 MutableLiveData 重新创建,您之前订阅的内容不再相关 - 您只需订阅一次 onCreate()。

    您应该将绑定与异步任务(例如loadData())分开,它们不是一回事。绑定是您在开始收集 MutableLiveData 时所做的事情(您在 loadData 中所做的事情),但是一旦您完成了该操作,就不应再这样做了。

    我还注意到您实际上在模型中包含 LiveData,不建议这样做,因为它会破坏模式并可能带来其他问题。准备演示文稿的是 ViewModel,而不是 Repository。由于您当前已经配置了一些东西,您的存储库也可以称为 ViewModel。相反,您应该做的是使用 observables 通知 ViewModel 有一个新批次来发布或处理可能发生的错误。

    研究这个例子:https://developer.android.com/topic/libraries/architecture/viewmodel

    请注意,loadUsers() 在调用 getUsers() 时会执行一次。这就是将 Activity 绑定到 ViewModel 的原因。但是loadUsers() 可以稍后再进行,并且应该将更改发布到 ViewModel 内的 LiveData。

    【讨论】:

    • 感谢您的精彩解释,我将重写结构,看看会发生什么
    • 我掉进了另一个陷阱:第一次从viewModel获取数据来调用observe,因为异步调用返回null,我需要在MainActivity中知道何时准确获取数据以便调用观察它,另一方面,我应该观察数据以了解它何时更新。死循环。 mLivePhotos = loadData() 数据尚未加载,我在空对象上调用 mLivePhotos 观察。 LiveData 没有达到它的目的?为了开始观察,我需要将 MainActivity 实例传递给 ViewModel,它不应该持有对视图的引用
    • 为了观察模型,您可以使用 Android 服务,例如 AsyncTask 或类似 RXJava。我很难评论您的具体问题,因为我面前没有您的代码。你甚至可以不使用简单的 java Observable,但你必须注意 UI 位于不同的线程上。
    • "it returns null because of asynchronous call," 它返回 null,因为您在某处调用了 postValue(null) / setValue(null)
    • @pskink 你是对的,救了我! Bot 现在我想知道异步调用如何及时适应?如果响应迟于对数据调用观察怎么办?让我觉得......
    【解决方案2】:

    您不应该在每次加载更多照片时都使用new MutableLiveData(),而是在单个MutableLiveData 对象上调用postValue。示例代码如下:

    视图模型:

    public class ArticleViewModel extends ViewModel {
        private MutableLiveData<List<Photo>> photos;
    
        public MutableLiveData<List<Photo>> getPhotoList() {
            Log.d(TAG, "loadData");
            if (photos == null) {
                photos = new MutableLiveData<>();
                loadPhotos();
            }
            return photos;
        }
    
        public void loadPhotos() {
            Log.d(TAG, "getArticleList");
    
            ApiService.getService().getPhotos().enqueue(new Callback<List<Photo>>() {
                @Override
                public void onResponse(Call<List<Photo>> call, Response<List<Photo>> response) {
                    Log.d(TAG, "onResponse");
                    if (response.isSuccessful()) {
                        Log.d(TAG, "isSuccessful");
                        List<Photo> morePhotos = response.body();
                        List<Photo> allPhotos = new ArrayList<>();
                        allPhotos.addAll(photos.getValue());
                        allPhotos.addAll(morePhotos);
                        photos.postValue(allPhotos);   // <--- post change here
                    }
                }
    
                @Override
                public void onFailure(Call<List<Photo>> call, Throwable t) {
                    Log.d(TAG, "onFailure: " + t.getMessage() + "\n" +  t.getStackTrace());
                }
            });
        }
    }
    

    主活动:

    public class MainActivity extends AppCompatActivity 
        private ArticleViewModel mArticalViewModel;
    
        // some code...
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            mArticalViewModel = ViewModelProviders.of(this).get(ArticleViewModel.class);
            mArticleViewModel.getPhotoList().observe(this, photos -> {
                Log.d(TAG, "onChanged!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
            mProgressBar.setVisibility(View.GONE);
            mPhotos = photos;
            if (mIsInitialCall) {
                initiateAdapter();
                mIsInitialCall = false;
            } else {
                mAdapter.updateList(mPhotos.subList(mPageNumber, mPageNumber + 10));
            }
        });
    
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                int lastPosition = 
    mLayoutManager.findLastCompletelyVisibleItemPosition();
                Log.d(TAG, "onScrolled - lastPosition: " + lastPosition);
                if (lastPosition == mLayoutManager.getItemCount() - 1) {
                    Log.d(TAG, "onScrolled - End of list?");
                    mArticleViewModel.loadPhotos(); // <--- changed here
                }
            }
        });
    }
    

    【讨论】:

    • 这看起来可以解决问题,但我不喜欢将数据层也变成表示层。 :)
    • 感谢您指出:),已将MutableLiveData 从活动中移出。
    • @Hong Duan 我这里也有类似的问题:stackoverflow.com/questions/57650511/…。在我的存储库中使用 MutableLiveData、loadPhotos() 和 postValue() 有帮助吗?如果您能提供任何想法,我将不胜感激......
    【解决方案3】:

    回收器适配器“arraylist.addAll 或 list.addAll”中的“mAdapter.updateList”函数是什么,viewModel 中的“loadPhotos”函数是否称为 onCreate?如果是这样,请尝试将“arraylist.addAll 或 list.addAll”更改为“arraylist = data 或 list = data”。并将 loadmore 函数与 firstCallApi(您放入 onCreate 的 viewmodel 函数)分开。

    所以 loadmore 函数用于更新数据,而 firstCall 用于初始数据。观察 loadmore 插入代码“arraylist.addAll 或 list.addAll”和 firstCall 插入“list = data”。

    示例: https://gist.github.com/kazuiains/c52f9fc597b66c05b7ec0439a0bb4ccb

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多