I have a MVVM implementation to fetch data from internet using Retrofit2, Rxjava and Rxandroid.
My goal is to refresh the data when the user Swipes in the SwipeRefreshLayout. This is the implementation.
NewsFeedFragment.java
#Inject
ViewModelFactory viewModelFactory;
RandomVideosViewModel viewModel;
#Override
public void onAttach(Context context) {
((BaseApplication) context.getApplicationContext())
.getAppComponent()
.inject(this);
super.onAttach(context);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
...
viewModel = ViewModelProviders.of(this, viewModelFactory).get(RandomVideosViewModel.class);
VideoCardAdapter videoCardAdapter = new VideoCardAdapter(getActivity());
viewModel.videosList.observe(this, videoCardAdapter::submitList);
viewModel.networkState.observe(this, videoCardAdapter::setNetworkState);
mRecyclerView.setAdapter(videoCardAdapter);
...
}
RandomVideosViewModel.java
public class RandomVideosViewModel extends ViewModel {
public LiveData<PagedList<Video>> videosList;
public LiveData<NetworkState> networkState;
public RandomVideosViewModel(RandomVideosDataSourceFactory randomVideosDataSourceFactory) {
networkState = Transformations.switchMap(randomVideosDataSourceFactory.getMutableLiveData(),
PageKeyedRandomVideosDataSource::getNetworkState);
PagedList.Config pagedListConfig = (new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(10)
.setPageSize(10))
.build();
videosList = (new LivePagedListBuilder(randomVideosDataSourceFactory, pagedListConfig)).build();
}
}
RandomVideosDataSourceFactory
public class RandomVideosDataSourceFactory extends DataSource.Factory {
private MutableLiveData<PageKeyedRandomVideosDataSource> mutableLiveData = new MutableLiveData<>();
private PageKeyedRandomVideosDataSource pageKeyedRandomVideosDataSource;
public RandomVideosDataSourceFactory(PageKeyedRandomVideosDataSource pageKeyedRandomVideosDataSource) {
this.pageKeyedRandomVideosDataSource = pageKeyedRandomVideosDataSource;
}
public MutableLiveData<PageKeyedRandomVideosDataSource> getMutableLiveData() {
return mutableLiveData;
}
#Override
public DataSource create() {
mutableLiveData.postValue(pageKeyedRandomVideosDataSource);
return pageKeyedRandomVideosDataSource;
}
}
PageKeyedRandomVideosDataSource
public class PageKeyedRandomVideosDataSource extends PageKeyedDataSource<Integer, Video> {
private static final String TAG = "Refresh - PageKeyedRand";
private CompositeDisposable disposable = new CompositeDisposable();
private MutableLiveData<NetworkState> networkState = new MutableLiveData<>();
private MutableLiveData<NetworkState> initialLoading = new MutableLiveData<>();
private Repository repository;
private Random random = new Random();
private int firstPage;
private List<Integer> usedNumbers = new ArrayList<>();
private int numbOfTimes = 0;
public PageKeyedRandomVideosDataSource(Repository repository) {
this.repository = repository;
}
public MutableLiveData<NetworkState> getNetworkState() {
return networkState;
}
public MutableLiveData<NetworkState> getInitialLoading() {
return initialLoading;
}
#Override
public void
loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, Video> callback) {
List<Video> videoList = new ArrayList<>();
networkState.postValue(NetworkState.LOADING);
initialLoading.postValue(NetworkState.LOADING);
firstPage = getFirstPage();
usedNumbers.add(firstPage);
numbOfTimes++;
Log.i(TAG, "loadInitial: Loading page: " + firstPage + " - Size: " + params.requestedLoadSize);
disposable.add(RxHelper.getObservable(repository.getHomeVideosObservable(params.requestedLoadSize, firstPage))
.subscribe(requestVideo -> {
if (requestVideo != null) {
int lastPage = requestVideo.getLastPage();
int nextPage = getRandomPage(lastPage);
Log.i(TAG, "loadInitial: NextPage: " + nextPage);
videoList.addAll(requestVideo.getVideoList());
callback.onResult(videoList, null, nextPage);
networkState.postValue(NetworkState.LOADED);
initialLoading.postValue(NetworkState.LOADED);
}
}, throwable -> {
throwable.printStackTrace();
networkState.postValue(NetworkState.failed(throwable.getMessage()));
initialLoading.postValue(NetworkState.failed(throwable.getMessage()));
}));
}
#Override
public void loadBefore(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, Video> callback) {
}
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, Video> callback) {
Log.i(TAG, "loadAfter: Loading page: " + params.key + " - Size: " + params.requestedLoadSize);
List<Video> videoList = new ArrayList<>();
networkState.postValue(NetworkState.LOADING);
disposable.add(RxHelper.getObservable(repository.getHomeVideosObservable(params.requestedLoadSize, params.key))
.subscribe(requestVideo -> {
if (requestVideo != null) {
int lastPage = requestVideo.getLastPage();
int nextPage = getRandomPage(lastPage);
Log.i(TAG, "loadAfter: NextPage: " + nextPage);
videoList.addAll(requestVideo.getVideoList());
callback.onResult(videoList, nextPage);
networkState.postValue(NetworkState.LOADED);
}
}, throwable -> {
throwable.printStackTrace();
networkState.postValue(NetworkState.failed(throwable.getMessage()));
}));
}
The RandomVideosDataSourceFactory just retrieves the PageKeyedRandomVideosDataSource and this last one loads the data in chunk of pages.
Question
Once this data is loaded, I am not finding a way to refresh it when user uses the SwipeRefreshLayout. Any clue?
What I tried
I tried to recreate the ViewModel, but it seems it keeps retrieving the same instance.
Typically, the best way is the abstract data collection from the ViewModel by placing it into a Repository.
The Repository is a singleton. It has a LiveData that the ViewModel observes.
The Repository gets the data initially from a cache (Room for example). It also observes that data.
When you need a new set of data (first load, swiping on refresh or even a Worker) you call your Repository to start updating the cache. That will automatically update the data in your ViewModel (and anything else that is observing the ViewModel).
Related
I am removing an item in PagedListAdapter. I am not using room, simply I store a cached list inMemoryElements of all the elements I have loaded. When I remove one item, I just delete that item from the list, and then call DataSource.invalidate(), which calls again LoadInitialCallback.onResult() where I pass my cached list.
Everything works fine the first time I delete one item, but the second time I delete other item, I get:
java.lang.IndexOutOfBoundsException: Index: 18, Size: 18 at java.util.ArrayList.get(ArrayList.java:437) at androidx.paging.PagedStorage.get(PagedStorage.java:174) at androidx.paging.PagedStorageDiffHelper$1.areItemsTheSame(PagedStorageDiffHelper.java:77)
Theese are my components:
In my Fragment, where I have the paginated RecyclerView, here I call the ViewModel method makeCallGetArticleListPaging for loading the elements into LiveData wrap. I then call function getPagedList for subscribing to LiveData<PagedList<ArticleDto>> values.
In DataSourceFactory: I have a reference of current datasource for calling .invalidate()
Reposiroty: where I create the pagedList.
Finally when item is deleted, I call ViewModel method invalidateDatasource()
DataSource:
public class ArticleDataSource extends PageKeyedDataSource<Integer, ArticleDto> {
ApiService apiService;
private List<ArticleDto> inMemoryElements;
public ArticleDataSource(ApiService apiService, String apiServiceMethod, List<ArticleDto> inMemoryElements, int currentPage) {
this.apiService = apiService;
this.inMemoryElements = inMemoryElements;
this.currentPage = currentPage;
}
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, ArticleDto> callback) {
queryArticleDto.setPageIndex(currentPage);
HashMap<String,String> headers = new HashMap<String, String>();
headers.put("Content-Type", HttpConstants.HTTP_CONTENT_TYPE);
if(inMemoryElements.isEmpty()){
apiService.search(headers,queryArticleDto).enqueue(new Callback<PageResultDto<ArticleDto>>() {
#Override
public void onResponse(Call<PageResultDto<ArticleDto>> call, Response<PageResultDto<ArticleDto>> response) {
inMemoryElements.addAll(response.body().getElements());
callback.onResult(response.body().getElements(), null, 1);
}
#Override
public void onFailure(Call<PageResultDto<ArticleDto>> call, Throwable t) {
callback.onResult(new ArrayList<>(), null, 0);
}
});
}else{
callback.onResult(inMemoryElements,null,currentPage + 1);
}
}
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull final LoadCallback<Integer, ArticleDto> callback) {
int currentPage = params.key;
queryArticleDto.setPageIndex(currentPage);
apiService.search(headers,queryArticleDto).enqueue(new Callback<PageResultDto<ArticleDto>>() {
#Override
public void onResponse(Call<PageResultDto<ArticleDto>> call, Response<PageResultDto<ArticleDto>> response) {
if (response.isSuccessful()) {
inMemoryElements.addAll(response.body().getElements());
callback.onResult(response.body().getElements(), currentPage + 1);
}
}
#Override
public void onFailure(Call<PageResultDto<ArticleDto>> call, Throwable t) {
callback.onResult(new ArrayList<>(), currentPage);
}
});
}
}
DataSourceFactory:
public class ArticleDataSourceFactory extends DataSource.Factory<Integer, ArticleDto> {
//To perform network calls
private ApiService apiService;
private UserSessionLiveData userSessionLiveData;
private ArticleDto queryArticleDto;
private ArticleDataSource articleDataSource;
#Inject
public ArticleDataSourceFactory(ApiService apiService, UserSessionLiveData userSessionLiveData) {
this.apiService = apiService;
this.userSessionLiveData = userSessionLiveData;
}
public void setQueryArticleDto(ArticleDto queryArticleDto) {
this.queryArticleDto = queryArticleDto;
if(articleDataSource != null)
articleDataSource.setQueryArticleDto(queryArticleDto);
}
public void setApiServiceMethod(String apiServiceMethod) {
this.apiServiceMethod = apiServiceMethod;
}
//Factory method pattern implemented below
//Where a create method does the job of initializing the objects for client
#NonNull
#Override
public DataSource<Integer, ArticleDto> create() {
articleDataSource = new ArticleDataSource(apiService,
apiServiceMethod,
userSessionLiveData,
articleDataSource == null ? new ArrayList<>() : articleDataSource.getInMemoryElements(),
articleDataSource == null ? 0 : articleDataSource.getCurrentPage()
);
articleDataSource.setQueryArticleDto(queryArticleDto);
return articleDataSource;
}
public ArticleDataSource getArticleDataSource() {
return articleDataSource;
}}
PagedList:
public class ArticleRepository {
public LiveData<PagedList<ArticleDto>> getPagedList(ArticleDto articleDto, String apiServiceMethod) {
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPageSize(5)
.setPrefetchDistance(1)
.setMaxSize(10)
.build();
return new LivePagedListBuilder<>(articleDataSourceFactory, config)
.setFetchExecutor(Executors.newFixedThreadPool(5)) // Use five threads to do the fetching operations
.build();
}}
ViewModel:
public class ArticleViewModel extends ViewModel {
private final MutableLiveData<PagedList<ArticleDto>> _pagedList = new MutableLiveData<>();
private LiveData<PagedList<ArticleDto>> pagedList = (LiveData<PagedList<ArticleDto>>) _pagedList;
public void makeCallGetArticleListPaging(ArticleDto articleDto, String apiServiceMethod){
pagedList = articleRepository.getPagedList(articleDto,apiServiceMethod);
}
public LiveData<PagedList<ArticleDto>> getPagedList() {
return pagedList;
}}
Fragment:
public class ArticleListFragment extends Fragment {
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
articleViewModel.makeCallGetArticleListPaging(articleDto, ArticleDataSource.API_SERVICE_METHOD_SEARCH);
observeData();
}
private void observeData() {
articleViewModel.getPagedList().observe(getViewLifecycleOwner(), new Observer<PagedList<ArticleDto>>() {
#Override
public void onChanged(PagedList<ArticleDto> posts) {
postsPagedListAdapter.submitList(posts);
}
});
}
public void invalidateDatasource(int rowPosition){
articleDataSourceFactory.getArticleDataSource().getInMemoryElements().remove(rowPosition);
articleDataSourceFactory.getArticleDataSource().invalidate();
}}
DiffUtil.ItemCallback
new DiffUtil.ItemCallback<ArticleDto>() {
#Override
public boolean areItemsTheSame(#NonNull ArticleDto oldItem, #NonNull ArticleDto newItem) {
return oldItem.id == newItem.id;
}
#Override
public boolean areContentsTheSame(#NonNull ArticleDto oldItem, #NonNull ArticleDto newItem) {
return oldItem.equals(newItem);
}
};
I am using retrofit2 for fetching data from the server and after fetching saving data in room database and then showing in recycler view. But it is no displayed (my data what I get using retrofit). I try display it in my fragment. In file ...data/data/databese/source.db these data are saved. I see it. So, that means that my code works. But I can't understand why it is not displayed.
my database class:
#Database(entities = {Source.class}, exportSchema = false, version = 1)
public abstract class SourceDatabase extends RoomDatabase {
private static final String DB_NAME = "source.db";
public abstract SourceDao sourceDao();
private static SourceDatabase instance;
public static SourceDatabase getInstance(Context context) {
if (instance == null) {
instance =buildDatabaseInstance(context);
}
return instance;
}
private static SourceDatabase buildDatabaseInstance(Context context) {
return Room.databaseBuilder(context,
SourceDatabase.class,
DB_NAME).build();
}
}
repository:
public class DataBaseRepository {
private static DataBaseRepository dataBaseRepository;
private SourceDao sourceDao;
private LiveData<List<Source>> allSourcestoDb;
private Context context;
public static DataBaseRepository getInstance(Context context) {
if (dataBaseRepository == null) {
dataBaseRepository = new DataBaseRepository(context);
}
return dataBaseRepository;
}
public DataBaseRepository(Context context) {
this.context = context;
SourceDatabase db = SourceDatabase.getInstance(context);
sourceDao = db.sourceDao();
allSourcestoDb = sourceDao.getSources();
}
public void getSourceListTodb(String key) {//отправка данных в LiveData
RestClient restClient = RestClient.getInstance();
restClient.startRetrofit();
restClient.getServerApi().getNews(key).enqueue(new Callback<News>() {
#Override
public void onResponse(Call<News> call, Response<News> response) {
Completable.fromAction(new Action (){
#Override
public void run() throws Exception {
if (response.body() != null) {
List<Source> list = response.body().getSources();
sourceDao.insert(list);
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
}
#Override
public void onError(Throwable e) {
}
});
}
#Override
public void onFailure(Call<News> call, Throwable t) {
Log.d("error", "Can't parse data " + t);
}
});
}
public LiveData<List<Source>> getAllSourcestoDb() {
return allSourcestoDb;
}
}
dao:
#Dao
public interface SourceDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(List<Source> sources);
#Query("SELECT * FROM source")
LiveData<List<Source>> getSources();
}
viewModel:
public class SourceViewModel extends AndroidViewModel {
private DataBaseRepository dataBaseRepository;
private LiveData<List<Source>> allSources; //for db
public SourceViewModel(#NonNull Application application) {
super(application);
dataBaseRepository =DataBaseRepository.getInstance(application); //for db
allSources = dataBaseRepository.getAllSourcestoDb();
}
public LiveData<List<Source>> getAllSources() {
return allSources;
}
}
and fragment:
public class SavedDataFragment extends Fragment {
private SourceViewModel sourceViewModel;
private DataBaseRepository dataBaseRepository;
private RecyclerView recyclerView;
private List<Source> sourceList;
private SavedDataAdapter adapter;
public SavedDataFragment() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.saved_data,container,false);
DataSharedPreference sharedPreference = DataSharedPreference.getSPInstance();
String api_key = sharedPreference.loadText(getActivity());
dataBaseRepository = new DataBaseRepository(getActivity());
sourceViewModel = ViewModelProviders.of(this).get(SourceViewModel.class);
recyclerView = view.findViewById(R.id.recyclerViewSavedFragment);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
sourceList = new ArrayList<>();
adapter = new SavedDataAdapter(getActivity(), sourceList);
recyclerView.setAdapter(adapter);
sourceViewModel.getAllSources().observe(this, new Observer<List<Source>>() {
#Override
public void onChanged(List<Source> sources) {
adapter.setSourceList(sourceList);
}
});
dataBaseRepository.getSourceListTodb(api_key);
return view;
}
}
adapter:
public class SavedDataAdapter extends RecyclerView.Adapter<SavedDataAdapter.SourceSavedViewHolder> {
private LayoutInflater inflater;
private List<Source> sources;
public SavedDataAdapter(Context context, List<Source> sources) {
this.sources = sources;
this.inflater = LayoutInflater.from(context);
}
#NonNull
#Override
public SavedDataAdapter.SourceSavedViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.saved_item, parent, false);
return new SourceSavedViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull final SavedDataAdapter.SourceSavedViewHolder holder, int position) {
final Source source = sources.get(position);
holder.sourceId.setText(source.getId());
holder.sourceName.setText(source.getName());
holder.sourceDescription.setText(source.getDescription());
holder.sourceURL.setText(source.getUrl());
holder.sourceCategory.setText(source.getCategory());
holder.sourceLanguage.setText(source.getLanguage());
holder.sourceCountry.setText(source.getCountry());
}
#Override
public int getItemCount() {
return sources.size();
}
public void setSourceList(List<Source> sources) {
this.sources = sources;
notifyDataSetChanged();
}
public static class SourceSavedViewHolder extends RecyclerView.ViewHolder {
TextView sourceName, sourceId, sourceDescription, sourceURL, sourceCategory, sourceLanguage, sourceCountry;
public SourceSavedViewHolder(View view) {
super(view);
sourceName = view.findViewById(R.id.sourceName);
sourceId = view.findViewById(R.id.sourceIdItem);
sourceDescription = view.findViewById(R.id.sourceDescription);
sourceURL = view.findViewById(R.id.sourceURL);
sourceCategory = view.findViewById(R.id.sourceCategory);
sourceLanguage = view.findViewById(R.id.sourceLanguage);
sourceCountry = view.findViewById(R.id.sourceCountry);
}
}
}
In your Fragment inside onChanged,
you're setting adapter.setSourceList(sourceList) where sourceList is an empty arrayList.
You should instead setSourceList to sources which is the updated list passed as an argument to onChanged method
That is :-
sourceViewModel.getAllSources().observe(this, new Observer<List<Source>>() {
#Override
public void onChanged(List<Source> sources) {
adapter.setSourceList(sources); // sources and not sourceList
}
});
Also there are few more things that should be taken care of.
For ex- in your observe method, you have passed this as first argument which is wrong when using Fragments as it may causes memory leaks. Instead you should pass viewLifeOwner..
More can found on this link Use viewLifecycleOwner as the LifecycleOwner
Try ti change this:
#Query("SELECT * FROM source")
To:
#Query("SELECT * FROM Source")
I'm using PositionalDataSource and LiveData/PagedListAdapter
I want to make my recyclerview scroll to specific position when given a corresponding key value.
How to do this?
added my trial code : what i hope is when button clicked recyclerview method scrolltoposition works well.. but I know it's an incorrect way. I believe i need to use key instead of this direct access.
itemViewModel.getAllItems().observe(this, new Observer<List<Item>>() {
#Override
public void onChanged(#Nullable List<Item> itemList) {
itemViewModel.getPagedListLiveData(itemList).observe(MainActivity.this, new Observer<PagedList<Item>>() {
#Override
public void onChanged(#Nullable PagedList<Item> items) {
adapter.submitList(items);
}
});
}
});
binding.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ItemDataSource dataSource = itemViewModel.getDataSource();
dataSource.invalidate();
binding.rv.getLayoutManager().scrollToPosition(20000);
}
});
datasource class
public class ItemDataSource extends PositionalDataSource<Item> {
private List<Item> list;
public ItemDataSource(List<Item> list) {
this.list = list;
}
private int computeCount() {
return list.size();
}
private List<Item> loadRangeInternal(int startPosition, int loadCount) {
List<Item> modelList = new ArrayList<>();
int endPosition = Math.min(computeCount(), startPosition + loadCount);
for (int i = startPosition; i < endPosition; i++) {
modelList.add(list.get(i));
}
return modelList;
}
#Override
public void loadInitial(#NonNull LoadInitialParams params, #NonNull LoadInitialCallback<Item> callback) {
int totalCount = computeCount();
int position = computeInitialLoadPosition(params, totalCount);
int loadSize = computeInitialLoadSize(params, position, totalCount);
callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
}
#Override
public void loadRange(#NonNull LoadRangeParams params, #NonNull LoadRangeCallback<Item> callback) {
callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
}
repository class
public class ItemRepository {
private ItemDao itemDao;
private LiveData<List<Item>> allItems;
private MutableLiveData<PagedList<Item>> pagedListLiveData = new MutableLiveData<>();
private ItemDataSource dataSource;
public ItemRepository(Application application) {
ItemDatabase database = ItemDatabase.getInstance(application.getApplicationContext());
itemDao = database.itemDao();
allItems = itemDao.getAllItems();
}
public LiveData<PagedList<Item>> getPagedListLiveData(List<Item> itemList) {
dataSource = new ItemDataSource(itemList);
PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(200)
.setEnablePlaceholders(false)
.build();
PagedList<Item> itemPagedList = new PagedList.Builder<>(dataSource, config)
.setInitialKey(10000)
.setNotifyExecutor(new MainThreadExecutor())
.setFetchExecutor(Executors.newSingleThreadExecutor())
.build();
pagedListLiveData.setValue(itemPagedList);
return pagedListLiveData;
}
public ItemDataSource getDataSource() {...}
public LiveData<List<Item>> getAllItems() {...}
If you have a recyclerView object then you can use method scrollToPosition(position: Int)
I am working with MVVM. Main screen shows movie's posters only during debugging (and not during regular run).
The problem is in observation of RecyclerView population. There is Observer in MainActivity. I expect that notifyDataSetChanged method will cause
posters to appear after receiving data from the API, but it doesn't happen.
My cleaned code related to this issue only is available in https://github.com/RayaLevinson/Test
I am missing some important point related to Observer. Please help me! Thank you.
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();
}
}
Your MovieRepository#getMovies() executes the Livedata.setValue() before the AsyncTask finishes. You can see that in your debug output.
What you have to do is to call postValue() (cause your on not on the mainthread) in your onPostExecute() method. Then you have to call mAdapter.update() from the onChanged() method.
Also I would recommend to refactor your ViewModel a little bit. Remove the call to the repository from your init() method and create a new method that only calls the load function from the repo. So if you later on would like to support things like endless scrolling, this will help you a lot.
Just a matter of opinion, but i like to create my observables inside my ViewModel and not in the Repository and pass it along as parameter. Thats how it could look like:
Activity
#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;
}
Repository
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
}
I am creating an app with Android Paging Library. I'm using retrofit with it.
Retrofit code is in ItemDataSource and there i can't pass variable to it. I have some variable coming with intent. How can i set my variable in Retrofit Post method.
ItemDataSource
public class ItemDataSource extends PageKeyedDataSource<Integer, Item> {
//we will start from the first page which is 1
private static final int PAGE_NUMBER = 1;
//this will be called once to load the initial data
String table
ItemDataSource(String table){
this.table = table;
}
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull
final LoadInitialCallback<Integer, Item> callback) {
RetrofitClient.getInstance()
// I want to pass table variable here.
.getApi().getAnswers("table","","","",PAGE_NUMBER,"")
.enqueue(new Callback<StackApiResponse>() {
#Override
public void onResponse(Call<StackApiResponse> call,
Response<StackApiResponse> response) {
if (response.body() != null) {
callback.onResult(response.body().images, null,
PAGE_NUMBER + 1);
}
}
#Override
public void onFailure(Call<StackApiResponse> call,
Throwable
t) {
}
});
}
}
Main Activity
public class Detail extends AppCompatActivity {
ArrayList<Item> items;
Api api;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
// I'm getting intent here.
final RecyclerView recyclerView =
findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
ItemViewModel itemViewModel =
ViewModelProviders.of(this).get(ItemViewModel.class);
//creating the Adapter
//observing the itemPagedList from view model
itemViewModel.itemPagedList.observe(this, new
Observer<PagedList<Item>>() {
#Override
public void onChanged(#Nullable PagedList<Item> items) {
//in case of any changes
//submitting the items to adapter
adapter.submitList(items);
}
});
//setting the adapter
recyclerView.setAdapter(adapter);
}
}
Item View Model
public class ItemViewModel extends ViewModel {
//creating livedata for PagedList and PagedKeyedDataSource
LiveData<PagedList<Item>> itemPagedList;
LiveData<PageKeyedDataSource<Integer, Item>> liveDataSource;
//constructor
public ItemViewModel() {
//getting our data source factory
ItemDataSourceFactory itemDataSourceFactory = new
ItemDataSourceFactory();
//getting the live data source from data source factory
liveDataSource = itemDataSourceFactory.getItemLiveDataSource();
//Getting PagedList config
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setPageSize(10).build();
//Building the paged list
itemPagedList = (new LivePagedListBuilder(itemDataSourceFactory,
pagedListConfig))
.build();
}
}
BTW i'm following this https://www.simplifiedcoding.net/android-paging-library-tutorial/
for doing this, you have to create a constructor in ItemDataSource class which you did, a new object of that class is created in ItemDataSourceFactory so you have to create a constructor there to get the value and pass it to ItemDataSource. and you have to pass the value to ItemDataSourceFactory from your viewModel. this is how it should look (based on the link that you posted)
public class ItemViewModel extends ViewModel {
LiveData<PagedList<Item>> itemPagedList;
LiveData<PageKeyedDataSource<Integer, Item>> liveDataSource;
public ItemViewModel(String table) {
ItemDataSourceFactory itemDataSourceFactory = new ItemDataSourceFactory(table);
liveDataSource = itemDataSourceFactory.getItemLiveDataSource();
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setPageSize(ItemDataSource.PAGE_SIZE).build();
itemPagedList = (new LivePagedListBuilder(itemDataSourceFactory,pagedListConfig))
.build();
}}
then in your activity/fragment you should pass the value like this:
ItemViewModel itemViewModel = ViewModelProviders.of(this, new ViewModelProvider.Factory() {
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
return (T)new ItemViewModel ("your table name");
}
}).get(ItemViewModel.class);