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);
Related
I have JSON as below:
{
"Search": [
{
"Title": "Iron Man",
"Year": "2008"
},
{
"Title": "Iron Man2",
"Year": "20010"
},
{
"Title": "Iron Man3",
"Year": "2013"
}
]
}
Then I've created APIservice as below:
public interface MyApi {
#GET("/movies")
Call<ArrayList<MovieSearch>> getartistdata();
}
My data classes as per below
public class MovieSearch {
#SerializedName("Search")
public List<Search> Search =null;
}
public class Search {
#SerializedName("Title")
public String Title="";
#SerializedName("Year")
public String Year="";
#SerializedName("Poster")
public String Poster="";
public Search() {
}
public Search(String Title, String Year, String Poster) {
this.Title = Title;
this.Year = Year;
}
}
Now I'm trying to implement viewmodel class as below
public class MyListViewModel extends ViewModel {
public String Title = "";
public String Year= "";
public String Poster = "";
public MutableLiveData<ArrayList<MyListViewModel>> mutableLiveData = new MutableLiveData<>();
private ArrayList<MyListViewModel> arrayList;
private ArrayList<MovieSearch> myList;
public String getImageurl() {
return Poster;
}
#BindingAdapter({"imageUrl"})
public static void loadimage(ImageView imageView, String imageUrl) {
Glide.with(imageView.getContext()).load(imageUrl).apply(RequestOptions.circleCropTransform()).into(imageView);
//Picasso.with(imageView.getContext()).load(imageUrl).into(imageView);
}
public MyListViewModel() {
}
public MyListViewModel(MovieSearch myList) {
this.Title = myList.Title;
this.Poster = myList.Poster;
this.Year= myList.Year;
}
public MutableLiveData<ArrayList<MyListViewModel>> getMutableLiveData() {
arrayList = new ArrayList<>();
MyApi api = MyClient.getInstance().getMyApi();
Call<ArrayList<MovieSearch>> call = api.getartistdata();
call.enqueue(new Callback<ArrayList<MovieSearch>>() {
#Override
public void onResponse(Call<ArrayList<MovieSearch>> call, Response<ArrayList<MovieSearch>> response) {
myList = new ArrayList<>();
myList = response.body();
for (int i = 0; i < myList.size(); i++) {
MovieSearch myk = myList.get(i);
MyListViewModel myListViewModel = new MyListViewModel(myk);
arrayList.add(myListViewModel);
mutableLiveData.setValue(arrayList);
}
}
#Override
public void onFailure(Call<ArrayList<MovieSearch>> call, Throwable t) {
}
});
return mutableLiveData;
}
}
But I'm getting "Cannot resolve symbol" error on the following:
public MyListViewModel(MovieSearch myList) {
this.Title = myList.Title;
this.Poster = myList.Poster;
this.Year= myList.Year;
}
I'm trying to get the data from JSON and bind it to view holder. But I couldn't figure it out how to call it. Someone please help me to fix it.
You are pretty close, you just have to make few changes in service class and viewmodel to make this work. Make below changes
MyApi interface: -> becoz your json is not arraylist but an object of MovieSearch so make change accordingly inside that the arraylist of search is there.
public interface MyApi {
#GET("/movies")
Call<MovieSearch> getartistdata();
}
MyListViewModel class:
public class MyListViewModel extends ViewModel {
public MutableLiveData<ArrayList<Search>> mutableLiveData = new MutableLiveData<>();
#BindingAdapter({"imageUrl"})
public static void loadimage(ImageView imageView, String imageUrl) {
Glide.with(imageView.getContext()).load(imageUrl).apply(RequestOptions.circleCropTransform()).into(imageView);
//Picasso.with(imageView.getContext()).load(imageUrl).into(imageView);
}
public MyListViewModel() {
//do something else, your view model is not POJO it's the handler not storage.
}
public MutableLiveData<ArrayList<Search>> getSearchResults() {
MutableLiveData<ArrayList<Search>> localData = new MutableLiveData<>();
MyApi api = MyClient.getInstance().getMyApi();
Call<MovieSearch> call = api.getartistdata();
call.enqueue(new Callback<MovieSearch>() {
#Override
public void onResponse(Call<MovieSearch> call, Response<MovieSearch> response) {
List<Search> myList = response.body().Search;
mutableLiveData.setValue(myList);
localData.setValue(myList);
}
#Override
public void onFailure(Call<ArrayList<MovieSearch>> call, Throwable t) {
//handle the error
}
});
return localData;
}
}
Call the above view model by creating an object of view model and inside some function. this part usually goes inside some UI class activity or fragment:
//listening for change in live data
// this would be in ui layer
myListViewModelObject.getSearchResults().observe(this, new Observer<ArrayList<Search>>() {
#Override
public void onChanged(#Nullable ArrayList<Search> obj) {
// handle changes here, to use the data write code below
}
});
Advice: Don't use your viewmodel to store the data it's not what is made for. It is for handling the data and actions not the keeper of data. Managing the data and business logic in viewmodel can cause many problems. Always if possible break things into smaller parts like, get retrofit object from some other class or mathod don't put it in the calling method itself this will code duplication and may impact the performance also.
Note: I haven't tested the code just made the best possible changes and it should run. Please add the missing parts if i have removed any.
Here I noticed that you are keeping an array of ViewModels. The main thing is ViewModels are not meant to be used like that.
I think its better if you read some documentation and sample implementations of ViewModels to have a better understanding.
Coming to your implementation, everything looks fine but one thing that is converting your list of MovieSearch into a list of MyListViewModel.
In ViewModel
public class MyListViewModel extends ViewModel {
private MutableLiveData<MovieSearch> mutableLiveData = new MutableLiveData<>();
#BindingAdapter({"imageUrl"})
public static void loadimage(ImageView imageView, String imageUrl) {
Glide.with(imageView.getContext()).load(imageUrl).apply(RequestOptions.circleCropTransform()).into(imageView);
//Picasso.with(imageView.getContext()).load(imageUrl).into(imageView);
}
public LiveData<MovieSearch> getMutableLiveData() {
loadData();
return mutableLiveData;
}
private void loadData(){
MyApi api = MyClient.getInstance().getMyApi();
Call<MovieSearch> call = api.getartistdata();
call.enqueue(new Callback<MovieSearch>() {
#Override
public void onResponse(Call<MovieSearch> call, Response<MovieSearch> response) {
MovieSearch movieSearch = response.body();
mutableLiveData.setValue(myList);
}
#Override
public void onFailure(Call<ArrayList<MovieSearch>> call, Throwable t) {
}
});
}
}
In activity or fragment, you can access the liveData through getMutableLiveData() and set the List<MovieSearch> to the adapter.
One more thing, given your response JSON, your API should like below
public interface MyApi {
#GET("/movies")
Call<MovieSearch> getartistdata();
}
MovieSearch is not in a list
Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<Search> arrayList = new ArrayList<>;
private LayoutInflater layoutInflater;
public void submitList(ArrayList<Search> searchList){
this.arrayList = searchList;
notifyDataSetChanged();
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
if (layoutInflater==null){
layoutInflater=LayoutInflater.from(parent.getContext());
}
MyListBinding myListBinding= DataBindingUtil.inflate(layoutInflater, R.layout.mylist,parent,false);
return new ViewHolder(myListBinding);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
Search search =arrayList.get(position);
holder.bind(search);
}
#Override
public int getItemCount() {
return arrayList.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
private MyListBinding myListBinding;
public ViewHolder(#NonNull MyListBinding myListBinding) {
super(myListBinding.getRoot());
this.myListBinding=myListBinding;
}
public void bind(Search myli){
this.myListBinding.setMylistmodel(myli);
myListBinding.executePendingBindings();
}
public MyListBinding getMyListBinding(){
return myListBinding;
}
}
}
Activity
adapter = MyAdapter();
recyclerview=(RecyclerView)findViewById(R.id.recyclerView);
recyclerview.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
recyclerview.setAdapter(adapter);
myListViewModel = ViewModelProviders.of(this).get(MyListViewModel.class);
myListViewModel.getSearchResults().observe(this, new Observer<MovieSearch>() {
#Override
public void onChanged(#Nullable MovieSearch movieSearch) {
// handle changes here, to use the data write code below
adapter.submitList(movieSearch.Search);
}
});
And one small thing, please don't start variable names with capital letters. It is better to follow naming conventions from the start.
I had to filter PageList content by transfering filter parameters MinAge and MaxAge to ItemDataSource class:
public class ItemDataSource extends PageKeyedDataSource<Integer, Item> {
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull final LoadInitialCallback<Integer, Item> callback) {
loadData(Util.getMinAge, Util.getMaxAge);
}
// and so on in loadBefore and loadAfter methods
}
Here's refresh method:
public void refresh() {
RecyclerView recyclerView = findViewById(R.id.list);
ItemViewModel itemViewModel = ViewModelProviders.of(MainActivity.this)
.get(ItemViewModel.class);
final ItemAdapter adapter = new ItemAdapter(MainActivity.this);
itemViewModel.itemPagedList.observe(MainActivity.this,
new Observer<PagedList<Item>>() {
#Override
public void onChanged(#Nullable PagedList<Item> items) {
adapter.submitList(items);
}
});
recyclerView.setAdapter(adapter);
}
But this method don't re-create ItemDataSource that contains new filter parameters, so I get refreshed list but with old parameters.
How can I solve this issue?
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).
We decided to use Flowable of RxJava with Room persistence library. We have tables in which content is added by services on indefinite intervals(there are 2-3 update inserts every few seconds) and that table's rows are subscribed in BaseAdapter for making changes in view on live basis.
The problem is that when ever there is any update/insert operation, we get whole list again making the base adaptor regenerate the view. There can be delete operations as well making the length of rows no use to us.
I want to ask if there is any other operator which supports live data and call onNext on new data, and provide whole list on delete operations.
are subscribed in BaseAdapter
Use RecyclerView with fine-grained notify* calls like notifyItemInserted instead of a ListView
The problem is that when ever there is any update/insert operation, we get whole list again
That is completely expected behavior with both LiveData<List<T>> and Flowable<List<T>>.
making the base adaptor regenerate the view.
That's because you aren't using DiffUtil or you aren't using RecyclerView's new addition, ListAdapter (which handles the diffing internally and automatically)
provide whole list on delete operations.
It actually already provides the whole list (without the deleted items, of course).
The solution from AAC side is to use DataSource.Factory<Integer, T> instead of Flowable<List<T>>/LiveData<List<T>> so that you can create a LiveData<PagedList<T>> via a LivePagedListBuilder which you can set to your PagedListAdapter. That way, it only fetches a given page size instead of the whole list, and handles diffing.
EDIT:
#Entity(tableName = Task.TABLE_NAME)
public class Task {
public static DiffUtil.ItemCallback<Task> DIFF_CALLBACK = new DiffUtil.ItemCallback<Task>() {
#Override
public boolean areItemsTheSame(#NonNull Task oldItem, #NonNull Task newItem) {
return oldItem.id == newItem.id;
}
#Override
public boolean areContentsTheSame(#NonNull Task oldItem, #NonNull Task newItem) {
return oldItem.equals(newItem);
}
};
public static final String TABLE_NAME = "TASK";
public static final String COLUMN_ID = "task_id";
public static final String COLUMN_TEXT = "task_text";
public static final String COLUMN_DATE = "task_date";
and
#Dao
public interface TaskDao {
#Query("SELECT * FROM " + Task.TABLE_NAME + " ORDER BY " + Task.COLUMN_DATE + " ASC ")
DataSource.Factory<Integer, Task> tasksSortedByDate();
// ...
}
and
public class TaskViewModel
extends ViewModel {
private final TaskDao taskDao;
private LiveData<PagedList<Task>> liveResults;
public TaskViewModel(TaskDao taskDao) {
this.taskDao = taskDao;
liveResults = new LivePagedListBuilder<>(taskDao.tasksSortedByDate(),
new PagedList.Config.Builder() //
.setPageSize(20) //
.setPrefetchDistance(20) //
.setEnablePlaceholders(true) //
.build())
.setInitialLoadKey(0)
.build();
}
public LiveData<PagedList<Task>> getTasks() {
return liveResults;
}
}
and
public class TaskFragment
extends Fragment {
RecyclerView recyclerView;
// ...
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView = view.findViewById(R.id.recycler_view);
TaskViewModel viewModel = ViewModelProviders.of(this).get(TaskViewModel.class);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
final TaskAdapter taskAdapter = new TaskAdapter();
recyclerView.setAdapter(taskAdapter);
viewModel.getTasks().observe(this, pagedList -> {
//noinspection Convert2MethodRef
taskAdapter.submitList(pagedList);
});
}
#Override
protected void onDestroyView() {
super.onDestroyView();
viewModel.getTasks().removeObservers(this);
}
}
and
public class TaskAdapter
extends PagedListAdapter<Task, TaskAdapter.ViewHolder> {
public TaskAdapter() {
super(Task.DIFF_CALLBACK);
}
No, With room, there aren't. If you are using Rx with Room, you can use Diff util
or you can use List Adapter
There's also a variation called SortedListAdapter
Providing a sample implementation from developer android
#Dao
interface UserDao {
#Query("SELECT * FROM user ORDER BY lastName ASC")
public abstract LiveData<List<User>> usersByLastName();
}
class MyViewModel extends ViewModel {
public final LiveData<List<User>> usersList;
public MyViewModel(UserDao userDao) {
usersList = userDao.usersByLastName();
}
}
class MyActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
RecyclerView recyclerView = findViewById(R.id.user_list);
UserAdapter<User> adapter = new UserAdapter();
viewModel.usersList.observe(this, list -> adapter.submitList(list));
recyclerView.setAdapter(adapter);
}
}
class UserAdapter extends ListAdapter<User, UserViewHolder> {
public UserAdapter() {
super(User.DIFF_CALLBACK);
}
#Override
public void onBindViewHolder(UserViewHolder holder, int position) {
holder.bindTo(getItem(position));
}
public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK =
new DiffUtil.ItemCallback<User>() {
#Override
public boolean areItemsTheSame(
#NonNull User oldUser, #NonNull User newUser) {
// User properties may have changed if reloaded from the DB, but ID is fixed
return oldUser.getId() == newUser.getId();
}
#Override
public boolean areContentsTheSame(
#NonNull User oldUser, #NonNull User newUser) {
// NOTE: if you use equals, your object must properly override Object#equals()
// Incorrectly returning false here will result in too many animations.
return oldUser.equals(newUser);
}
}
}
Or, you can create a RXBus implementation, and when you are inserting data into database, publish an event with the added/deleted data. Subscribe to it, and you can get what you want.
Whenever i try to add data to recycler view, the recycler view doesn't show any data. I tried debugging the program and I am successfully getting JSON data using Retrofit into the application(Checked by printing it in Log). But RecyclerView shows no data.Here is my code:
CartActivity.java
public class CartActivity extends AppCompatActivity {
RecyclerView listshowrcy;
List<CartDisplay> cartlist = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cart);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
API api = retrofit.create(API.class);
String username = getIntent().getStringExtra("Username");
Call<List<CartDisplay>> call = api.getCartContent(username);
call.enqueue(new Callback<List<CartDisplay>>() {
#Override
public void onResponse(Call<List<CartDisplay>> call, Response<List<CartDisplay>> response) {
List<CartDisplay> cart = response.body();
for(CartDisplay cartContent : cart){
cartlist.add(cartContent);
}
}
#Override
public void onFailure(Call<List<CartDisplay>> call, Throwable t) {
}
});
listshowrcy = (RecyclerView)findViewById(R.id.cartList);
listshowrcy.setHasFixedSize(true);
CartAdapter cardadapter = new CartAdapter(cartlist,this,username);
listshowrcy.setAdapter(cardadapter);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
listshowrcy.setLayoutManager(linearLayoutManager);
}
}
CartAdapter.java
public class CartAdapter extends RecyclerView.Adapter<CartAdapter.Holderview> {
private List<CartDisplay> cartlist;
private Context context;
private String username;
public CartAdapter(List<CartDisplay> cartlist, Context context,String username) {
this.cartlist = cartlist;
this.context = context;
this.username = username;
}
#Override
public Holderview onCreateViewHolder(ViewGroup parent, int viewType) {
View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.cart_item,parent,false);
return new Holderview(layout);
}
#Override
public void onBindViewHolder(Holderview holder, int position) {
holder.pname.setText(cartlist.get(position).getP_name());
holder.pquant.setText(cartlist.get(position).getQuantity());
holder.price.setText(String.valueOf(cartlist.get(position).getPrice()));
}
#Override
public int getItemCount() {
return cartlist.size();
}
class Holderview extends RecyclerView.ViewHolder
{
TextView pname;
TextView pquant;
TextView price;
Holderview(View itemview){
super(itemview);
pname = (TextView)itemview.findViewById(R.id.product_name);
pquant = (TextView)itemview.findViewById(R.id.product_quant);
price = (TextView)itemview.findViewById(R.id.product_price);
}
}
}
After you get your response you must notify adapter, that data has changed:
#Override
public void onResponse(Call<List<CartDisplay>> call, Response<List<CartDisplay>> response) {
List<CartDisplay> cart = response.body();
cartList.clear(); // don't forget to clear list, to avoid duplicates
for(CartDisplay cartContent : cart){
cartlist.add(cartContent);
}
adapter.notifyDataSetChanged();
}
Another way: you can create method: adapter.setData(cardList)
And there refresh adapter data and call notifyDataSetChanged()
In addition to #kdblue's answer, there is another issue with your code. The adapter doesn't know that new data has been added to the underlying list.
You can either use notifyDataSetChanged:
List<CartDisplay> cart = response.body();
for(CartDisplay cartContent : cart){
cartlist.add(cartContent);
}
cardadapter.notifyDataSetChanged();
Or let the adapter handle new items directly, by adding a method to the adapter like:
public void add(CartDisplay cartDisplay) {
cartlist.add(user);
notifyItemInserted(cartlist.size());
}
And adding the items directly to the adapter:
List<CartDisplay> cart = response.body();
for(CartDisplay cartContent : cart){
cardadapter.add(cartContent);
}
Be aware that you will have to change you code structure to apply these strategies.