RecyclerView не обновляется, когда notifyDataSetChanged используется с MVVM - PullRequest
0 голосов
/ 14 апреля 2019

Я работаю с MVVM. Главный экран показывает постеры фильма только во время отладки (а не во время обычного запуска).

Проблема в наблюдении за RecyclerView населением. Observer в MainActivity. Я ожидаю, что метод 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();
    }
}

1 Ответ

0 голосов
/ 14 апреля 2019

Ваш MovieRepository#getMovies() выполняет Livedata.setValue() до завершения AsyncTask.Вы можете видеть это в своем отладочном выводе.

Что вам нужно сделать, это вызвать postValue() (не включайте основной поток) в вашем методе onPostExecute().Затем вы должны вызвать mAdapter.update() из метода onChanged().

Также я бы порекомендовал немного реорганизовать вашу ViewModel.Удалите вызов хранилища из вашего метода init() и создайте новый метод, который вызывает только функцию загрузки из хранилища.Так что если вы позже захотите поддерживать такие вещи, как бесконечная прокрутка, это вам очень поможет.

Просто вопрос мнения, но мне нравится создавать свои наблюдаемые внутри моей ViewModel, а не в репозитории и передавать их в качестве параметра.Вот как это может выглядеть:

Активность

@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()
}

ViewModel

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
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...