当notifyDataSetChanged与MVVM一起使用时,RecyclerView不会更新

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了当notifyDataSetChanged与MVVM一起使用时,RecyclerView不会更新相关的知识,希望对你有一定的参考价值。

我正在使用MVVM。主屏幕仅在调试期间(而不是在常规运行期间)显示电影的海报。

问题在于观察RecyclerView人口。在ObserverMainActivity。我希望notifyDataSetChanged方法在从API接收数据后会出现海报,但它不会发生。

我在https://github.com/RayaLevinson/Test中只提供与此问题相关的清理代码

我遗漏了与Observer有关的一些重要观点。请帮我!谢谢。

   protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecyclerView = findViewById(R.id.recycler_view_movie);

        mMainActivityViewModal = ViewModelProviders.of(this).get(MainActivityViewModel.class);
        mMainActivityViewModal.init();
        mMainActivityViewModal.getMovies().observe(this, new Observer<List<Movie>>() {
            @Override
            public void onChanged(@Nullable List<Movie> movies) {
                mAdapter.notifyDataSetChanged();
            }
        });

        initRecyclerView();
    }

    private void initRecyclerView() {
        mAdapter = new RecyclerViewAdapter(this, mMainActivityViewModal.getMovies().getValue());
        mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));
        mRecyclerView.setAdapter(mAdapter);
    }

MovieRepository.java

public class MovieRepository {

    private static final String TAG = "MovieRepository";
    private static String mSortBy = "popular";

    private static MovieRepository instance;
    private List<Movie> movies = new ArrayList<>();

    public static MovieRepository getInstance() {
        if (instance == null) {
            instance = new MovieRepository();
        }
        return instance;
    }

    public MutableLiveData<List<Movie>> getMovies() {
        setMovies();

        MutableLiveData<List<Movie>> data = new MutableLiveData<List<Movie>>();
        data.setValue(movies);
        return data;
    }

    private void setMovies() {
        Context context = GlobalApplication.getAppContext();

        if (NetworkUtils.isNetworkAvailable(context)) {
            movies.clear();
            new MovieRepository.FetchMoviesTask().execute(mSortBy);
        } else {
            alertUserAboutNetworkError();
        }
    }

    private void alertUserAboutNetworkError() {
        Context context = GlobalApplication.getAppContext();
     //   Toast.makeText(context, R.string.networkErr, Toast.LENGTH_LONG).show();
    }

    private class FetchMoviesTask extends AsyncTask<String, Void, List<Movie>> {

        @Override
        protected List<Movie> doInBackground(String... params) {

            if (params.length == 0) {
                return null;
            }

            String sortBy = params[0];

            Log.d(TAG, "In doInBackground " + sortBy);
            URL moviesRequestUrl = NetworkUtils.buildUrl(sortBy);

            try {
                String jsonWeatherResponse = NetworkUtils.getResponseFromHttpUrl(moviesRequestUrl);

                return MovieJsonUtils.getMoviesDataFromJson(jsonWeatherResponse);

            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }

        @Override
        protected void onPostExecute(List<Movie> parsedMoviesData) {
            if (parsedMoviesData != null) {
                for (Movie movie : parsedMoviesData) {
                    movies.add(movie);
                    Log.d(TAG, "In onPostExecute " + " movie was added");
                }

            }
        }
    }
}


MainActivityViewModel.java

public class MainActivityViewModel extends ViewModel {
    private MutableLiveData<List<Movie>> mMovies;
    private MovieRepository mMoviewRepository;

    public void init() {
        if (mMovies != null) {
            return;
        }
        mMoviewRepository = MovieRepository.getInstance();
        mMovies = mMoviewRepository.getMovies();
    }

    public LiveData<List<Movie>> getMovies() {
        return mMovies;
    }
}

RecyclerViewAdapter.java
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {

    private static final String TAG = "RecyclerViewAdapter";
    private final Context mContext;
    private List<Movie> mMovies;

    public RecyclerViewAdapter(Context mContext, List<Movie> movies) {
        this.mMovies = movies;
        this.mContext   = mContext;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_list_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
        Log.d(TAG, "onBindViewHolder called");

        Picasso.get()
                .load(mMovies.get(holder.getAdapterPosition()).getPosterPath())
                .placeholder(R.mipmap.ic_launcher)
                .into(holder.image);


    }

    @Override
    public int getItemCount() {
        return mMovies.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        final ImageView image;
        final LinearLayout parentLayout;

        private ViewHolder(@NonNull View itemView) {
            super(itemView);

            image = itemView.findViewById(R.id.image);
            parentLayout = itemView.findViewById(R.id.parent_layout);
        }
    }

    public void update(List<Movie> movies) {
        mMovies.clear();
        mMovies.addAll(movies);
        notifyDataSetChanged();
    }
}
答案

你的MovieRepository#getMovies()在AsyncTask完成之前执行Livedata.setValue()。您可以在调试输出中看到它。

你要做的就是在你的postValue()方法中调用onPostExecute()(导致你的on on on mainthread)。然后你必须从mAdapter.update()方法调用onChanged()

另外我建议稍微重构一下ViewModel。从init()方法中删除对存储库的调用,并创建一个仅从repo调用load函数的新方法。因此,如果您以后想要支持无限滚动等内容,这将对您有所帮助。

只是意见问题,但我喜欢在我的ViewModel中创建我的observable而不是在Repository中,并将其作为参数传递。这是它的样子:

活动

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    viewModel = ViewModelProviders.of(this).get(YOUR_VIEW_MODEL.class);
    viewModel.init();
    viewModel.getItemsObservable().observe(this, new Observer<List<Item>>() {
        @Override
        public void onChanged(@Nullable List<Item> items) {
            // Add/replace your existing adapter 
            adapter.add/replaceItems(items);
            // For better performance when adding/updating elements you should call notifyItemRangeInserted()/notifyItemRangeChanged(). For replacing the whole dataset notifyDataSetChanged() is fine
            adapter.notifyDataSetChanged();

            // Normally i would put those calls inside the adapter and make appropriate methods but for demonstration.
        }
    });

    initRecyclerView();
    viewModel.loadItems()
}

视图模型

public void init(){
    repository = Repository.getInstance();
}

public void loadItems(){
    repository.loadItems(getItemsObservable());
}

public LiveData<List<Item>> getItemsObservable() {
    if (items == null) {
        items = new MutableLiveData<>();
    }

    return items;
}

知识库

public void loadItems(LiveData<List<Item>> liveData){
    List<Item> data = remote.getDataAsync(); // get your data asynchronously 
    liveData.postValue(data);  // call this after you got your data, in your case inside the onPostExecute() method
}

以上是关于当notifyDataSetChanged与MVVM一起使用时,RecyclerView不会更新的主要内容,如果未能解决你的问题,请参考以下文章

我需要简单的例子来在数据网格中使用组合框(MVV

Android源码与设计模式之notifyDataSetChanged()方法与观察者模式

安卓:notifyDataSetChanged();不工作

FragmentPagerAdapter与FragmentStatePagerAdapter区别

Android ListView 适配器 notifyDataSetInvalidated() 与 notifyDataSetChanged()

ArrayAdapter 未使用 notifyDataSetChanged() 进行更新