I have come across an issue when using RecyclerViews. Basically during the initial load of a RecyclerView (after I start an activity) there is a small delay before items appear.
After experimenting for a while I found a way to remove this delay by wrapping my adapter in another class as follows:
public class AdapterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private RecyclerView.Adapter<RecyclerView.ViewHolder> mAdapter;
public AdapterWrapper(RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
mAdapter = adapter;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return mAdapter.onCreateViewHolder(parent, viewType);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
mAdapter.onBindViewHolder(holder, position);
}
#Override
public int getItemCount() {
return mAdapter.getItemCount();
}
#Override
public int getItemViewType(int position) {
return mAdapter.getItemViewType(position);
}
}
Then in my activity I have this:
protected void onCreate(Bundle savedInstanceState) {
...
setUpRecyclerView();
...
}
public void setUpRecyclerView() {
mAdapter = new MyAdapter(this, mCursor);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//mRecyclerView.setAdapter(mAdapter); // This causes a small delay.
mRecyclerView.setAdapter(new AdapterWrapper(mAdapter)); // This doesn't
}
This seems really weird to me and I have no idea why the behaviour is different. Has anybody got any potential theory to explain this?
Extra information:
-I'm using a cursor loader to provide data to my adapter.
-My adapter is subclassed using a CursorRecyclerAdapter found at http://quanturium.github.io
You have to override every method, not just implement the abstract methods.
When you call your overriden methods on AdapterWrapper it's getting passed to the wrapped object. Every other method is going to your wrapper and not getting passed on.
If im right, your premise may be a red herring. Delays like this can be—and usually are—because you are performing updates to the ui from a background thread.
We had a similar issue and finally tracked it dowm to that being rhe case. Moving the code back to the main thread, everything became instant again.
Hope this helps!
Related
I'm trying to implement swipe to archive note in RecyclerView.
It was working fine but after I added these codes to refresh the RecyclerView from onResume(), Swiping although does archive the Note, but the item doesn't get removed and stays at a state you can see in image below:
This is what I do in onResume() :
#Override
protected void onResume() {
super.onResume();
notes = noteDAO.getAllNotes();
noteAdapter = new NoteAdapter(notes,this);
recyclerView.setAdapter(noteAdapter);
}
ItemTouchHelper onSwiped():
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
noteAdapter.deleteItem(position,rv);
}
deleteItem method in Adapter:
public void deleteItem(int position, RecyclerView rv) {
noteDAO = DBInjector.provideNoteDao(context);
recentlyDeletedNote = notes.get(position);
recentlyDeletedNotePosition = position;
recentlyDeletedNote.setArchive(true);
notes.remove(position);
noteDAO.archiveNote(recentlyDeletedNote);
notifyItemRemoved(position);
}
I have tried a lot of solutions, the only reason why it is not updating the view is that on updating recycler views, views are getting updated especially when ItemTouchHelper is used. As I didn't have much choice I used
recreate()
function for refreshing the whole activity and the error went off.
PS: This is not the ideal solution, it is just workaround fix.
This is a difficult one, so I attached a picture to help visualize a bit.
So I have a recyclerview which contains a palette of colors. It loads the colors by creating a new ImageView for each color, and then changing the color and drawable of each imageview, having a different one for the "selected color". The selected color is also used to change the color of the toolbar from the parent DialogFragment.
The problem comes that the OnClicklistener hasn't worked at all after hours of research. Right now, I can't use an OnClickListener without the app crashing.
Now, lets get to the actual code and problems.
I'll declare (to you) some variables and classes I'm using:
ArrayList<String> colorsList = //arraylist which contains all the colors which can be displayed
NewNotebookFragment = //DialogFragment, contains a toolbar, miscellaneous views and a rvNewNotebook
RecyclerView rvNewNotebook = //the recyclerview containing imageviews representing each individual color retrieved from colorsList
NewNotebookAdapter = adapter which is bound to rvNewNotebook
Right now, what I've done is make a method in NewNotebookFragment called changeColor which can receive an int, representing the position of the color which was selected. changeColor() changes the color of the toolbar. changeColor() is called from an onClick method from an OnclickListener, defined in the Viewholder in NewNotebookAdapter.
Now comes the actual code.
newNotebookFragment:
public class NewNotebookFragment extends DialogFragment {
RecyclerView rvNewNotebook;
int activeColor;
ArrayList<String> colors=Helpers.getPossibleColors();
#Override
public View onCreateView ...}
#Override
public void onViewCreated(View v, Bundle savedInstanceState){
activeColor=new Random().nextInt(colors.size());
toolbar = (Toolbar) v.findViewById(R.id.newnotebooktoolbar);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
#Override
public boolean onMenuItemClick(MenuItem item) {
return false;
}
});
toolbar.inflateMenu(R.menu.newnotebook);
toolbar.setTitle("Create new Notebook");
if(Helpers.isColorDark(Color.parseColor(colors.get(activeColor)))){
toolbar.setTitleTextColor(getResources().getColor(R.color.md_dark_primary_text));
}else{
toolbar.setTitleTextColor(getResources().getColor(R.color.md_light_primary_text));
}
rvNewNotebook = (RecyclerView) v.findViewById(R.id.rvNewNotebook);
final GridLayoutManager rvNotebookManager = new GridLayoutManager(getContext(),6);
rvNewNotebook.setLayoutManager(rvNotebookManager);
NewNotebookAdapter adapter= new NewNotebookAdapter(getContext(), Helpers.getPossibleColors(), activeColor);
toolbar.setBackgroundColor(Color.parseColor(Helpers.getPossibleColors().get(activeColor)));
rvNewNotebook.setAdapter(adapter);
Log.d("onViewCreated", ""+rvNewNotebook.getWidth());
}
#Override public void onResume(){...}
public static NewNotebookFragment newInstance() {...}
public void changeColor(int position){
if(Helpers.isColorDark(Color.parseColor(colors.get(position)))){
toolbar.setTitleTextColor(getResources().getColor(R.color.md_dark_primary_text));
}else{
toolbar.setTitleTextColor(getResources().getColor(R.color.md_light_primary_text));
}
toolbar.setBackgroundColor(Color.parseColor(Helpers.getPossibleColors().get(position)));
}
NewNotebookAdapter:
public class NewNotebookAdapter extends RecyclerView.Adapter<NewNotebookAdapter.ViewHolder>{
Context context;
ArrayList<String> colors = new ArrayList<>();
int activeColor;
public NewNotebookAdapter(){
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public ImageView ivSwatch;
public Toolbar toolbar;
public ViewHolder(View itemView) {
super(itemView);
ivSwatch = (ImageView) itemView.findViewById(R.id.ivSwatch);
toolbar = (Toolbar) itemView.findViewById(R.id.newnotebooktoolbar);
ivSwatch.setOnClickListener(this);
}
#Override
public void onClick(View view) {
NewNotebookFragment newNotebookFragment = new NewNotebookFragment();
final int adapterPosition = ViewHolder.this.getAdapterPosition();
Log.d("OnClick. Adapter", "getAdapterPosition: "+adapterPosition);
newNotebookFragment.changeColor(adapterPosition);
}
}
public NewNotebookAdapter(Context context, ArrayList<String> colors, int activeColor){
this.context=context;
this.colors=colors;
this.activeColor=activeColor;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.colorcircle,parent,false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Drawable drawablefull = ContextCompat.getDrawable(context,R.drawable.colorcircle);
Drawable drawableHollow = ContextCompat.getDrawable(context,R.drawable.coloroutlinecircle);
if (position==activeColor){
holder.ivSwatch.setImageDrawable(drawablefull.mutate());
}else if (position!=activeColor){
holder.ivSwatch.setImageDrawable(drawableHollow.mutate());
}
holder.ivSwatch.setColorFilter(Color.parseColor(colors.get(position)));
The current problem I have is the following stack trace:
FATAL EXCEPTION: main
Process: com.twotowerstudios.virtualnotebookdesign, PID: 32272
java.lang.IllegalStateException: Fragment NewNotebookFragment{b10003e} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:648)
at com.twotowerstudios.virtualnotebookdesign.NewNotebookDialog.NewNotebookFragment.changeColor(NewNotebookFragment.java:85)
at com.twotowerstudios.virtualnotebookdesign.NewNotebookDialog.NewNotebookAdapter$ViewHolder.onClick(NewNotebookAdapter.java:49)
at android.view.View.performClick(View.java)
at android.view.View$PerformClick.run(View.java)
at android.os.Handler.handleCallback(Handler.java)
at android.os.Handler.dispatchMessage(Handler.java)
at android.os.Looper.loop(Looper.java)
at android.app.ActivityThread.main(ActivityThread.java)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java)
at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)
Now I'm almost positive this is related to my onClick method, which creates a new instance of NewNotebookFragment, causing the fragment I'm using in the onClick method to not actually be the same as the one which is already being used and displayed. HOWEVER, I'm making this thread because I have no real idea on how to solve the issue as a whole. I don't know how I could for example get the actual NewNotebookFragment from the onClick method so I could modify it or call methods from it. I don't know if once that
s fixed if it would even work or anything. How could I get this working?
Edit: I got it working perfectly. Basically what I did was define the setonclickListener inside the onBindViewHolder, on the Imageviews. The clickListener will call an interface called AdapterInterface, and use a method in it called clickListener, which allows me to pass the int of the color to the parent fragment. The fragment will implement that interface defined in the adapter, and it will override clickListener, using the color passed to it and from it invoking the changeColor method. If anyone in the future needs examples, feel free to contact me. The relevant changes are in these two files, uploaded to Github: https://gist.github.com/coldblade2000/0c2cac8b1af4df5985fe6cebebc9cff2 The links contain a certain level of explanation and documentation about the two classes
Looking at your code, you're creating a new Fragment every time it's clicked. I believe what you want is to change color of the dialog fragment toolbar. The first thing you can do is to create an interface listener for as a Callback to Fragment, which is passed into Adapter. Something like this
public interface ViewHolderOnClickListener {
//This method can be any parameters, I'm pasing color here since you need the color code
public void onViewHolderClick(View itemView, int position, int color);
}
And in your adapter, and viewholder Constructor methods, add an interface instance as your parameter. You can either directly pass it on creating Adapter or make your Fragment implements the interface and pass it with "this"
public TempAdapter(ViewHolderOnClickListener viewHolderOnClickListener) {
this.viewHolderOnClickListener = viewHolderOnClickListener;
}
And In your ViewHolder, do a callback with
#Override
public void onClick(View view) {
viewHolderOnClickListener.onViewHolderClick(*PARAMS HERE*);
}
That way, your fragment can listen to OnClick That is happening inside your ViewHolder, and respond accordingly.
Another way is to use EventBuses
I want to display many [CardViews] within a [RecyclerView].
Therefore I am using an adapter that extends
RecyclerView.Adapter < RecyclerView.ViewHolder >
Everything works fine so far.
But now, I want to populate the CardView with different widgets(checkboxes, textviews, buttons.. ) in different order, dependent on the data of the current dataset position. Defining a ViewHolder class for every possible combination of the widgets order is not an option due too many possibilities.
I managed it already with two RecyclerView-Adapters. One for the RecyclerView "list" and one for populating the CardView inside each item.
But that leads to bad performance.
So I came back to use only one RecyclerView and one Adapter.
But, how to populate each CardView in a good way then? Thank you for your help!
Update1:
This is my current and result updated_screenshot. Looks "fine" so far but is achieved not in a proper way.
I did it as following:
Setting an Adapter (OuterAdapter) to the recyclerView, that holds each CardView:
public class OuterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
.
.
.
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
rviewHolder = (RviewHolder) holder;
llm = new LinearLayoutManager(context);
rviewHolder.rv.setLayoutManager(llm);
innerAdapter = new VHAdapter(context, questionList);
rviewHolder.rv.setAdapter(innerAdapter);
}
.
.
.
}
Now, every Item has its own "VHAdapter" which fills the CardViews like:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder h, final int position) {
switch (h.getItemViewType()) {
case ONLY_HEADER_TEXT:
HeaderTextHolder holder = (HeaderTextHolder) h;
holder.tv.setText(q.getText());
break;
case ANSWER_TYPE_NUMBER:
NumberHolder nh = (NumberHolder) h;
nh.tv.setText(q.getText());
nh.et.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
tempAnswer.setNumber(Integer.parseInt(v.getText().toString()));
tempAnswer.setQuestionType(Question.ANSWER_TYPE_NUMBER);
return false;
}
});
break;
.
.
.
What I am trying to do now is getting rid the VHAdapter. But that means, I'd have to manually add widgets to ViewHolders rootLayout at OuterAdapter's onBindViewHolder.. Is that correct?
I have a simple adapter
public class ConversationListAdapter extends
RecyclerView.Adapter<Conversation.ViewHolder> {
private List<Conversation> items;
private Activity activity;
public ConversationListAdapter(Activity activity, List<Conversation> items) {
super();
this. items = items;
this.activity = activity;
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
Conversation conversation = mItems.get(i);
viewHolder.name.setText(conversation.getName());
if ( conversation.getUrl() != null) {
Picasso.with(activity.getApplicationContext()).load(conversation.getUrl())
.into(viewHolder.imageView);
}
}
and a basic
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {..}
}
Now in the fragment as always:
mRecyclerView.setAdapter(new ConversationAdapter(getActivity(), mItems));
Now Im calling my rest api to get the data and the first time it works great everything is where it should be (let's say in c there is only 2 items and the order is conv1 and conv2)
private void handleResult(List<Conversation> c) {
items.clear()
items.addAll(c)
adapter.notifyDataSetChanged()
}
But... now if I refresh for example and the data in the List comes in a different order (conv2 and then conv1) after the adapter.notifyDataSetChanged() both of my imageView in the list have the same pictures.. ! But the textView however has the right text
This only happens with view filled with Picasso and cannot understand why
Could someone help me on this ?
you use if condition in any adapter you also have to set else part of it. i also don't know exactly why this is happen that if condition is given it takes same condition for child which are not match this, may be a bug in android. please try else part of it also. may be this work for you.
You have to replace your items in your adapter or create a new adapter with the new items
1st solution:
private void handleResult(List<Conversation> c) {
mRecyclerView.setAdapter(new ConversationAdapter(getActivity(), c));
}
2nd solution:
private void handleResult(List<Conversation> c) {
adapter.setList(c);
adapter.notifyDataSetChanged();
}
And don't to forget to create setList(List<Conversation> c) method in your Adapter
I'm working on an Android application, and when I attempt to display large amounts of data in a listview, I receive the following error:
01-24 18:06:32.214:
INFO/System.out(9244):
EX:Observer android.widget.AdapterView$AdapterDataSetObserver#43c9b200
was not registered.
i got this error too.
in my case this exception (Observer * was not registered.) was thrown because i called
setAdapter(Adapter)
multiple times.
for example:
MyAdapter (extends BaseAdapter) vla;
ListView lv;
for (int i = 0; i < 2; i++) {
vla.clear();
vla.add(tv);
lv.setAdapter(vla);
}
The fix here is to place
v.setAdapter(vla);
outside the loop, thereby avoiding multiple calls.
after fix no more exception throws.
best regards, flood. (fldr atincircle mail dot ru)
I faced the same problem and it turned out to be a threading issue. Multiple threads were calling setAdapter which does not seem to be threadsafe. After making sure that all setAdapter calls are done on the same thread, the problem was solved.
What worked nicely for me, was to force my code to change it only on the UI thread:
runOnUiThread(new Runnable() {
#Override
public void run() {
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rc_view);
recyclerView.setAdapter(new MyAdapter(items));
}
});
I've ran into this problem multiple times when dealing with listviews. If you're using a custom adapter with an edit text involved, you'll want to do a clearFocus() before you refresh the adapter. Observer not registered is caused by the listview deleting a child that has focus on the activity
How I do it:
this is the view holder for my custom adapter
class viewHolder {
public viewHolder() {
views = new ArrayList<viewInfo>();
}
public int position;
public ArrayList<viewInfo> views;
public View view;
}
After which i invoke an unfocusAll() which consists of:
public void unFocusAll() {
viewHolder holder = (viewHolder) v.getTag();
if (holder != null) {
holder.view.clearFocus();
}
}
So before you invoke your listview you have to do
CustomAdapter aa = new CustomAdapter();
if (aa != null) {
aa.unFocusAll();
}
I had this problem today too and flood's answer helped me solving it. In my case the error would show up whenever I switched back to my application from another one, so the method setAdapter was allways called twice.
This helped me avoid it:
if ( pager.getAdapter() == null ){
pager.setAdapter(pagerAdapter);
}
Thanks!
Simply override in your adapter onGroupCollapsed.
Add to your adapter:
#Override
public void onGroupCollapsed(int groupPosition) {
}
at fragment which contains the Viewpager
#Override
public void onDestroyView() {
super.onDestroyView();
viewDestroyed = true;
}
then before you set the adapter check
if(viewDestroyed)
pager.setAdapter(adapter)
best regards