Is initializing a new RecyclerView Adapter an asynchronous call? - java

My question is, is the initialization of a new RecyclerView adapter an asynchronous call?
I have an adapter that I am creating:
mRecyclerAdapter = new TestAdapter(mContext, mListImages);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setAdapter(mRecyclerAdapter);
After initializing it, I can call .add() directly after these methods without calling .notifyDataSetChanged() and they would still be added to my adapter, and displayed.
mRecyclerAdapter = new TestAdapter(mContext, mListImages);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setAdapter(mRecyclerAdapter);
mListImages.add( . . .);
mListImages.add( . . .);
mListImages.add( . . .);
Are RecyclerView adapters automatically initialized on a background thread?
Here is my adapter:
public class SelectBucketAdapter extends RecyclerView.Adapter<SelectBucketAdapter.ViewHolder> {
private static final String TAG = "SelectBucketAdapter";
private Context mContext;
private ArrayList<String> mBucketList;
public SelectBucketAdapter(Context mContext, ArrayList<String> mBucketList,
) {
this.mContext = mContext;
this.mBucketList = mBucketList;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.vh_selectbucketmenu_layout, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int i) {
... binding views
}
#Override
public int getItemCount() {
return mBucketList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
#BindView(R.id.vh_selectbucketmenu_name)
TextView vhBucketName;
int mPosition;
public ViewHolder(#NonNull View itemView) {
super(itemView);
}
}
}

Are RecyclerView adapters automatically initialized on a background thread?
No, they are not.
Is the initialization of a new RecyclerView adapter an asynchronous call?
No, it is not.
The layout creation and attachment to window is async.
What this means?
Assume we have following code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val adapter = MyAdapter()
recyclerView.adapter = adapter
adapter.list.add("1")
}
In this case we will see the "1" being displayed on the screen, because at the point when adapter.list.add("1") was executed RecyclerView hasn't yet passed through its measure-layout-draw cycle.
Now let's consider following code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val adapter = MyAdapter()
recyclerView.adapter = adapter
Handler().postDelayed({ adapter.list.add("AAA") }, 2000)
}
In this case adapter.list.add("AAA") will be executed in roughly 2 seconds. As long as RecyclerView will already be laid out by that time, then mutating the adapter dataset won't make the RecyclerView show the item, because RecyclerView doesn't know if dataset has suffered a change.
Let's consider following case:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val adapter = MyAdapter()
recyclerView.adapter = adapter
recyclerView.doOnPreDraw { adapter.list.add("preDraw") }
recyclerView.doOnLayout { adapter.list.add("layout") }
adapter.list.add("onCreate")
}
In this case still only "onCreate" will be displayed on screen.
To sum up: as soon as RecyclerView has passed its measure step (i.e. View#onMeasure) then mutating adapter won't be reflected unless adapter explicitly notifies RecyclerView.

The term 'intiializing' is quite ambiguous. At what point do you consider the adapter 'intiialized'?. To me, an Adapter can be intiialized with 0 items. So you can't really measure whether the Adapter has been intiialized by looking at the contents of the RecyclerView.
Secondly, you're asking if 'intiializing is an asynchronous call'. The 'initialization' of a RecyclerView Adapter is a whole bunch of calls. What you've observed is that the result of these calls is not always immediately visible - which tells you that at least some of what is happening behind the scenes is asynchronous.
I think what you're trying to ask is 'at what point in the lifecycle of a RecyclerView are you required to notify the Adapter of changes'. And it sounds like the answer is 'once the RecyclerView has reached onMeasure() (based on #azizbekian's answer).
If you want to add items to the Adapter without having to call notifyDataSetChanged(), then I would suggest adding them before calling RecyclerView.setAdapter(). Once you've set the Adapter, any further changes you make to the Adapter's dataset should be followed with a notifyDataSetChanged() call (or preferably, one of the more specific notifyX() calls).

Related

Android Studio Problem: W/RecyclerView: No adapter attached; skipping layout [duplicate]

Just implemented RecyclerView in my code, replacing ListView.
Everything works fine. The data is displayed.
But error messages are being logged:
15:25:53.476 E/RecyclerView: No adapter attached; skipping layout
15:25:53.655 E/RecyclerView: No adapter attached; skipping layout
for the following code:
ArtistArrayAdapter adapter = new ArtistArrayAdapter(this, artists);
recyclerView = (RecyclerView) findViewById(R.id.cardList);
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
As you can see I have attached an adapter for RecyclerView.
So why do I keep getting this error?
I have read other questions related to the same problem but none of them help.
Can you make sure that you are calling these statements from the "main" thread outside of a delayed asynchronous callback (for example inside the onCreate() method).
As soon as I call the same statements from a "delayed" method. In my case a ResultCallback, I get the same message.
In my Fragment, calling the code below from inside a ResultCallback method produces the same message. After moving the code to the onConnected() method within my app, the message was gone...
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setOrientation(LinearLayoutManager.VERTICAL);
list.setLayoutManager(llm);
list.setAdapter( adapter );
I was getting the same two error messages until I fixed two things in my code:
(1) By default, when you implement methods in the RecyclerView.Adapter it generates:
#Override
public int getItemCount() {
return 0;
}
Make sure you update your code so it says:
#Override
public int getItemCount() {
return artists.size();
}
Obviously if you have zero items in your items then you will get zero things displayed on the screen.
(2) I was not doing this as shown in the top answer: CardView layout_width="match_parent" does not match parent RecyclerView width
//correct
LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_listitem, parent, false);
//incorrect (what I had)
LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_listitem,null);
(3) EDIT: BONUS:
Also make sure you set up your RecyclerView like this:
<android.support.v7.widget.RecyclerView
android:id="#+id/RecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
NOT like this:
<view
android:id="#+id/RecyclerView"
class="android.support.v7.widget.RecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
I have seen some tutorials using the latter method. While it works I think it generates this error too.
I have the same situation with you, display is ok, but error appear in the locat.
That's my solution:
(1) Initialize the RecyclerView & bind adapter ON CREATE()
RecyclerView mRecycler = (RecyclerView) this.findViewById(R.id.yourid);
mRecycler.setAdapter(adapter);
(2) call notifyDataStateChanged when you get the data
adapter.notifyDataStateChanged();
In the recyclerView's source code, there is other thread to check the state of data.
public RecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.mObserver = new RecyclerView.RecyclerViewDataObserver(null);
this.mRecycler = new RecyclerView.Recycler();
this.mUpdateChildViewsRunnable = new Runnable() {
public void run() {
if(RecyclerView.this.mFirstLayoutComplete) {
if(RecyclerView.this.mDataSetHasChangedAfterLayout) {
TraceCompat.beginSection("RV FullInvalidate");
RecyclerView.this.dispatchLayout();
TraceCompat.endSection();
} else if(RecyclerView.this.mAdapterHelper.hasPendingUpdates()) {
TraceCompat.beginSection("RV PartialInvalidate");
RecyclerView.this.eatRequestLayout();
RecyclerView.this.mAdapterHelper.preProcess();
if(!RecyclerView.this.mLayoutRequestEaten) {
RecyclerView.this.rebindUpdatedViewHolders();
}
RecyclerView.this.resumeRequestLayout(true);
TraceCompat.endSection();
}
}
}
};
In the dispatchLayout(), we can find there is the error in it:
void dispatchLayout() {
if(this.mAdapter == null) {
Log.e("RecyclerView", "No adapter attached; skipping layout");
} else if(this.mLayout == null) {
Log.e("RecyclerView", "No layout manager attached; skipping layout");
} else {
i have this problem , a few time problem is recycleView put in ScrollView object
After checking implementation, the reason appears to be the following. If RecyclerView gets put into a ScrollView, then during measure step its height is unspecified (because ScrollView allows any height) and, as a result, gets equal to minimum height (as per implementation) which is apparently zero.
You have couple of options for fixing this:
Set a certain height to RecyclerView
Set ScrollView.fillViewport to true
Or keep RecyclerView outside of ScrollView. In my opinion, this is the best option by far. If RecyclerView height is not limited - which is the case when it's put into ScrollView - then all Adapter's views have enough place vertically and get created all at once. There is no view recycling anymore which kinda breaks the purpose of RecyclerView .
(Can be followed for android.support.v4.widget.NestedScrollView as well)
1) Create ViewHolder that does nothing :)
// SampleHolder.java
public class SampleHolder extends RecyclerView.ViewHolder {
public SampleHolder(View itemView) {
super(itemView);
}
}
2) Again create RecyclerView that does nothing :)
// SampleRecycler.java
public class SampleRecycler extends RecyclerView.Adapter<SampleHolder> {
#Override
public SampleHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
#Override
public void onBindViewHolder(SampleHolder holder, int position) {
}
#Override
public int getItemCount() {
return 0;
}
}
3) Now when your real recycler is not ready just use the sample one like below.
RecyclerView myRecycler = (RecyclerView) findViewById(R.id.recycler_id);
myRecycler.setLayoutManager(new LinearLayoutManager(this));
myRecycler.setAdapter(new SampleRecycler());
This is not best solution though but it works! Hope this is helpful.
It happens when you are not setting the adapter during the creation phase:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
....
}
public void onResume() {
super.onResume();
mRecyclerView.setAdapter(mAdapter);
....
}
Just move setting the adapter into onCreate with an empty data and when you have the data call:
mAdapter.notifyDataSetChanged();
Check if you have missed to call this method in your adapter
#Override
public int getItemCount() {
return list.size();
}
In Kotlin we had this weird illogical issue.
This didn't work:
mBinding.serviceProviderCertificates.apply {
adapter = adapter
layoutManager = LinearLayoutManager(activity)
}
While this worked:
mBinding.serviceProviderCertificates.adapter = adapter
mBinding.serviceProviderCertificates.layoutManager = LinearLayoutManager(activity)
Once I get more after work hours, I will share more insights.
Make sure you set the layout manager for your RecyclerView by:
mRecyclerView.setLayoutManager(new LinearLayoutManager(context));
Instead of LinearLayoutManager, you can use other layout managers too.
ArtistArrayAdapter adapter = new ArtistArrayAdapter(this, artists);
recyclerView = (RecyclerView) findViewById(R.id.cardList);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
Just replace above code with this and it should work. What you did wrong is you called setAdapter(adapter) before calling layout manager.
I had the same error I fixed it doing this if you are waiting for data like me using retrofit or something like that
Put before Oncreate
private ArtistArrayAdapter adapter;
private RecyclerView recyclerView;
Put them in your Oncreate
recyclerView = (RecyclerView) findViewById(R.id.cardList);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new ArtistArrayAdapter( artists , R.layout.list_item ,getApplicationContext());
recyclerView.setAdapter(adapter);
When you receive data put
adapter = new ArtistArrayAdapter( artists , R.layout.list_item ,getApplicationContext());
recyclerView.setAdapter(adapter);
Now go in your ArtistArrayAdapter class and do this what it will do is if your array is empty or is null it will make GetItemCount return 0 if not it will make it the size of artists array
#Override
public int getItemCount() {
int a ;
if(artists != null && !artists.isEmpty()) {
a = artists.size();
}
else {
a = 0;
}
return a;
}
For those who use the RecyclerView within a fragment and inflate it from other views: when inflating the whole fragment view, make sure that you bind the RecyclerView to its root view.
I was connecting and doing everything for the adapter correctly, but I never did the binding.
This answer by #Prateek Agarwal has it all for me, but here is more elaboration.
Kotlin
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val rootView = inflater?.inflate(R.layout.fragment_layout, container, false)
recyclerView = rootView?.findViewById(R.id.recycler_view_id)
// rest of my stuff
recyclerView?.setHasFixedSize(true)
recyclerView?.layoutManager = viewManager
recyclerView?.adapter = viewAdapter
// return the root view
return rootView
}
Java
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView= inflater.inflate(R.layout.fragment_layout,container,false);
recyclerview= rootView.findViewById(R.id.recycler_view_id);
return rootView;
}
These Lines must be in OnCreate:
mmAdapter = new Adapter(msgList);
mrecyclerView.setAdapter(mmAdapter);
This happens because the actual inflated layout is different from that which is being referred by you while finding the recyclerView. By default when you create the fragment, the onCreateView method appears as follows:
return inflater.inflate(R.layout.<related layout>,container.false);
Instead of that, separately create the view and use that to refer to recyclerView
View view= inflater.inflate(R.layout.<related layout>,container.false);
recyclerview=view.findViewById(R.id.<recyclerView ID>);
return view;
In my layout xml file, the bottom line with layoutManager was missing. The error disappeared after I added it.
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view_chat"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:layoutManager="LinearLayoutManager"/>
First initialize the adapter
public void initializeComments(){
comments = new ArrayList<>();
comments_myRecyclerView = (RecyclerView) findViewById(R.id.comments_recycler);
comments_mLayoutManager = new LinearLayoutManager(myContext);
comments_myRecyclerView.setLayoutManager(comments_mLayoutManager);
updateComments();
getCommentsData();
}
public void updateComments(){
comments_mAdapter = new CommentsAdapter(comments, myContext);
comments_myRecyclerView.setAdapter(comments_mAdapter);
}
When ever there is a change in the dataset set, just call the updateComments method.
I had this error, and I tried to fix for a while until I found the solution.
I made a private method buildRecyclerView, and I called it twice, first on onCreateView and then after my callback (in which I fetch data from an API). This is my method buildRecyclerView in my Fragment:
private void buildRecyclerView(View v) {
mRecyclerView = v.findViewById(R.id.recycler_view_loan);
mLayoutManager = new LinearLayoutManager(getActivity());
((LinearLayoutManager) mLayoutManager).setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new LoanAdapter(mExampleList);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
}
Besides, I have to modify the method get-Item-Count in my adapter, because On on-Create-View the list is null and it through an error. So, my get-Item-Count is the following:
#Override
public int getItemCount() {
try {
return mLoanList.size();
} catch (Exception ex){return 0;}
}
This is really a simple error you are getting, there in no need of doing any codes in this.
This error occurs due to the wrong layout file used by the activity. By IDE i automatically created a layout v21 of a layout which became a default layout of the activity.
all codes I did in the old layout file and new one was only having few xml codes, which led to that error.
Solution: Copy all codes of old layout and paste in layout v 21
In my case, I was setting the adapter inside onLocationChanged() callback AND debugging in the emulator. Since it didn't detected a location change it never fired. When I set them manually in the Extended controls of the emulator it worked as expected.
I have solved this error. You just need to add layout manager
and add the empty adapter.
Like this code:
myRecyclerView.setLayoutManager(...//your layout manager);
myRecyclerView.setAdapter(new RecyclerView.Adapter() {
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
#Override
public int getItemCount() {
return 0;
}
});
//other code's
// and for change you can use if(mrecyclerview.getadapter != speacialadapter){
//replice your adapter
//}
Just add the following to RecyclerView
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
Example:
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</android.support.v7.widget.RecyclerView>
In case you're getting still error while using ViewBinding, make sure you're using the binding to return the inflated view ie
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return fragmentBinding.getRoot();
}
In my situation it was a forgotten component which locates in ViewHolder class but it wasn't located in layout file
I had the same problem and realized I was setting both the LayoutManager and adapter after retrieving the data from my source instead of setting the two in the onCreate method.
salesAdapter = new SalesAdapter(this,ordersList);
salesView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
salesView.setAdapter(salesAdapter);
Then notified the adapter on data change
//get the Orders
Orders orders;
JSONArray ordersArray = jObj.getJSONArray("orders");
for (int i = 0; i < ordersArray.length() ; i++) {
JSONObject orderItem = ordersArray.getJSONObject(i);
//populate the Order model
orders = new Orders(
orderItem.getString("order_id"),
orderItem.getString("customer"),
orderItem.getString("date_added"),
orderItem.getString("total"));
ordersList.add(i,orders);
salesAdapter.notifyDataSetChanged();
}
This issue is because you are not adding any LayoutManager for your RecyclerView.
Another reason is because you are calling this code in a NonUIThread. Make sure to call this call in the UIThread.
The solution is only you have to add a LayoutManager for the RecyclerView before you setAdapter in the UI Thread.
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
Solved by setting the initialized empty list and adapter at the bottom and calling notifyDataSetChanged when results are fetched.
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
recyclerviewItems.setLayoutManager(linearLayoutManager);
someAdapter = new SomeAdapter(getContext(),feedList);
recyclerviewItems.setAdapter(someAdapter);
I lost 16 minutes of my life with this issue, so I'll just admit to this incredibly embarrassing mistake that I was making- I'm using Butterknife and I bind the view in onCreateView in this fragment.
It took a long time to figure out why I had no layoutmanager - but obviously the views are injected so they won't actually be null, so the the recycler will never be null .. whoops!
#BindView(R.id.recycler_view)
RecyclerView recyclerView;
#Override
public View onCreateView(......) {
View v = ...;
ButterKnife.bind(this, v);
setUpRecycler()
}
public void setUpRecycler(Data data)
if (recyclerView == null) {
/*very silly because this will never happen*/
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
//more setup
//...
}
recyclerView.setAdapter(new XAdapter(data));
}
If you are getting an issue like this trace your view and use something like uiautomatorviewer
In my case it happened cause i embedded a RecyclerView in a LinearLayout.
I previously had a layout file only containing one root RecyclerView as follows
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="#layout/fragment_products"
android:name="Products.ProductsFragment"
app:layoutManager="LinearLayoutManager"
tools:context=".Products.ProductsFragment"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"/>
I believe the problem is within the 3 lines separated. Anyway, I think its a simple problem, ill be working on it tomorrow; thought i should write what i found before forgetting about this thread.
Adding yet another answer since I came across this thread googling the error. I was trying to initialize a PreferenceFragmentCompat but I forgot to inflate the preference XML in onCreatePreferences like this:
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.fragment_settings, null)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
// Missing this line here:
// setPreferencesFromResource(R.xml.settings, rootKey)
}
}
The error was a mystery until I realized that PreferenceFragmentCompat must be using a RecyclerView internally.
// It happens when you are not setting the adapter during the creation phase: call notifyDataSetChanged() when api response is getting Its Working
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
magazineAdapter = new MagazineAdapter(getContext(), null, this );
newClipRecyclerView.setAdapter(magazineAdapter);
magazineAdapter.notifyDataSetChanged();
APICall();
}
public void APICall() {
if(Response.isSuccessfull()){
mRecyclerView.setAdapter(mAdapter);
}
}
Just move setting the adapter into onCreate with an empty data and when you have the data call:
mAdapter.notifyDataSetChanged();

Using multiple onClick methods in a single interface for RecyclerView

Context:
I've implemented a RecyclerView in my to-do list app.
I wanted to be able to use various onClick methods for items within the RecyclerView so I created an interface called onTaskListener.
This interface has two method stubs, one for onClick and one for onLongClick. In my ViewHolder, I implement both the onClick() and onLongClick() methods which simply pass off control to my onTaskClickListener().
In my adapter, I create an onTaskClickListener().
Then in my main activity, I implement the methods within onTaskClickListener().
My issue is that while my onTaskClick() works perfectly, my onTaskLongClick doesn't seem to function at all. Is there something wrong with the way I set up my RecyclerView/Adapter/ViewHolder/ViewModel pattern?
Question: If the way I have implemented my interface is wrong, how do I include multiple types of click events within a single interface?
Here are the relevant contents of each file (I know it's a lot, I'm very sorry for the wall of code):
onTaskClickListener.java:
public interface OnTaskListener {
void onTaskClick(int position); // Interfaces are implicitly abstract
void onTaskLongClick(int position);
}
itemViewHolder.java:
public class itemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
View itmView; // This is the general view
TextView txtView; // This is the specific text view that shows up as a singular task in the list of to-do tasks
OnTaskListener onTaskListener; // Create an OnTaskListener inside our view holder which allows the view holder to realize it's been clicked
public itemViewHolder(#NonNull View itemView, OnTaskListener inputOnTaskListener) {
super(itemView);
itmView = itemView;
txtView = itemView.findViewById(R.id.txtTask);
this.onTaskListener = inputOnTaskListener; // Take an onTaskListener that is passed into the object and store it internally
itemView.setOnClickListener(this); // passes the View.OnClickListener context to the itemView via "this"
}
#Override
public void onClick(View view) {
onTaskListener.onTaskClick(getAdapterPosition()); // This says that whenever we register a click event, we pass the logic onto the taskClick event
}
#Override
public boolean onLongClick(View view) {
onTaskListener.onTaskLongClick(getAdapterPosition()); // This says that whenever we register a longClick event, we pass the logic onto the taskClick event
return true; // This means that we have successfully consumed the long click event. No other click events will be notified
}
}
dataAdapter.java
public class dataAdapter extends RecyclerView.Adapter<itemViewHolder> {
List<taskItem> taskItemList;
private OnTaskListener onTaskListener;
public dataAdapter(List<taskItem> inputTaskItemList, OnTaskListener inputOnTaskListener){
this.taskItemList = inputTaskItemList;
this.onTaskListener = inputOnTaskListener;
}
#NonNull
#Override
public itemViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View localView = LayoutInflater.from(parent.getContext()).inflate(R.layout.taskholder, parent, false); //Don't even know what this line does, it's all so over my head
return new itemViewHolder(localView, onTaskListener); // Return an instance of whatever we made directly above this line
}
#Override
public void onBindViewHolder(#NonNull itemViewHolder holder, final int position) {
holder.txtView.setText(taskItemList.get(position).taskTitle);
// Look inside our ViewModel and get the text for this specific instance of the ViewModel, which corresponds to the current position
}
#Override
public int getItemCount() {
return taskItemList.size();
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements OnTaskListener{
private RecyclerView taskList; // Creates a RecyclerView to hook up to our RecyclerView widget in the UI
private dataAdapter localAdapter; // Instantiates our custom adapter class
List<taskItem> myItems; // Stores the items in a list of taskItem's
private RecyclerView.LayoutManager localLayoutManager; // God knows what this does :(
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
taskList = findViewById(R.id.taskList); // Connects our list from UI to recycler view code
localLayoutManager = new LinearLayoutManager(this); // assigns our localLayoutManager to an actual Layout Manager
taskList.setLayoutManager(localLayoutManager); // connecting our layout manager to our recycler view
taskList.setHasFixedSize(true);
myItems = new ArrayList<>(); // Now we FINALLY make our to-do list and populate it with actual tasks
myItems.add(new taskItem("groceries"));
myItems.add(new taskItem("practice bjj"));
localAdapter = new dataAdapter(myItems, this); // Pass the to do list to the adapter so it can feed it to the recycler view
taskList.setAdapter(localAdapter); // Lastly set the recycler view's adapter to the one we made above
}
#Override
public void onTaskClick(int position) {
taskItem currentTask = myItems.get(position);
if(!(currentTask.taskTitle.startsWith("Done: "))){ // Logic that marks a task as done on tap
currentTask.taskTitle = "Done: " + currentTask.taskTitle;
//logic that moves the tapped item to bottom of list
myItems.remove(position);
myItems.add(myItems.size(), currentTask);
localAdapter.notifyItemMoved(position, myItems.size());
}
else if(myItems.get(position).taskTitle.startsWith("Done: ")){ // Logic for if user taps a task already marked "done"
currentTask.taskTitle = currentTask.taskTitle.replaceFirst("Done: ", "");
myItems.set(position, currentTask); // Remove prefix
localAdapter.notifyItemChanged(position);
myItems.remove(position);
myItems.add(0, currentTask);
}
localAdapter.notifyDataSetChanged(); // Let the activity know that the data has changed
}
#Override
public void onTaskLongClick(int position) { // This branch deals with deleting tasks on long click
myItems.remove(position);
localAdapter.notifyItemRemoved(position); // Item has been deleted
}
}
You never call setOnLongClickListener():
public itemViewHolder(#NonNull View itemView, OnTaskListener inputOnTaskListener) {
super(itemView);
itmView = itemView;
txtView = itemView.findViewById(R.id.txtTask);
this.onTaskListener = inputOnTaskListener; // Take an onTaskListener that is passed into the object and store it internally
itemView.setOnClickListener(this); // passes the View.OnClickListener context to the itemView via "this"
// Add this line
itemView.setOnLongClickListener(this); // passes the View.OnLongClickListener context to the itemView via "this"
}
Alternatively, you can avoid going through this entirely by inlining the entire OnLongClickListener (and similarly for the OnClickListener):
itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
onTaskListener.onTaskLongClick(getAdapterPosition()); // This says that whenever we register a longClick event, we pass the logic onto the taskClick event
return true; // This means that we have successfully consumed the long click event. No other click events will be notified
}
});
Thus avoiding having your itemViewHolder class implement the OnLongClickListener interface and making it impossible to forget to call setOnLongClickListener().

Refreshing a RecyclerView when all items are removed

I have a RecyclerView that shows a list of items.
If there is no item to show, The recyclerview shows one item with a specific view (to tell the user there is no item instead of a white screen).
Within HistoryFragment:
private void initRecyclerView(Boolean isNoResult){
HistoryRecyclerViewAdapter adapter = new HistoryRecyclerViewAdapter(mContext, mRecords, **isNoResult**);
mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
mRecyclerView.setAdapter(adapter);
}
Within HistoryRecyclerViewAdapter:
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view;
if(**isEmpty**) {
**view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_listitem_prhistory_empty, parent, false);**
} else {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_listitem_prhistory, parent, false);
}
ViewHolder holder = new ViewHolder(view);
return holder;
}
So, it's possible to remove items one by one, if we click on them.
I would like to set isEmpty to true and refresh the RecyclerView when the dataSet is null.
I already know where to call that method but I really don't know how I can do that? (i.e. refresh the RecyclerView with isEmpty = true so I can display the cell that explain to the user that there is no record anymore).
Don't inflate different view-holders, because when the adapter has no items, not a single one of them will ever be inflated. Instead one can wrap the RecyclerView together with a "no data" Fragment into a ViewFlipper, which then can be switched to the Fragment, when the RecyclerView adapter has no items.
Best practice is to use an empty view outside of RecyclerView but in case you like to do what you want:
1.in onCreateViewHolder only inflate one layout which has empty and item views
on item delete check if your array is empty and then add a null item
then in onBindViewHolder check the model if model is Null visible empty view otherwise show item view
summary:
onBind:
model is null : empty View visible
model is not null: item View visible
use interface to refresh the RecyclerView after remove something like this
public interface RefreshRecyclerView {
public void Refresh();
}
then in activity or fragment implement the interface
Fragment implements RefreshRecyclerView
you will have override method like this
#Override
public void Refresh() {
// set adapter again here
}
then pass the interface to adapter like this
RefreshRecyclerView refresh = (RefreshRecyclerView) this;
yourRecycler.setadapter(refresh);
fially when user clicked on adapter use this
refresh. Refresh();

Using a Fragment as the Context in Android

I'm trying to follow this tutorial
I have a project that uses the Sidebar Navigation, so I have one MainActivity and multiple Fragments. At ~6:20 into the video, you can see the following code:
PersonListAdapter adapter = new PersonListAdapter(
this,
R.layout.adapter_view_layout,
peopleList);
The constructor for the PersonListAdapter Class is:
public PersonListAdapter(Context context, int resource, ArrayList<Attacks> objects) {
super(context, resource, objects);
this.mContext = mContext;
mResource = resource;
}
The problem lies with Context.
If I use the word "this", there is a red line.
If I replace
"this" with "getActivity()", there is no red line, but the app
crashes when I run it.
I've also tried "this.getContext()" and "this.getActivity()"
I have also tried replacing "this" with "getActivity().getApplicationContext()", and the app crashes.
The tutorial uses MainActivity.java, but my code is in FragmentCharacters.java. I don't know what I'm supposed to write in place of "this", or if I need to change something in the PersonListAdapter class for Context.
You cannot use a Fragment as a Context, because Fragment doesn't inherit from Context.
However, if you consult the Fragment lifecycle, you can see that the Fragment has access to its host Activity at any time between the lifecycle callbacks OnActivityCreated() and onDestroyView(). If you try to access the Activity before OnActivityCreated(), for example, it will probably return null.
So make sure you are calling getActivity() from within onActivityCreated() or later, which will make sure your Activity is available.
UPDATE, I Provided a Case Example inside the Code Snippets as well, and I chose "FragmentName" as Fragment name for example.
First Look at This Fragment Structure.
I Added [mAdapter] in Both onCreate and onCreateView
And I Added FragmentName.this for the Forth argument
The Reason is, You can send data from The Adapter to Other Activities with it, for Example FragmentName.mAdapter.getLayoutPosition()
But, Let's assume We have an ImageView which is In MainActivity and we want to use it in our Adapter, So let's Establish an ImageView In our Fragment as well, Notice I Declared the ImageView Inside onCreate
And, For Another Example, Let's Assume we Have a Public Void at the End of our Fragment as Well, It can be Literally Anything. I Named it ExampleClass
/////////FIRST TAKE A LOOK AT FRAGMENT//////////
public class FragmentName extends Fragment {
PersonListAdapter adapter;
ImageView imageView; // For Example thi ImageView is from MainActivity
public FragmentName() {
...
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
adapter = new PersonListAdapter(getContext(), R.layout.adapter_view_layout, peopleList, FragmentName.this);
imageView = (ImageView) getActivity().findViewById(R.id.ImageView);
// This Imageview is in Another Activity, Like MainActivity
// So we Need to Find it Using 'getActivity()'
...
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
adapter = new PersonListAdapter(getContext(), R.layout.adapter_view_layout, peopleList, FragmentName.this);
}
}
public void ExampleClass(int color, ...) {
...
}
////////////////////////////////////////////////////////////////
Now, Let's take this Example into our Adapter as well, to Show how it can be Used.
But, In the Adapter Use [FragmentName], Instead of [Fragment] like Below:
///////////NOW INSIDE YOUR ADAPTER/////////////
public class PersonListAdapter extends RecyclerView.Adapter < PersonListAdapter.myViewHolder > {
FragmentName myFragment; // SEE WHAT HAPPENDED HERE?
...
public PersonListAdapter(Context context, int resource, ArrayList < Attacks > objects, FragmentName fragment) {
super(context, resource, objects);
this.mContext = mContext;
mResource = resource;
this.myFragment = fragment
}
#Override
public PersonListAdapter.myViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Example use of myFragment
// Lets Execute ExmpleClass inside the Fragment
myFragment.ExampleClass(int color, ...);
// Let's Use the ImageView from MainActivity Here
myFragment.imageView.setImageRresource(...);
...
}
...
// YOU CAN NOW USE "myFragment" As a Context In your Adapter
The Good Part about this is That You can Use Fragment As CONTEXT in Your PersonListAdapter
Update: The second Code, onCreateViewHolder is wrong, it has to be inside a ClickListener in ViewHolder or onBindViewHolder

How to access LayoutManager from RecyclerView.Adapter to get scrollToPosition?

I customized RecyclerView by adding separate layouts for header and footer. I created constants to determine the property of header, footer and list item in the adapter class. I also created a ViewHolder pattern and assign the layouts to be shown based on the view type. I fixed the header at zeroth position and footer at last position of the array by override getItemViewType method.
I want to make the footer element clickable, so I assign a setOnClickListener(new View.OnClickListener()) and overwrote onClick(view: View)
My goal is to click on the footer and scrollToPosition 0 or 1 (0 = Header, 1 = first item element).
That's MyAdapter definition:
class MyAdapter(context: Context, data: Array[String]) extends RecyclerView.Adapter[RecyclerView.ViewHolder]
...
override def onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int): Unit = holder match {
...
case holder:FooterViewHolder =>
holder.backToTop.setOnClickListener(new View.OnClickListener() {
override def onClick (view: View) {
backToTop(???)
Toast.makeText (context, "Clicked Footer", Toast.LENGTH_SHORT).show()
}
})
...
}
...
I read I just need to do this: recyclerView.getLayoutManager().scrollToPosition(position)
Unfortunately I can't access the LayoutManager from my Adapter class. Any ideas what I can do?
Another approach
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
}
Make constuctor of MyAdapter as follow.
MyAdapter myAdapter=new MyAdapter(context,list,mLayoutManager);

Categories