RecyclerView is not updated when notifyDataSetChanged is used with MVVM - android

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
}

Related

Fetching data with retrofit2 and saving in room

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")

how to update 1 recycler view adapter from 2 view models?

I have 2 view models observing 2 tables in room each emitting live data, they should update my recycler view when a value changes. My adapter is equipped to handle more than one model and view holder, but I'm not sure how to update the recycler views adapter with new data without overwriting the current data or duplicating any data any ideas?
So my adapter takes a list of Visitable (Visitable pattern)
I have 2 objects that implement this interface, the interface has a type so I can tell what view holder it wants and I update the recycler view using diff utils, it look like this
public class CardAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private final List<Visitable> elements;
private final TypeFactory typeFactory;
private final ItemTouchListener onItemTouchListener;
private final Context context;
private String cardType;
private final String layoutIdentifier;
private static final String TAG = "Adptr-Card";
private String CARD_CLICK_UPDATE = "card_click_update";
private final String[] imageFilePathNames;
private RequestManager glide;
public CardAdapter(List<Visitable> elements, TypeFactory typeFactory, ItemTouchListener onItemTouchListener,
Context context,
String cardType, String layoutIdentifier, RequestManager glide) {
this.glide = glide;
this.elements = elements;
this.typeFactory = typeFactory;
this.onItemTouchListener = onItemTouchListener;
this.context = context;
this.cardType = cardType;
this.layoutIdentifier = layoutIdentifier;
this.imageFilePathNames = context.getResources().getStringArray(R.array.image_set_names);
}
#NonNull
#Override
public BaseViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View contactView = LayoutInflater.from(context).inflate(viewType, parent, false);
return typeFactory.createViewHolder(contactView, viewType, onItemTouchListener, glide, cardType);
}
#Override
public void onBindViewHolder(#NonNull BaseViewHolder holder, int position) {
holder.bind(elements.get(position), position);
}
#Override
public int getItemViewType(int position) {
return elements.get(position).type(typeFactory);
}
public void setCardType(String cardType) {
this.cardType = cardType;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return elements.size();
}
public List<Visitable> getList() {
return elements;
}
public List<Sentence> getSentencesList() {
ArrayList<Sentence> sentences = new ArrayList<>();
for (Visitable visitable : elements) {
if (visitable.type(typeFactory) == CardViewHolder.LAYOUT) {
sentences.add((Sentence) visitable);
}
}
return sentences;
}
public Visitable getItem(int position) {
if (position > 0 && position < elements.size()) {
return elements.get(position);
}
return elements.get(0);
}
class CalculateDiffUtils extends AsyncTask<Void, Void, DiffResult> {
private final List<Visitable> oldCardList;
private final List<Visitable> newCardList;
CalculateDiffUtils(List<Visitable> oldCardList, List<Visitable> newCardList) {
this.oldCardList = oldCardList;
this.newCardList = newCardList;
}
#Override
protected DiffUtil.DiffResult doInBackground(Void... params) {
return DiffUtil.calculateDiff(new VisitableDiffUtils(oldCardList, newCardList, typeFactory));
}
#Override
protected void onPostExecute(DiffUtil.DiffResult diffResult) {
super.onPostExecute(diffResult);
dispatchUpdates(diffResult, newCardList);
}
}
private void dispatchUpdates(DiffUtil.DiffResult diffResult, List<Visitable> newCardList) {
this.elements.clear();
this.elements.addAll(newCardList);
diffResult.dispatchUpdatesTo(this);
}
public void refreshDiffUtilsList(List<Visitable> sentences) {
new CalculateDiffUtils(elements, sentences).execute();
}
public void removeItem(int position) {
elements.remove(position);
notifyItemRemoved(position);
}
public void addCard(Sentence sentence) {
elements.add(getItemCount(), sentence);
notifyItemInserted(getItemCount());
}
public void addGroup(GroupsWithSentences sentence) {
elements.add(getItemCount(), sentence);
notifyItemInserted(getItemCount());
}
public void updateCardClick(int position) {
notifyItemChanged(position, CARD_CLICK_UPDATE);
}
public void refreshList(List<Visitable> newElements) {
ArrayList<Visitable> elementArrayList = new ArrayList<>(newElements);
elements.clear();
elements.addAll(elementArrayList);
notifyDataSetChanged();
}
}
My 2 view models sit in a fragment, they observe some data from my Room database and are updated when changes happen, but this means I will only ever have the data from one of the view models, I guess I want a way to combine these view models maybe using some kind of mediator live data, here are my 2 view models (I've removed stuff for brevity, they are both initiated using factories)
GROUP VIEW MODEL
public class GroupViewModel extends ViewModel {
private final GroupRepository groupRepository;
private final LiveData<List<GroupsWithSentences>> groups;
public GroupViewModel(#NonNull Application application, String[] cardArgs) {
groupRepository = new GroupRepository(application);
groups = groupRepository.getGroupsByWordDescriptionAndWordType(cardArgs[0],cardArgs[1]);
}
public LiveData<List<GroupsWithSentences>> getGroups() {
return groups;
}
}
SENTENCE VIEW MODEL
public class CardViewModel extends ViewModel {
private final SentenceRepository sentenceRepository;
private final LiveData<List<Sentence>> cards;
private static final String TAG = "view_model";
public CardViewModel(#NonNull Application application , int clicks){
sentenceRepository = new SentenceRepository(application);
search = new MutableLiveData<>();
cardArgs = new MutableLiveData<>();
cards = Transformations.switchMap(search, mySearch -> sentenceRepository.searchLiveCardListByWordTypeAndWordDescriptionAndSearchWord(getCardArgs()[0],getCardArgs()[1],mySearch));
}
public LiveData<List<Sentence>> getLiveCardList(){
return cards;
}
}
CALLING ADAPTER IN MY FRAGMENT
private void setUpCardViewModelObserver(String[] args) {
cardViewModel.getLiveCardList().observe(getViewLifecycleOwner(), sentenceList -> {
if (sentenceList != null) {
ArrayList<Visitable> list = new ArrayList<>(sentenceList);
cardAdapter.refreshDiffUtilsList(list);
checkResults(list.size());
}
});
}
private void setUpGroupViewModelObserver() {
groupViewModel.getGroups().observe(getViewLifecycleOwner(), groupsWithSentencesList -> {
if (groupsWithSentencesList != null) {
ArrayList<Visitable> list = new ArrayList<>(groupsWithSentencesList);
cardAdapter.refreshDiffUtilsList(list);
checkResults(groupsWithSentencesList.size());
}
});
}
Any help is welcome, many thanks.
So the answer was to use Mediator Live data, i set the new mediator live data to respond to changes to my existing live data objects and then mediate those changes so i now only have one stream of data so my card view model now looks like this
public CardViewModel(#NonNull Application application , int clicks, String[] cardArgs){
sentenceRepository = new SentenceRepository(application);
search = new MutableLiveData<>();
cards = Transformations.switchMap(search, mySearch -> sentenceRepository.searchLiveCardListByWordTypeAndWordDescriptionAndSearchWord(cardArgs[0],cardArgs[1],mySearch));
groupRepository = new GroupRepository(application);
groups = groupRepository.getGroupsByWordDescriptionAndWordType(cardArgs[0],cardArgs[1]);
sentencesAndGroups = new MediatorLiveData<>();
sentencesAndGroups.addSource(cards, sentences -> {
sentencesAndGroups.setValue(combineLatest(sentences, groups.getValue()));
});
sentencesAndGroups.addSource(groups, groupsWithSentences -> {
sentencesAndGroups.setValue(combineLatest(cards.getValue(), groupsWithSentences));
});
}
and my new combine latest method looks like this
private List<Visitable> combineLatest(List<Sentence> sentenceList, List<GroupsWithSentences> groupsWithSentences) {
List<Visitable> visitableList = new ArrayList<>();
if (sentenceList != null){
visitableList.addAll(sentenceList);
}
if (groupsWithSentences != null){
visitableList.addAll(groupsWithSentences);
}
return visitableList;
}

iam using Retrofit library to fetch data from Database to recyclerview

am trying to Fetch the movies data from Mysql DB and show it to Recycler view
but when i run the app nothing shows
here is code i am using Retrofite Library
but i can't parse the Data to the Recycler view
i've made Adapter and Model Class normally like the Json
MainActivity.class
public class MainActivity extends AppCompatActivity {
private static final String url="http://192.168.1.109/stu/";
RecyclerView recyclerViewMovies;
List<MovieListsModels> movies;
MoviesAdapter adapter;
TextView Errortxt;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Errortxt = (TextView)findViewById(R.id.txterror);
recyclerViewMovies = (RecyclerView)findViewById(R.id.recyclerview);
recyclerViewMovies.setHasFixedSize(true);
recyclerViewMovies.setLayoutManager(new LinearLayoutManager(this));
movies = new ArrayList<>();
loadDatafromServer();
}
private void loadDatafromServer() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
Api api = retrofit.create(Api.class);
Call<MovieListsModels> call = api.ShowMoviesData();
call.enqueue(new Callback<MovieListsModels>() {
#Override
public void onResponse(Call<MovieListsModels> call, Response<MovieListsModels> response) {
try {
MovieListsModels movie = response.body();
adapter = new MoviesAdapter(MainActivity.this, (List<MovieListsModels>) movie);
recyclerViewMovies.setAdapter(adapter);
}
catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<MovieListsModels> call, Throwable t) {
Errortxt.setText(t.getMessage().toString());
}
});
}
this is the interface of the methods
Api.class Interface
public interface Api {
#GET("config.php")
Call<MovieListsModels> ShowMoviesData();
}
MovieLists.class
public class MovieListsModels {
public MovieListsModels() {
}
int id;
String movie_name;
String movie_image;
String movie_genre;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMovie_name() {
return movie_name;
}
public void setMovie_name(String movie_name) {
this.movie_name = movie_name;
}
public String getMovie_image() {
return movie_image;
}
public void setMovie_image(String movie_image) {
this.movie_image = movie_image;
}
public String getMovie_genre() {
return movie_genre;
}
public void setMovie_genre(String movie_genre) {
this.movie_genre = movie_genre;
}
public MovieListsModels(int id, String movie_name, String movie_image, String movie_genre) {
this.id = id;
this.movie_name = movie_name;
this.movie_image = movie_image;
this.movie_genre = movie_genre;
}
}
MovieAdapter.class
public class MoviesAdapter extends RecyclerView.Adapter<MoviesAdapter.MovieHolderView> {
private Context mContext;
private List<MovieListsModels> MovieList = new ArrayList<>();
public MoviesAdapter(Context mContext, List<MovieListsModels> movieList) {
this.mContext = mContext;
MovieList = movieList;
}
#NonNull
#Override
public MovieHolderView onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item,parent,false);
MovieHolderView holder = new MovieHolderView(view);
return holder;
}
#Override
public void onBindViewHolder(#NonNull MovieHolderView holder, int position) {
MovieListsModels list = MovieList.get(position);
holder.txtName.setText(list.getMovie_name());
holder.txtGenre.setText(list.getMovie_genre());
Picasso.get()
.load(list.getMovie_image())
.into(holder.imgMovie);
}
#Override
public int getItemCount() {
return MovieList.size();
}
public class MovieHolderView extends RecyclerView.ViewHolder {
TextView txtName,txtGenre;
ImageView imgMovie;
public MovieHolderView(View itemView) {
super(itemView);
txtName =(TextView)itemView.findViewById(R.id.movieName);
txtGenre =(TextView)itemView.findViewById(R.id.movieGenre);
imgMovie =(ImageView)itemView.findViewById(R.id.movieImg);
}
}
}
If you receive a list of movies is better because you expect a list, I suppose
public void onResponse(Call<MovieListsModels> call, Response<MovieListsModels> response) {
try {
List<MovieListsModels> movie = response.body();
adapter = new MoviesAdapter(MainActivity.this, movies);
And I believe that not executing the notifyDataSetChanged, you can added like that:
private Context mContext;
private List<MovieListsModels> MovieList = new ArrayList<>();
public MoviesAdapter(Context mContext, List<MovieListsModels> movieList) {
this.mContext = mContext;
MovieList = movieList;
notifiyDataSetChanged();
If you are having json response of the form {..}, you are having an object response and you should expect an object as you have done i.e, Call<YourObject>
If you are having json response of the form [..], you are having an array response and you should expect an array i.e, Call<List<YourObject>>
In your case, i hope its an array(second case), So make changes as per the above answer done by #Guillodacosta
First don't forget to add the internet permission in your manifest file
<uses-permission android:name="android.permission.INTERNET" />
Second try this
Picasso.with(mContext).load(list.getMovie_image()).into(holder.imgMovie);

How to update an item on a recyclerview after excuting AsyncTask doInBackground?

I'm creating a chat feature for an application and it works super fine. But I would like to show the user that message has been sent or it still wating for the server's response.
Fields:
List<ChatMessage> chatMessages;
ChatAdapter chatAdapter;
RecyclerView chatRecyclerView;
ImageButton submitMessageBtn;
this how I send a message on my ChatActivity class:
public void submitMessage(final String messageType, final byte[] message){
final ChatMessageResponse messageObject = new ChatMessageResponse();
new AsyncTask<Void, Void, Void>() {
#Override
protected void onPreExecute() {
super.onPreExecute();
messageObject.setMessage( message);
messageObject.setYours(true);
messageObject.setUserNickname(getNickname());
messageObject.setCreationDate(DateTime.now().withZone(DateTimeZone.UTC));
messageObject.setType(messageType);
AddMessage(messageObject);
}
#Override
protected Void doInBackground(Void... voids) {
try {
chatClient.chat().sendMessage(eventId, messageType, message);
runOnUiThread(new Runnable() {
#Override
public void run() {
// Update message on the list after has been sent to server
}
});
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}.execute();
}
public void AddMessage(ChatMessage message)
{
chatMessages.add(message);
chatAdapter.notifyDataSetChanged();
chatRecyclerView.scrollToPosition(chatMessages.size() -1);
}
When message is immediatly added to the adapter it should look like this:
my ChatAdapter class is setup like this:
public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder> {
private static final int VIEW_TYPE_MESSAGE_THIS_USER = 0;
private static final int VIEW_TYPE_MESSAGE_OTHER_USER = 1;
private final Activity activity;
public List<ChatMessage> chats=new ArrayList<>();
ArrayList<String> usercolor=new ArrayList<>();
Context mContext;
View view;
public ChatAdapter(List<ChatMessage> chats, Context mContext, Activity activity) {
this.chats = chats;
this.mContext = mContext;
this.activity = activity;
}
#Override
public ChatViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
mContext = parent.getContext();
if (viewType == VIEW_TYPE_MESSAGE_OTHER_USER) {
view = View.inflate(mContext, R.layout.message_item_left, null);
} else if (viewType == VIEW_TYPE_MESSAGE_THIS_USER){
view = View.inflate(mContext, R.layout.message_item, null);
}
return new ChatViewHolder(view,(View.OnLongClickListener)activity);
}
#Override
public void onBindViewHolder(final ChatViewHolder holder, int position){
final ChatMessageResponse m = (ChatMessageResponse) chats.get(position);
if (getItemViewType(position) == VIEW_TYPE_MESSAGE_OTHER_USER){
holder.bindToView1(m);
} else if (getItemViewType(position) == VIEW_TYPE_MESSAGE_THIS_USER)
{
holder.bindToView(m);
}
}
#Override
public int getItemCount() {
return chats.size();
}
#Override
public int getItemViewType(int position) {
return chats.get(position).isYours() ? VIEW_TYPE_MESSAGE_THIS_USER : VIEW_TYPE_MESSAGE_OTHER_USER;
}
}
When the server's response is positive the views in the ChatViewHolder (that I don't show the code because is too long) should change visibility state
Someone told me to get a referece for the view and change it on the activity's asynctask or create a Callback listener for my adapter.
But I have no Idea how to do either one of then any help is appreciated.
Are you familiar with the use of "Callbacks" or "Interfaces"? You can create an interface and implement it in your activity. Pass the callback by parameters in the "AsyncTask" and use it there.
//Interface class
/**
* Created by gmora
*/
public interface IProcess {
void updateAdapter(String result);
}
On Activity:
public class YourActivity extends AppCompatActivity {
private IProcess mProcess;
private Adapter mRecyclerAdapter;
private RecyclerView mRecyclerView;
private List<ChatMessage> chats; //update chats on activity and refresh your adapter
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mProcess = new IProceso() {
#Override
public void updateAdapter(String pException) {
//update chats ... and update mAdater.notifyDataChange()...
// or mRecyclerView.setAdapter(new Adpater.... with new list chats)..
}
};
mRecyclerView = find....
// etc....
mRecyclerAdapter = new RecyclerAdapter( chats, ...);
mRecyclerView.setAdapter(mRecyclerAdapter);
}
}
Finally on AsyncTask... create a external class from AsyncTask please!
/**
* Created by gmora.
*/
public class YourAsyncTaskClass extends AsyncTask<String, Void, String > {
private IProcess iProcess;
public StarSearchPrinterTask(IProcess pIProcess) {
this.iProcess= pIProcess;
}
#Override
protected void onPreExecute() {
//loading... its optional
}
#Override
protected String doInBackground(String... interfaceType) {
// execute webservice or api and get results..
return results;
}
#Override
protected void onPostExecute(String results) {
mIProceso.updateAdapter(results);
}
}

Can't update RecyclerView from my model Class?

I am working on a RecyclerView which must be Draggable & swipeable. Everything works perfect.
The Data is getting Fetched in one class called ExerciseDataProvider & the RV code is another Fragment RecyclerListViewFragment.
The problem is that i can't notify Data changed from the FetchExercise on postExecute method. So the Data's are not getting populated in the RV.
Please Guide me in a Right Direction.
ACTIVITY
public class DraggableSwipeableExampleActivity extends AppCompatActivity {
private static final String FRAGMENT_TAG_DATA_PROVIDER = "data provider";
private static final String FRAGMENT_LIST_VIEW = "list view";
private static final String FRAGMENT_TAG_ITEM_PINNED_DIALOG = "item pinned dialog";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(new ExampleDataProviderFragment(), FRAGMENT_TAG_DATA_PROVIDER)
.commit();
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new RecyclerListViewFragment(), FRAGMENT_LIST_VIEW)
.commit();
}
}
public AbstractDataProvider getDataProvider() {
final Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DATA_PROVIDER);
return ((ExampleDataProviderFragment) fragment).getDataProvider();
}
DATA PROVIDER
public class ExerciseDataProvider extends AbstractDataProvider {
private List<ConcreteData> mData;
private ConcreteData mLastRemovedData;
private int mLastRemovedPosition = -1;
public ExerciseDataProvider() {
new FetchExercise().execute();
mData = new LinkedList<>();
}
class FetchExercise extends AsyncTask<Void,Void,Void> {
#Override
protected Void doInBackground(Void... params) {
final int viewType = 0;
final int swipeReaction = RecyclerViewSwipeManager.REACTION_CAN_SWIPE_UP | RecyclerViewSwipeManager.REACTION_CAN_SWIPE_DOWN;
String url = "https://gist.githubusercontent.com/fake/cb9aa5494e7ee36ac3ca/raw/a4abfd19368063/exercise.JSON";
Log.d("Path", url);
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
String jsonData = response.body().string();
try {
JSONArray jsonArray = new JSONArray(jsonData);
for (int i = 0; i < jsonArray.length(); i++) {
final long id = i;
JSONObject jsonObject = jsonArray.getJSONObject(i);
String exercise_name = jsonObject.getString("name");
int exercise_duration = jsonObject.getInt("duration");
mData.add(new ConcreteData(id, viewType, exercise_name, exercise_duration, swipeReaction));
Log.d("exercise_name", exercise_name);
}
} catch (JSONException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
}
}
#Override
public int getCount() {
return mData.size();
}
#Override
public Data getItem(int index) {
if (index < 0 || index >= getCount()) {
throw new IndexOutOfBoundsException("index = " + index);
}
return mData.get(index);
}
#Override
public int undoLastRemoval() {
if (mLastRemovedData != null) {
int insertedPosition;
if (mLastRemovedPosition >= 0 && mLastRemovedPosition < mData.size()) {
insertedPosition = mLastRemovedPosition;
} else {
insertedPosition = mData.size();
}
mData.add(insertedPosition, mLastRemovedData);
mLastRemovedData = null;
mLastRemovedPosition = -1;
return insertedPosition;
} else {
return -1;
}
}
#Override
public void moveItem(int fromPosition, int toPosition) {
if (fromPosition == toPosition) {
return;
}
final ConcreteData item = mData.remove(fromPosition);
mData.add(toPosition, item);
mLastRemovedPosition = -1;
}
#Override
public void removeItem(int position) {
//noinspection UnnecessaryLocalVariable
final ConcreteData removedItem = mData.remove(position);
mLastRemovedData = removedItem;
mLastRemovedPosition = position;
}
public static final class ConcreteData extends Data {
private final long mId;
private final String mText;
private final int mViewType;
private final int mDuration;
private boolean mPinned;
ConcreteData(long id, int viewType, String text, int duration, int swipeReaction) {
mId = id;
mViewType = viewType;
mText = text;
mDuration = duration;
}
#Override
public int getViewType() {
return mViewType;
}
#Override
public int getDuration() {
return mDuration;
}
#Override
public long getId() {
return mId;
}
#Override
public String toString() {
return mText;
}
#Override
public String getText() {
return mText;
}
#Override
public boolean isPinned() {
return mPinned;
}
#Override
public void setPinned(boolean pinned) {
mPinned = pinned;
}
}
}
RecyclerListViewFragment
public class RecyclerListViewFragment extends Fragment {
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;
private RecyclerView.Adapter mAdapter;
private RecyclerView.Adapter mWrappedAdapter;
private RecyclerViewDragDropManager mRecyclerViewDragDropManager;
private RecyclerViewSwipeManager mRecyclerViewSwipeManager;
private RecyclerViewTouchActionGuardManager mRecyclerViewTouchActionGuardManager;
public RecyclerListViewFragment() {
super();
}
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_recycler_list_view, container, false);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//noinspection ConstantConditions
mRecyclerView = (RecyclerView) getView().findViewById(R.id.recycler_view);
mLayoutManager = new LinearLayoutManager(getContext());
// touch guard manager (this class is required to suppress scrolling while swipe-dismiss animation is running)
mRecyclerViewTouchActionGuardManager = new RecyclerViewTouchActionGuardManager();
mRecyclerViewTouchActionGuardManager.setInterceptVerticalScrollingWhileAnimationRunning(true);
mRecyclerViewTouchActionGuardManager.setEnabled(true);
// drag & drop manager
mRecyclerViewDragDropManager = new RecyclerViewDragDropManager();
mRecyclerViewDragDropManager.setDraggingItemShadowDrawable(
(NinePatchDrawable) ContextCompat.getDrawable(getContext(), R.drawable.material_shadow_z3));
// swipe manager
mRecyclerViewSwipeManager = new RecyclerViewSwipeManager();
//adapter
final MyDraggableSwipeableItemAdapter myItemAdapter = new MyDraggableSwipeableItemAdapter(getDataProvider());
myItemAdapter.setEventListener(new MyDraggableSwipeableItemAdapter.EventListener() {
#Override
public void onItemRemoved(int position) {
((DraggableSwipeableExampleActivity) getActivity()).onItemRemoved(position);
}
#Override
public void onItemViewClicked(View v, boolean pinned) {
onItemViewClick(v, pinned);
}
});
mAdapter = myItemAdapter;
mWrappedAdapter = mRecyclerViewDragDropManager.createWrappedAdapter(myItemAdapter); // wrap for dragging
mWrappedAdapter = mRecyclerViewSwipeManager.createWrappedAdapter(mWrappedAdapter); // wrap for swiping
final GeneralItemAnimator animator = new SwipeDismissItemAnimator();
animator.setSupportsChangeAnimations(false);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mWrappedAdapter); // requires *wrapped* adapter
mRecyclerView.setItemAnimator(animator);
// additional decorations
//noinspection StatementWithEmptyBody
if (supportsViewElevation()) {
// Lollipop or later has native drop shadow feature. ItemShadowDecorator is not required.
} else {
mRecyclerView.addItemDecoration(new ItemShadowDecorator((NinePatchDrawable) ContextCompat.getDrawable(getContext(), R.drawable.material_shadow_z1)));
}
mRecyclerView.addItemDecoration(new SimpleListDividerDecorator(ContextCompat.getDrawable(getContext(), R.drawable.list_divider_h), true));
mRecyclerViewTouchActionGuardManager.attachRecyclerView(mRecyclerView);
mRecyclerViewSwipeManager.attachRecyclerView(mRecyclerView);
mRecyclerViewDragDropManager.attachRecyclerView(mRecyclerView);
}
#Override
public void onPause() {
mRecyclerViewDragDropManager.cancelDrag();
super.onPause();
}
#Override
public void onDestroyView() {
if (mRecyclerViewDragDropManager != null) {
mRecyclerViewDragDropManager.release();
mRecyclerViewDragDropManager = null;
}
if (mRecyclerViewSwipeManager != null) {
mRecyclerViewSwipeManager.release();
mRecyclerViewSwipeManager = null;
}
if (mRecyclerViewTouchActionGuardManager != null) {
mRecyclerViewTouchActionGuardManager.release();
mRecyclerViewTouchActionGuardManager = null;
}
if (mRecyclerView != null) {
mRecyclerView.setItemAnimator(null);
mRecyclerView.setAdapter(null);
mRecyclerView = null;
}
if (mWrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(mWrappedAdapter);
mWrappedAdapter = null;
}
mAdapter = null;
mLayoutManager = null;
super.onDestroyView();
}
private void onItemViewClick(View v, boolean pinned) {
int position = mRecyclerView.getChildAdapterPosition(v);
if (position != RecyclerView.NO_POSITION) {
((DraggableSwipeableExampleActivity) getActivity()).onItemClicked(position);
}
}
public AbstractDataProvider getDataProvider() {
return ((DraggableSwipeableExampleActivity) getActivity()).getDataProvider();
}
public void notifyItemChanged(int position) {
mAdapter.notifyItemChanged(position);
}
public void notifyItemInserted(int position) {
mAdapter.notifyItemInserted(position);
mRecyclerView.scrollToPosition(position);
}
}
To update recyclerView from onPostExecute in a data provider class, your onPostExecute should have access to context where your recyclerView is defined.
Since your FetchExercise async task is defined inside ExerciseDataProvider class, try passing activity context to ExerciseDataProvider's constructor and then pass it on to FetchExercise async task as described here: getting context in AsyncTask
public class MyCustomTask extends AsyncTask<Void, Void, Long> {
private Context mContext;
public MyCustomTask (Context context){
mContext = context;
}
protected void onPostExecute(Long result) {
//use mContext to update recycler view
}
}
}
Use the context to update the recyclerView.
UPDATE
Step 1
Define an interface that will notify your activity of data set change inside a class that initialises your data provider class and pass activity context to constructor of data provider class.
public class ExampleDataProviderFragment extends Fragment {
private AbstractDataProvider mDataProvider;
//Define an interface that will notify your activity of data set change
public interface EventListener {
void onNotifyDataSetChanged();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
//Pass activity context to ExerciseDataProvider
mDataProvider = new ExerciseDataProvider(getActivity());
}
public AbstractDataProvider getDataProvider() {
return mDataProvider;
}
}
Step 2
Add context parameter to ExerciseDataProvider's constructor and use it to notify activity that implements your interface to notify dataset change.
public class ExerciseDataProvider extends AbstractDataProvider {
private List<ConcreteData> mData;
private ConcreteData mLastRemovedData;
private int mLastRemovedPosition = -1;
//Add context parameter to constructor
public ExerciseDataProvider(Context context) {
//Pass context to async task
new FetchExercise(context).execute();
mData = new LinkedList<>();
}
class FetchExercise extends AsyncTask<Void,Void,Integer> {
Context mContext;
public FetchExercise(Context context) {
mContext = context;
}
#Override
protected Integer doInBackground(Void... params) {
...
return 1;
}
#Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
//Typecast context to interface defined above
//and notify dataset changes by calling its method
ExampleDataProviderFragment.EventListener eventListener = (ExampleDataProviderFragment.EventListener)mContext;
eventListener.onNotifyDataSetChanged();
}
}
}
Step 3
Implement above defined interface in your activity class and notify recyclerview adapter inside it
public class DraggableSwipeableExampleActivity extends AppCompatActivity
implements ExampleDataProviderFragment.EventListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
...
}
//implement interface method and notify recyclerview of changes
#Override
public void onNotifyDataSetChanged() {
Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_LIST_VIEW);
// you might need to change visibility of `mWrappedAdapter` in the fragment that defines it or create a getter for it so that you can access it here
((RecyclerListViewFragment) fragment).mWrappedAdapter.notifyDataSetChanged();
}
...
}
I think #random is correct you should be notifying your Recycle view on post execute.
#Override
protected void onPostExecute(Void aVoid) {
mRecyclerViewAdapter.notifyDataSetChanged();
super.onPostExecute(aVoid);
}
or if you have done something in your async task to add/delete something in the data set you would do:
#Override
protected void onPostExecute(Void aVoid) {
mRecyclerViewAdapter.notifyItemRemoved(itemposition); // or item added
mRecyclerViewAdapter.notifyDataSetChanged();
super.onPostExecute(aVoid);
}
Hope it helps !

Categories

Resources