I am starting to use android jetpack arch components and have run into some confusion. Also please note data binding is not a option.
I have an activity that has a RecylcerView. I have a ViewModel that looks like the following
public class Movie extends ViewModel {
public Movie movie;
public URL logoURL;
private MutableLiveData<Drawable> logo;
public MutableLiveData<Drawable> getLogo() {
if (logo == null) {
logo = new MutableLiveData<>();
}
return logo;
}
public PikTvChannelItemVM(Movie movie, URL logo) {
this.movie = movie;
this.logoURL = logoURL;
}
public Bitmap getChannelLogo() {
//Do some network call to get the bitmap logo from the url
}
}
The above is all fine although in my I recyclerview have the following code below. Although in onbindviewholder when I try to observe for the returned image from the viewmodels live data it needs a life cycle owner reference which I don't have in my recycler view. Please help thanks
public class MovieRecyclerViewAdapter extends RecyclerView
.Adapter<MovieRecyclerViewAdapter.MovieItemViewHolder> {
public List<MovieViewModel> vmList;
public MovieRecyclerViewAdapter(List<MovieViewModel> vmList) {
this.vmList = vmList;
setHasStableIds(true);
}
#Override
public MovieItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MovieView itemView = new MovieView(parent.getContext(), null);
itemView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
return new MovieItemViewHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull MovieItemViewHolder holder, int position) {
vmList.get(position).observe(?????, users -> {
// As you can see I have no reference to the life cycle owner
holder.imageView.setimage(some drawable returned by the View Model)
});
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public int getItemCount() {
return vmList.size();
}
class MovieItemViewHolder extends RecyclerView.ViewHolder {
private MovieView channelView;
public MovieItemViewHolder(View v) {
super(v);
channelView = (MovieView) v;
}
public MovieView getChannelView() {
return channelView;
}
}
}
I would try to send a list to the adapter to display it. I would observe the data outside of the adapter, as it is not the adapter's responsibility to observe the data but rather to display it.
Your adapter list type should not be viewmodel. If you want to use live data without binding, you want auto ui change when your list update.
You can do it like this:
First of all, your movie class must be a model class, not be a viewmodel.
Do it adapter like normal. Just add a setList(Movie) method. This method should update the adapter list. Do not forget notify adapter after update list.
Then create livedata list like
MutableLiveData<List<Movie>> movieLiveData =
new MutableLiveData<>();
where you want.
Observe this list in the activity and call adapters setList(Movie)method inside observe{}.
After all this if your list updated, setList(Movie) medhod will be triggered then your ui will be update.
You can do something like this:
ViewModel:
class MyViewModel : ViewModel() {
private val items = MutableLiveData<List<String>>()
init {
obtainList()
}
fun obtainList() { // obtain list from repository
items.value = listOf("item1", "item2", "item3", "item4")
}
fun getItems(): LiveData<List<String>> {
return items
}
}
Your Fragment (or Activity):
public class ContentFragment extends Fragment {
private MyViewModel viewModel;
private RecyclerView recyclerView;
private MyAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_main, container, false);
recyclerView = root.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 4));
// add observer
viewModel.getItems().observe(this, items -> {
adapter = new MyAdapter(items); // add items to adapter
recyclerView.setAdapter(adapter);
});
return root;
}
Adapter:
class MyAdapter(val list: List<String>) : RecyclerView.Adapter<MyAdapter.TextViewHolder {
...
override fun onBindViewHolder(holder: TextViewHolder, position: Int) {
holder.textView.text = list[position] // assign titles from the list.
...
}
}
Any custom objects can be used instead of String objects.
Related
I am developing an app with Android Studio, and I wish to use MutableLiveData alongside with RecyclerView. Problem is, when I add a new item to the MutableLiveData, it gets updated, then going back to the previous fragment, the item gets erased for some unknown reason.
Code in my FirstFragment.java for the method onCreateView:
#Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState
) {
binding = FragmentFirstBinding.inflate(inflater, container, false);
notificationsViewModel = new ViewModelProvider(requireActivity()).get(NotificationsViewModel.class);
// Binding recycler views
// Notifications
RecyclerView rvNotification = binding.registeredNotification;
Log.d("NA", "New Adapter");
NotificationAdapter adapter = new NotificationAdapter();
Log.d("NA", "Setting adapter");
rvNotification.setAdapter(adapter);
Log.d("NA", "Setting layout");
rvNotification.setLayoutManager(new LinearLayoutManager(getActivity()));
Log.d("NA", "Getting the MLD");
MutableLiveData<List<NotificationContent>> notifs = notificationsViewModel.getNotifications();
Log.d("NA", "Adding observer");
notifs.observe(requireActivity(), new Observer<List<NotificationContent>>() {
#Override
public void onChanged(List<NotificationContent> notificationContents) {
Log.d("ANDROIDAPP", String.format("onChanged: detected and done, item size %d", notificationContents.size()));
adapter.updateNotifications(notificationContents);
}
});
return binding.getRoot();
}
Code of the ViewModel implementation :
public class NotificationsViewModel extends ViewModel {
private final MutableLiveData<List<NotificationContent>> notifications = new MutableLiveData<List<NotificationContent>>();
public MutableLiveData<List<NotificationContent>> getNotifications() {
if (notifications.getValue() == null) {
notifications.setValue(new ArrayList<NotificationContent>());
}
return notifications;
}
public void removeNotification(NotificationContent notificationContent) {
List<NotificationContent> n = getNotifications().getValue();
n.remove(notificationContent);
notifications.setValue(n);
}
public void addNotification(NotificationContent notificationContent) {
List<NotificationContent> n = getNotifications().getValue();
n.add(notificationContent);
notifications.setValue(n);
}
public void addNotification(int position, NotificationContent notificationContent) {
List<NotificationContent> n = getNotifications().getValue();
n.add(position, notificationContent);
notifications.setValue(n);
}
}
And finally code of the RecyclerView.Adapter :
public class NotificationAdapter extends RecyclerView.Adapter<NotificationAdapter.ViewHolder> {
public class ViewHolder extends RecyclerView.ViewHolder {
// View Items declaration...
public ViewHolder(View itemView) {
super(itemView);
// View items retrieval ...
}
}
private List<NotificationContent> mNotifications = new ArrayList<>();
public void updateNotifications(List<NotificationContent> newNotifications) {
Log.d("NA", "Clearing");
this.mNotifications.clear();
Log.d("NA", "Setting");
this.mNotifications = newNotifications;
Log.d("NA", "NotifyChangedDataset");
this.notifyDataSetChanged();
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
// Some code ...
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
// Some code ...
}
#Override
public int getItemCount() {
return mNotifications.size();
}
}
To get an idea of the interplay between my fragments here is my nav_graph.xml graphical representation :
But the problem is that after adding an item through the addNotificationFragment it ends up emptying, here is the log from the log given in the code :
D/ANDROIDAPP: onViewCreated: addNotification 1
D/ANDROIDAPP: onChanged: detected and done, item size 1
D/NA: Clearing
D/NA: Setting
D/NA: NotifyChangedDataset
D/ANDROIDAPP: onViewCreated: addNotification 2
D/ForceDarkHelper: updateByCheckExcludeList: pkg: com.etilawin.tgvmaxplanner activity: com.etilawin.tgvmaxplanner.MainActivity#df816d4
D/ForceDarkHelper: updateByCheckExcludeList: pkg: com.etilawin.tgvmaxplanner activity: com.etilawin.tgvmaxplanner.MainActivity#df816d4
D/NA: New Adapter
D/NA: Setting adapter
D/NA: Setting layout
D/NA: Item Touche helper
D/NA: Button listener
D/NA: Getting the MLD
D/NA: Adding observer
D/ANDROIDAPP: onChanged: detected and done, item size 0
D/NA: Clearing
D/NA: Setting
D/NA: NotifyChangedDataset
once you go back your viewmodel is destroyed, so you have to create it again,
so you have to initialize MutableLiveData object and its value with a constructor, so in that way when model is clear and re assign your values again with this model.
so try this way.
public class NotificationsViewModel extends ViewModel {
private final MutableLiveData<List<NotificationContent>> notifications;
public NotificationsViewModel() {
notifications = new MutableLiveData<>(new ArrayList<>());
}
public MutableLiveData<List<NotificationContent>> getNotifications() {
return notifications;
}
public void removeNotification(NotificationContent notificationContent) {
List<NotificationContent> n = notifications.getValue();
n.remove(notificationContent);
notifications.setValue(n);
}
public void addNotification(NotificationContent notificationContent) {
List<NotificationContent> n = notifications.getValue();
n.add(notificationContent);
notifications.setValue(n);
}
public void addNotification(int position, NotificationContent notificationContent) {
List<NotificationContent> n = notifications.getValue();
n.add(position, notificationContent);
notifications.setValue(n);
}
}
Due to the fact that the ListView is not optimized enough, I decided that I would switch to the Recycler View. The first problem that hit me was this one.
My RecyclerView adapter:
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
private List<String> mData;
private LayoutInflater mInflater;
private ItemClickListener mClickListener;
// data is passed into the constructor
MyRecyclerViewAdapter(Context context, List<String> data) {
this.mInflater = LayoutInflater.from(context);
this.mData = data;
}
// inflates the row layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.adapter_box, parent, false);
return new ViewHolder(view);
}
// binds the data to the TextView in each row
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
String animal = mData.get(position);
holder.myTextView.setText(animal);
}
// total number of rows
#Override
public int getItemCount() {
return mData.size();
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView myTextView;
ViewHolder(View itemView) {
super(itemView);
myTextView = itemView.findViewById(R.id.text_adapter);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition());
}
}
// convenience method for getting data at click position
String getItem(int id) {
return mData.get(id);
}
// allows clicks events to be caught
void setClickListener(ItemClickListener itemClickListener) {
this.mClickListener = itemClickListener;
}
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}
Using ListView I could do like this:
ListView listView = findViewById(R.id.testL);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (parent.getItemAtPosition(position).equals("hello")) {
TextView details = word_dialog.findViewById(R.id.word_edit_desc);
details.setText("hello");
}
}
});
How can I achieve the same result, but only with the Recycle view?:
#Override
public void onItemClick(View view, int position) {
}
I will be very grateful if you can help me!
I want to be able to click on the recycler view items in MainActivity.java, I already did it, now I need to be able to do my own actions on each line sorted using equals
ArrayList<String> animalNames = new ArrayList<>();
animalNames.add("Dog");
animalNames.add("Cow");
animalNames.add("Camel");
animalNames.add("Sheep");
animalNames.add("Goat");
// set up the RecyclerView
recyclerView = findViewById(R.id.myList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new MyRecyclerViewAdapter(this, animalNames);
adapter.setClickListener(this);
recyclerView.setAdapter(adapter);
if (somecode.equals("Dog")){
soundPlay(MediaPlayer.create(getBaseContext(), R.raw.star));
}
if (somecode.equals("Camel")){
soundPlay(MediaPlayer.create(getBaseContext(), R.raw.tick));
}
You can define an interface in your adapter, like below
public interface ClickListener{
void onClick();
}
Implement in fragment or activity that your adapter at:
ClickListener listener = () ->{
TextView details = word_dialog.findViewById(R.id.word_edit_desc);
details.setText("hello");
}
then pass interface to adapter use constructor or setter, and you can use interface in your viewHolder when bind like below:
itemView.setOnClickListener(()->{
if (your_list.get(getAdapterPosition()).equals("hello")) {
interface_var_name.onClick()
}
});
This is Inbuild Click event of Recyclerview and another way to use Interface
holder.itemView.setOnClickListener(v ->
if (animal.equals("Your animal String"){
//Your code
}
);
if want to use interface then this is reference link
I need some help for a summer project
This is my Events fragment
This is my MyList fragment
I'm using a RecyclerView+Cardview to display the Events. The idea is that the user can click the big plus on the right side of each card, and the card would be displayed in the MyList fragment. I would like to ask if it's possible to transfer a card directly from one fragment to another? Also, both fragments are contained within the same activity, which makes it a little trickier(I haven't found any available solutions).
If that is not possible, another way is to transfer the reference type object contained in the CardView to the MyList fragment. However, this is even less straightforward. This is because the button is inflated in the adapter, but there is no reference type object created here. I have seen many tutorials on using the Parcelable interface, however, I don't know how to implement it here when I'm unable to even create the object in the adapter. The reference object is created in another activity and stored in Firebase before it is read and displayed.
I'm going to attach my EventsAdapter.java and EventsItem.java and EventsFragment.java code below, but please let me know if I should include more code to describe the problem.
Thanks for reading my very long post!!
public class EventsAdapter extends RecyclerView.Adapter<EventsAdapter.EventsViewHolder> implements Filterable {
private ArrayList<EventsItem> mEventsList;
private ArrayList<EventsItem> mEventsListFull;
private EventsAdapter.OnItemClickListener mListener;
private Context mContext;
private DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.UK);
public interface OnItemClickListener {
void onItemClick(int position);
}
//the ViewHolder holds the content of the card
public static class EventsViewHolder extends RecyclerView.ViewHolder {
public ImageView mImageView;
public ImageView mAddButton;
public TextView mTextView1;
public TextView mTextView2;
public TextView mTextView3;
public TextView mTextView4;
public TextView mTextView5;
public EventsViewHolder(Context context, View itemView, final EventsAdapter.OnItemClickListener listener) {
super(itemView);
final Context context1 = context;
mImageView = itemView.findViewById(R.id.imageView);
mAddButton = itemView.findViewById(R.id.image_add);
mTextView1 = itemView.findViewById(R.id.title);
mTextView2 = itemView.findViewById(R.id.event_description);
mTextView3 = itemView.findViewById(R.id.date);
mTextView4 = itemView.findViewById(R.id.location);
mTextView5 = itemView.findViewById(R.id.time);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position);
}
}
}
});
mAddButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String str1 = mTextView1.getText().toString();
String str2 = mTextView2.getText().toString();
String str3 = mTextView3.getText().toString();
String str4 = mTextView4.getText().toString();
String str5 = mTextView5.getText().toString();
Bundle bundle = new Bundle();
bundle.putString("title", str1);
bundle.putString("event description", str2);
bundle.putString("date", str3);
bundle.putString("location", str4);
bundle.putString("time", str5);
MylistFragment mlf = new MylistFragment();
mlf.setArguments(bundle);
}
});
}
}
//Constructor for EventsAdapter class. This ArrayList contains the
//complete list of items that we want to add to the View.
public EventsAdapter(Context context, ArrayList<EventsItem> EventsList) {
mEventsList = EventsList;
mContext = context;
mEventsListFull = new ArrayList<>(EventsList); // copy of EventsList for SearchView
}
//inflate the items in a EventsViewHolder
#NonNull
#Override
public EventsAdapter.EventsViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.event_item, parent, false);
EventsAdapter.EventsViewHolder evh = new EventsAdapter.EventsViewHolder(mContext, v, mListener);
return evh;
}
#Override
public void onBindViewHolder(#NonNull EventsAdapter.EventsViewHolder holder, int position) {
EventsItem currentItem = mEventsList.get(position);
holder.mImageView.setImageResource(currentItem.getProfilePicture());
holder.mTextView1.setText(currentItem.getTitle());
holder.mTextView2.setText(currentItem.getDescription());
holder.mTextView3.setText(df.format(currentItem.getDateInfo()));
holder.mTextView4.setText(currentItem.getLocationInfo());
holder.mTextView5.setText(currentItem.getTimeInfo());
}
#Override
public int getItemCount() {
return mEventsList.size();
}
public class EventsItem implements Occasion, Parcelable {
//fields removed for brevity
//constructor removed for brevity
}
public EventsItem() {
}
public EventsItem(Parcel in) {
profilePicture = in.readInt();
timeInfo = in.readString();
hourOfDay = in.readInt();
minute = in.readInt();
locationInfo = in.readString();
title = in.readString();
description = in.readString();
}
public static final Creator<EventsItem> CREATOR = new Creator<EventsItem>() {
#Override
public EventsItem createFromParcel(Parcel in) {
return new EventsItem(in);
}
#Override
public EventsItem[] newArray(int size) {
return new EventsItem[size];
}
};
//getter methods have been removed for brevity
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(profilePicture);
dest.writeString(timeInfo);
dest.writeString(locationInfo);
dest.writeString(title);
dest.writeString(description);
dest.writeString(df.format(dateInfo));
dest.writeInt(hourOfDay);
dest.writeInt(minute);
}
}
public class EventsFragment extends Fragment {
ArrayList<EventsItem> EventsItemList;
FirebaseDatabase mDatabase;
DatabaseReference mDatabaseReference;
ValueEventListener mValueEventListener;
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;
private EventsAdapter mAdapter;
private View rootView;
public FloatingActionButton floatingActionButton;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_events, container, false);
mDatabase = FirebaseDatabase.getInstance();
mDatabaseReference = mDatabase.getReference().child("Events");
createEventsList();
buildRecyclerView();
floatingActionButton = rootView.findViewById(R.id.fab);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), EventsAdder.class);
startActivity(intent);
}
});
mValueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
EventsItemList.add(snapshot.getValue(EventsItem.class));
}
EventsAdapter eventsAdapter = new EventsAdapter(getActivity(), EventsItemList);
mRecyclerView.setAdapter(eventsAdapter);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
mDatabaseReference.addValueEventListener(mValueEventListener);
setHasOptionsMenu(true);
Toolbar toolbar = rootView.findViewById(R.id.events_toolbar);
AppCompatActivity activity = (AppCompatActivity) getActivity();
activity.setSupportActionBar(toolbar);
return rootView;
}
public void createEventsList() {
EventsItemList = new ArrayList<>();
}
public void buildRecyclerView() {
mRecyclerView = rootView.findViewById(R.id.recyclerview);
mLayoutManager = new LinearLayoutManager(getContext());
mAdapter = new EventsAdapter(getActivity(), EventsItemList);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
}
}
If you would like to see the same CardView within the MyListFragment, you could have the MyListFragment contain a RecyclerView, and reuse the same EventsAdapter and EventsViewHolder. The only difference is that rather than populating the adapter with all the children of the "Events" from your database, you would only populate it with the single Event that you want.
Also, since you have made your Event class implement parcelable, you do not need to manually create the bundle when clicking the plus button.
I am assuming you have a single Activity, and you simply want to replace the EventsFragment with the MyListFragment. Checkout the docs for replacing one fragment with another.
Step 1:
Extend your onItemClickListener to look like:
public interface OnItemClickListener {
void onItemClick(int position);
void onPlusButtonClick(int position);
}
and adjust the code in your EventsViewHolder constructor to look like this when the plus button is clicked:
mAddButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
// no need to manually create the bundle here
// you already have all the information you need
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onPlusButtonClick(position);
}
}
}
});
Step 2:
Implement our new method onPlusButtonClick. As per our discussion in the comments, it seems you do not implement this interface anywhere. You can implement it inside the constructor to your EventsAdapter:
public EventsAdapter(Context context, ArrayList<EventsItem> EventsList) {
mEventsList = EventsList;
mContext = context;
mEventsListFull = new ArrayList<>(EventsList); // copy of EventsList for SearchView
mListener = new OnItemClickListener() {
#Override
public void onItemClick() {
// handle clicking the entire view holder
// NOTE: inside your EventsViewHolder, it looks like you call this method on the entire itemView. This could 'swallow' the click on the plus button. You may need to adjust your code to handle this.
}
#Override
public void onPlusButtonClick(int position) {
MyListFragment myListFragment = new MyListFragment();
Event event = mEventsList.get(position);
Bundle bundle = new Bundle();
bundle.putExtra("event", event); // this will work due to implementing parcelable
myListFragment.setArguments(bundle);
// use mContext since im assuming we areinside adapter
// if in an Activity, no need to use context to get the fragment manager
FragmentTransaction transaction = mContext.getSupportFragmentManager().beginTransaction();
// Replace the EventsFragment with the MyListFragment
// and add the transaction to the back stack so the user can navigate back
transaction.replace(R.id.fragment_container, myListFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
}
}
}
Step 3:
Inside your MyListFragments onCreateView() method:
#Override
public View onCreateView (
LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState
) {
Bundle bundle = getArguments();
Event event = bundle.getExtra("event"); // again, will work due to implementing parcelable
// from here you should bind to a recycler view, and you can even reuse your adapter like so:
List<EventsItem> singleEventList = new List<EventsItem>();
singleEventList.add(event);
EventsAdapter adapter = new EventsAdapter(getActivity(), singleEventList);
// be sure to inflate and return your view here...
}
and you should be good to go!
I have left out bits of code here and there for simplicity.. but I hope this is understandable.
As a side note.. in your firebase database listener, it is bad practice to create a new EventsAdapter every single time your data is updated. Instead, you should update the data in the adapter with the new values. Do this by creating a public method inside the adapter such as replaceEvents(List<EventsItem> newEvents), and inside, replace mEventsList with the new events, then call notifyDataSetChanged().
get API link using retrofit and live data call the first time after some time not updating itself
Call the function on resume in fragment
LiveDataReport used in call retrofit and set the value mutable live
data
Model Fragment used in RecyclerView and adapter
I want get change on anything or updating in get API
immediate change updating RecyclerView list
here my code
Java
public class LiveDataReport {
private static LiveDataReport liveDataReport;
private MutableLiveData<ArrayList<Model>> modelLiveData = new MutableLiveData<>();
private ApiService apiService;
private LiveDataReport() {
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new BasicAuth("user", "pass")).build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl("http://someipaddress")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create()).build();
apiService = retrofit.create(ApiService.class);
getModelLiveData();
}
public synchronized static LiveDataReport getInstance() {
if (liveDataReport == null)
liveDataReport = new LiveDataReport();
return liveDataReport;
}
public LiveData<ArrayList<Model>> getModelLiveData() {
Call<ArrayList<Model>> arrayListCall = apiService.getLiveDataJson();
arrayListCall.enqueue(new Callback<ArrayList<Model>>() {
#Override
public void onResponse(#NotNull Call<ArrayList<Model>> call, #NotNullResponse<ArrayList<Model>> response) {
if (response.isSuccessful() && response.body() != null) {
System.out.println("//report change success Model");
modelLiveData.postValue(response.body());
}
}
#Override
public void onFailure(#NotNull Call<ArrayList<Model>> call, #NotNull Throwable t) {
System.out.println("//report change failure " + t.toString());
}
});
return modelLiveData;
}
}
public class MainViewModel extends AndroidViewModel {
private LiveData<ArrayList<Model>> liveData ;
public MainViewModel(#NonNull Application application) {
super(application);
liveData=LiveDataReport.getInstance().getModelLiveData();
}
public LiveData<ArrayList<Model>> getLiveData() {
System.out.println("//report call last update : getModelLiveData");
liveData=LiveDataReport.getInstance().getModelLiveData();
return liveData;
}
}
public class ModelFragment extends Fragment implements LifecycleOwner {
private MainViewModel mainViewModel;
private RecyclerView recyclerView;
private FragmentBinding binding;
private LiveDataAdapter adapter;
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment, container, false);
mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
recyclerView = binding.myRecycleView;
adapter = new LiveDataAdapter();
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
recyclerView.setHasFixedSize(false);
return binding.getRoot();
}
#Override
public void onResume() {
super.onResume();
if (Utils.isNetWorkConnected(requireContext())) {
mainViewModel.getLiveData().observe(getViewLifecycleOwner(),new Observer<ArrayList<Model>>() {
#Override
public void onChanged(ArrayList<Model> models) {
recyclerView.setAdapter(adapter);
adapter.setModel(models,requireContext());
}
});
} else
Utils.alertCheckNetwork(requireContext());
}
}
public class LiveDataAdapter extends AdapterSkeleton<Model, LiveDataAdapter.CustomViewHolder> {
public LiveDataAdapter(Context context, ArrayList<Model> model, RecyclerView recyclerView, IsCanSetAdapterListener canSetAdapterListener){
this.context=context;
this.items=model;
this.isCanSetAdapterListener=canSetAdapterListener;
measureHeightRecyclerViewAndItem(recyclerView,R.layout.list_model);
}
#NonNull
#Override
public CustomViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
ListModelBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.list_model, parent, false);
return new CustomViewHolder(binding);
}
#Override
public void onBindViewHolder(#NonNull CustomViewHolder holder, int position) {
if(skeletonConfig.isSkeletonIsOn()){
return;
}
else{
holder.binding.appSkeleton.setShowSkeleton(false);
holder.binding.appSkeleton.finishAnimation();
}
Model model = items.get(position);
holder.binding.setModel(reportStatus);
setAnimation(holder.itemView, position);
}
private void setAnimation(View viewToAnimate, int position) {
Animation animation = AnimationUtils.loadAnimation(context, R.anim.item_animation_fall_down);
viewToAnimate.startAnimation(animation);
}
class CustomViewHolder extends RecyclerView.ViewHolder {
ListModelBinding binding;
public CustomViewHolder(#NonNull ListModelBinding itemView) {
super(itemView.getRoot());
binding = itemView;
}
}
}
updating list does not means it will shown into RecyclerView immediately . you have to manually update RecyclerView as well .
to implement this concept , create a method into your RecyclerView Adapter something like this one below
void updateList(List<POJO> newList) {
oldList = newList;
notifyDataChange();
}
so you will see that your RecyclerView will update , i know that we use livedata to do it automatically but in order to update recyclerView you have to approach this concept .
So I have created a generic PageAdapter to be used in various parts on the app, which looks like this:
public class ImagePagerAdapter extends PagerAdapter {
private final LayoutInflater layoutInflater;
private final Picasso picasso;
private final int layoutResId;
private final List<AssociatedMedia> images;
public ImagePagerAdapter(Context context, int layoutResId) {
layoutInflater = LayoutInflater.from(context);
picasso = Injector.getInstance().getPicasso();
this.layoutResId = layoutResId;
this.images = new ArrayList<>();
}
public void setMedia(List<AssociatedMedia> media) {
images.clear();
for (AssociatedMedia productMedia : media) {
if (productMedia.type == AssociatedMediaType.IMAGE) {
images.add(productMedia);
}
else {
// non-images all at the end
break;
}
}
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
AssociatedMedia image = images.get(position);
ImageView imageView = (ImageView) layoutInflater.inflate(layoutResId, container, false);
container.addView(imageView);
picasso.load(Uri.parse(image.urls[0])).into(imageView);
return imageView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
ImageView imageView = (ImageView) object;
container.removeView(imageView);
picasso.cancelRequest(imageView);
}
#Override
public int getCount() {
return images.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
I then call this adapter in a fragment, like this:
ImagePagerAdapter productImageAdapter = new ImagePagerAdapter(getActivity(), R.layout.photo_container_small);
productImageAdapter.setMedia(medias);
productImage.setAdapter(productImageAdapter);
My question is, how can I invoke a onClickListener in the fragment. So my scenario is that, we have a carousel of images, and once the user click on an image, it will open a large view on that image, so sort of need an onItemClickListener, but this can only be invoked in the pagerAdapter.
So is there a way to either call a onClickListener in the fragment, or notify the fragment from the adapter when an item has been clicked?
This is a response to your comment. For formating and size reasons I use an answer for it. It is a general example on how to use an interface to de-couple a fragment from an adapter class which makes the adapter re-usable in several fragments (and even other projects).
public class MyAdapter {
MyAdapterListener listener;
private MyAdapter() {}
public MyAdapter(MyAdapterListener listeningActivityOrFragment) {
listener = listeningActivityOrFragment;
}
}
public interface MyAdapterListener {
void somethingTheFragmentNeedsToKnow(Object someData);
}
public class SomeFragment extends Fragment implements MyAdapterListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.my_view, container, false);
// Do everyhting here to init your view.
// Create an Adapter and bind it to this fragment
MyAdapter myAdapter = new MyAdapter(this);
return view;
}
// Implement the listener interface
#Override
public void somethingTheFragmentNeedsToKnow(Object someData) {
// Get the data and process it.
}
}
So in your case the method within the interface may well be onClick(int position); If you need more than one method, then just add them.