Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
245 views
in Technique[技术] by (71.8m points)

java - LiveData isn't being observed properly (gets null) when using Android Pagination Library

I am trying to update the UI depending on whether the data is being loaded or has loaded but it is not working properly. I am using enum class for different states. Initially the error was

Attempt to invoke virtual method 'void androidx.lifecycle.LiveData.observe(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Observer)' on a null object reference

Then I passed an empty new MutableLiveData()<>. Now, it doesn't crashes the application, however, the getDataStatus() observer isn't working correctly. Kindly look at my implementations and see if they are right.

DataSource

public class ArticlesDataSource extends PageKeyedDataSource<Integer, NewsItem> {

    private static final int FIRST_PAGE = 1;
    private static final String TAG = "ArticlesDataSource";
    public static final String SORT_ORDER = "publishedAt";
    public static final String LANGUAGE = "en";
    public static final String API_KEY = Utils.API_KEY;
    public static final int PAGE_SIZE = 10;

    private String mKeyword;
    private MutableLiveData<DataStatus> dataStatusMutableLiveData = new MutableLiveData<>();

    public ArticlesDataSource(String keyword) {
        mKeyword = keyword;
        dataStatusMutableLiveData = new MutableLiveData<>();
    }

    public MutableLiveData<DataStatus> getDataStatusMutableLiveData() {
        return dataStatusMutableLiveData;
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, NewsItem> callback) {
        dataStatusMutableLiveData.postValue(DataStatus.LOADING);
        NewsAPI newsAPI = ServiceGenerator.createService(NewsAPI.class);
        Call<RootJsonData> call = newsAPI.searchArticlesByKeyWord(mKeyword, SORT_ORDER, LANGUAGE, API_KEY, FIRST_PAGE, PAGE_SIZE);
        call.enqueue(new Callback<RootJsonData>() {
            @Override
            public void onResponse(Call<RootJsonData> call, Response<RootJsonData> response) {
                if (response.body() != null) {
                    callback.onResult(response.body().getNewsItems(), null, FIRST_PAGE + 1);
                    dataStatusMutableLiveData.postValue(DataStatus.LOADED);
                }
            }

            @Override
            public void onFailure(Call<RootJsonData> call, Throwable t) {
                Log.d(TAG, "onFailure: " + t.getMessage());
                dataStatusMutableLiveData.postValue(DataStatus.ERROR);
            }
        });

    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, NewsItem> callback) {
        NewsAPI newsAPI = ServiceGenerator.createService(NewsAPI.class);
        Call<RootJsonData> call = newsAPI.searchArticlesByKeyWord(mKeyword, SORT_ORDER, LANGUAGE, API_KEY, FIRST_PAGE, PAGE_SIZE);
        call.enqueue(new Callback<RootJsonData>() {
            @Override
            public void onResponse(Call<RootJsonData> call, Response<RootJsonData> response) {
                // if the current page is greater than one
                // we are decrementing the page number
                // else there is no previous page
                Integer adjacentKey = (params.key > 1) ? params.key - 1 : null;
                if (response.body() != null) {
                    // passing the loaded data
                    // and the previous page key
                    callback.onResult(response.body().getNewsItems(), adjacentKey);
                }
            }

            @Override
            public void onFailure(Call<RootJsonData> call, Throwable t) {
                Log.d(TAG, "onFailure: " + t.getMessage());
            }
        });
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, NewsItem> callback) {
        NewsAPI newsAPI = ServiceGenerator.createService(NewsAPI.class);
        Call<RootJsonData> call = newsAPI.searchArticlesByKeyWord(mKeyword, SORT_ORDER, LANGUAGE, API_KEY, params.key, PAGE_SIZE);
        call.enqueue(new Callback<RootJsonData>() {
            @Override
            public void onResponse(Call<RootJsonData> call, Response<RootJsonData> response) {
                dataStatusMutableLiveData.postValue(DataStatus.LOADED);

                if (response.code() == 429) {
                    // no more results
                    List<NewsItem> emptyList = new ArrayList<>();
                    callback.onResult(emptyList, null);
                }

                if (response.body() != null) {
                    // if the response has next page
                    // incrementing the next page number
                    Integer key = params.key + 1;

                    // passing the loaded data and next page value
                    if (!response.body().getNewsItems().isEmpty()) {
                        callback.onResult(response.body().getNewsItems(), key);
                    }
                }
            }

            @Override
            public void onFailure(Call<RootJsonData> call, Throwable t) {
                Log.d(TAG, "onFailure: " + t.getMessage());
                dataStatusMutableLiveData.postValue(DataStatus.ERROR);
            }
        });
    }
}

DataSourceFactory


public class ArticlesDataSourceFactory extends DataSource.Factory {

   private final MutableLiveData<ArticlesDataSource> itemLiveDataSource;
    private String mQuery;
    private final LiveData<DataStatus> dataStatusLiveData = Transformations.switchMap(itemLiveDataSource, (itemDataSource) -> {
        return itemDataSource.getDataStatusMutableLiveData();
    });

    public ArticlesDataSourceFactory() {
        mQuery = "news";
        itemLiveDataSource = new MutableLiveData<>();
    }

    @Override
    public DataSource<Integer, NewsItem> create() {
        ArticlesDataSource itemDataSource = new ArticlesDataSource(mQuery);
        itemLiveDataSource.postValue(itemDataSource);
//        dataStatusMutableLiveData = itemDataSource.getDataStatusMutableLiveData();
        return itemDataSource;
    }

    public MutableLiveData<ArticlesDataSource> getArticlesLiveDataSource() {
        return itemLiveDataSource;
    }

    public void setQuery(String query) {
        mQuery = query;
    }

    public MutableLiveData<DataStatus> getDataStatusMutableLiveData() {
        return dataStatusMutableLiveData;
    }

    public void setDataStatusMutableLiveData(DataStatus dataStatus){
        dataStatusMutableLiveData.postValue(dataStatus);
    }

    public LiveData<DataStatus> getDataStatusLiveData() {
        return dataStatusLiveData;
    }
}

ViewModel

public class ArticlesViewModel extends ViewModel {

    public LiveData<PagedList<NewsItem>> itemPagedList;
    private MutableLiveData<ArticlesDataSource> liveDataSource;
    private ArticlesDataSourceFactory articlesDataSourceFactory;
    private LiveData dataStatus = new MutableLiveData<>();

    public ArticlesViewModel() {

        articlesDataSourceFactory = new ArticlesDataSourceFactory();
        liveDataSource = articlesDataSourceFactory.getArticlesLiveDataSource();
        dataStatus = articlesDataSourceFactory.getDataStatusMutableLiveData();

        PagedList.Config pagedListConfig =
                (new PagedList.Config.Builder())
                        .setEnablePlaceholders(false)
                        .setPageSize(10).build();

        itemPagedList = (new LivePagedListBuilder(articlesDataSourceFactory, pagedListConfig)).build();
    }

    public void setKeyword(String query) {
        if (query.equals("") || query.length() == 0)
            articlesDataSourceFactory.setDataStatusMutableLiveData(DataStatus.EMPTY);
        else {
            articlesDataSourceFactory.setQuery(query);
            refreshData();
        }
    }

    void refreshData() {
        if (itemPagedList.getValue() != null) {
            itemPagedList.getValue().getDataSource().invalidate();
        }
    }

    public LiveData<DataStatus> getDataStatus() {
        return dataStatus;
    }
}

Fragment

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View rootView = inflater.inflate(R.layout.fragment_articles, container, false);

        mContext = getActivity();
        progressBar = rootView.findViewById(R.id.progress_circular);
        emptyStateTextView = rootView.findViewById(R.id.empty_view);
        swipeRefreshLayout = rootView.findViewById(R.id.swipe_refresh);
        textViewTitle = rootView.findViewById(R.id.text_view_top_headlines);
        recyclerView = rootView.findViewById(R.id.recycler_view);

        if (savedInstanceState != null) {
            keyword = savedInstanceState.getString("keyword");
        }

        initEmptyRecyclerView();

        articlesViewModel = ViewModelProviders.of(this).get(ArticlesViewModel.class);
        articlesViewModel.itemPagedList.observe(getViewLifecycleOwner(), new Observer<PagedList<NewsItem>>() {
            @Override
            public void onChanged(PagedList<NewsItem> newsItems) {
                adapter.submitList(newsItems);
                // TODO: Handle UI changes
                //  handleUIChanges(newsItems);
            }
        });
        articlesViewModel.getDataStatus().observe(getViewLifecycleOwner(), new Observer<DataStatus>() {
            @Override
            public void onChanged(DataStatus dataStatus) {
                switch (dataStatus) {
                    case LOADED:
                        progressBar.setVisibility(View.GONE);
                        emptyStateTextView.setVisibility(View.INVISIBLE);
                        swipeRefreshLayout.setRefreshing(false);
                        textViewTitle.setVisibility(View.VISIBLE);
                        break;
                    case LOADING:
                        progressBar.setVisibility(View.VISIBLE);
                        swipeRefreshLayout.setRefreshing(true);
                        textViewTitle.setVisibility(View.INVISIBLE);
                        emptyStateTextView.setVisibility(View.INVISIBLE);
                        break;
                    case EMPTY:
                        progressBar.setVisibility(View.GONE);
                        swipeRefreshLayout.setRefreshing(false);
                        textViewTitle.setVisibility(View.INVISIBLE);
                        emptyStateTextView.setVisibility(View.VISIBLE);
                        emptyStateTextView.setText(R.string.no_news_found);
                        break;
                    case ERROR:
                        progressBar.setVisibility(View.GONE);
                        swipeRefreshLayout.setRefreshing(false);
                        textViewTitle.setVisibility(View.INVISIBLE);
                        emptyStateTextView.setVisibility(View.VISIBLE);
                        emptyStateTextView.setText(R.string.no_internet_connection);
                        break;
                }
            }
        });

        swipeRefreshLayout.setOnRefreshL

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

When you call invalidate(), a new datasource will be created by the factory. However, you are directly exposing the data status liveData of the "current" created datasource, without taking into consideration that more will be created in the future.

The solution is to store the current data source in the factory in a MutableLiveData, and expose the "most recent current data status" using switchMap.

public class ArticlesDataSourceFactory extends DataSource.Factory {

    private final MutableLiveData<ArticlesDataSource> itemLiveDataSource = new MutableLiveData<>();
    private String mQuery = "news";
    private final LiveData<DataStatus> dataStatusLiveData = Transformations.switchMap(itemLiveDataSource, (itemDataSource) -> {
        return itemDataSource.getDataStatusMutableLiveData();
    });

    public ArticlesDataSourceFactory() {
    }

    @Override
    public DataSource<Integer, NewsItem> create() {
        ArticlesDataSource itemDataSource = new ArticlesDataSource(mQuery);
        itemLiveDataSource.postValue(itemDataSource);
    ...

    public LiveData<DataStatus> getDataStatusLiveData() {
        return dataStatusLiveData;
    }

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...