This problem has been beaten a million times, but unfortunately, no answer on SO has helped solve my issue.
Fundamentally, my question is that, in a RecyclerView with a CardView in a Fragment, when and where should the following tasks be performed:
Fetching data from a server to the local database on the user's device
Processing the so-fetched local data
Instantiating and setting the RecyclerView's adapter
Calling the RecyclerView's notifyfDataSetChanged()
The RecyclerView in my implementation presently calls back only getItemCount(). onCreateViewHolder() and onBindViewHolder() are never getting called back.
(I have seen this answer and a gazillion more.)
Here is the sequence of how its being done:
An AppCompatActivity instantiates a Fragment
The Fragment's layout contains a RelativeLayout with a toolbar and an include tag for a ViewPager. The ViewPager layout contains another include tag that sets a layout with a RecyclerView in a LinearLayout. The layout XML with the CardView is set in the RecyclerView's adapter:
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
Log.wtf(LOG_TAG, "Inside onCreateViewHolder()");
View myView;
LayoutInflater inflater;
inflater = LayoutInflater.from(parent.getContext());
myItemView = inflater.inflate(R.layout.my_row_layout, parent, false);
MyViewHolder myViewHolderItem = new MyViewHolder(myItemView);
return myViewHolderItem;
}
There's no ScrollView in any layout
The Fragment's onCreate() calls a method to fetch data from a server
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Some cache processing
args = getArguments();
myDBHelper = MyDBHelper.getInstance(getActivity());
Intent intent = getActivity().getIntent();
// Search comes from search intent
if (intent.getBooleanExtra(MyContract.MY_SEARCH_REQUEST, false))
{
...
getDataFromCloud("", true);
getDataFromLocalDB(false, item);
} // of if search intent
// Plain intent
else
{
getDataFromCloud("item", false);
getDataFromLocalDB(true, item);
}
} // of onCreate()
This data gets populated into a local database on the user's device
Another method in the Fragment's onCreate()filters the local data into an ArrayList
This ArrayList is presented in the RecyclerView - CardView combo finally to the user.
notifyDataSetChanged() is called in the Fragment's onActivityCreated(). But the fact is, its not making a difference anywhere it is called from. Also, the adapter is instantiated in the onPostExecute() of the AsyncTask that creates the ArrayList for the CardView from the local database.
Here's the log that results on running the app:
01-03 08:47:34.546 13489-14033/com.my A/MyDBHelper class: Create View-1 query: CREATE VIEW
01-03 08:47:34.562 13489-13489/com.my A/MyFragment: getItemCount: 0
01-03 08:47:34.584 13489-14033/com.my A/MyDBHelper class: Create View-2 query: CREATE TABLE
01-03 08:47:34.638 13489-13489/com.my A/MyFragment: getItemCount: 0
01-03 08:47:34.722 13489-13489/com.my A/MyFragment: getItemCount: 2
01-03 08:47:34.743 13489-13489/com.my A/MyFragment: getItemCount: 2
In the above log, the View-1 query fetches server data and the View-2 query processes the so-fetched local data.
I have log statements in the onCreateViewHolder() and onBindViewHolder(). As evident above, these methods are just not being invoked, despite a positive return from getItemCount(), which is the size of the ArrayList. The app just shows the toolbar, and an empty white card, despite there being two records or items to be shown.
Been staring at these logs for the past couple of days with no progress or clues. Many thanks in advance for your expert advice or pointers (and for saving what's left of my sanity)!
Finally, it was Jared Burrows's advice that drove the last nail in the coffin. BoyOboy, the only way to get it right is fight! (...almost always!)
Though I cannot pin-point what was going wrong, here's what I re-did:
Created a fresh new project with only the RecyclerView - CardView combo in a Fragment. Well worth the time and effort, in terms of the clarity it brought in.
Read-up this very good resource.
Switched back to the ListView implementation of my app, first got the CardView right, into the ListView
With a fresh new understanding of RecyclerView, systematically replaced all the ListView code with the RecyclerView counterpart
In the app, the custom RecyclerAdapter and ViewHolder live in their own, independent classes now
Was stuck for another couple of hours with the "RecyclerView: No Adapter attached; skipping layout” error; nothing was showing up in the Fragment. That's when Jared Burrows's advice pitched in.
The RecyclerView is now looking all hunky dory, waiting to throw up the next challenge: animation!
Related
I am trying to update the data in 'onresume' when I come back to my main activity from different activities but its photo is not updating without scrolling the list.
they don't work;
detailRecyclerAdapter.notifyDataSetChanged();
detailRecyclerView.invalidate();
You have to update the recycler view by passing updated data into the adapter by creating a setter method into the adapter.
fun setResultItem(arrResultItem: java.util.ArrayList<ResultsItem>) {
arrayList = ArrayList()
arrayList.addAll(arrResultItem)
notifyDataSetChanged()
}
Now, please update recycler view from activity by,
adapter.setResultItem(updatedList)
In my activity I try to call a method from a class but it's not getting the right values.
This is ActivityTwo:
int id = intent.getIntExtra("id", 0);
LayoutInflater inflater = getLayoutInflater();
View mainActivity = inflater.inflate(R.layout.activity_main, null);
LinearLayout eventsLayout = mainActivity.findViewById(R.id.eventsLayout);
Log.d("ACTIVITY_eventlayout", String.valueOf(eventsLayout)); // gives the layout perfectly
Event.RemoveSpecific(eventsLayout, id);
finish();
This is the class(Event) with the method:
public static void RemoveSpecific(LinearLayout layout, int id){
View event = layout.findViewById(id);
Log.d("INSIDE_removespecific", String.valueOf(event));// event is null
layout.removeView(event);
}
And in my MainActivity it's working fine:
LinearLayout eventsLayout = findViewById(R.id.eventsLayout);
View event = eventsLayout.findViewById(id);
//Log.d("MAIN_event", String.valueOf(event1)); // gives it perfectly
eventsLayout.removeView(event);
I also add this view in my MainActivity and use .setId(id) to give it the right id. So my question is, why is the View in my class method null, while I pass the right LinearLayout from my activityTwo?
Where does the id come from?
I have the Event class which contains an id, name, date, & description. This class also has a static ArrayList called eventsList. Whenever the user creates a new reminder I create a new Event using my class and giving it an id as Event.eventsList().size() (after adding it to my eventsList [so the first event id is always 1]), then I create a new View and pass the recently created Event details and use setId() to give it an id [1]. Then in this View I have a button which has an onClickListener which passes the given View id and the LinearLayout (where the View was added) to my Event.removeSpecific() method.
App flow:
The user clicks a button in MainActivity to create an event, then the button click then use startActivityForResult() with an intent to open a new Activity where the user puts in a name,description and date for the event, then I create an intent with putExtra() methods and then use setResult(RESULT_OK, resultintent) to send the data back to MainActivity. Now when the MainActivity gets the data back I use .getStringExtra() to create a new Event object with the data and add it to may Event.eventsList ArrayList, then I use the following:
LayoutInflater inflater = getLayoutInflater();
View event_Exmaple = inflater.inflate(R.layout.event_example, null);
and set the textView's with the data I got, and use event_example.setId(Event.eventsList.size()) to give it an id.
After that I add this to my LinearLayout (which I declared already in the MainActivity):
eventsLayout.addView(event_example)
Now I mentioned that the Event class has a date field. I use that to set up an alarm with AlarmManager. With the AlarmManager I send the current Event object's data through the intent and when the user gets the notification it opens up a new Activity which gets the given Event object data (with intent.putExtra()'s) and that's the part where the user clicks on a Button. I want to remove the given Event object's View from my LinearLayout in the MainActivity XML.
well since you didn't share where the id value come from (from where the intent gets its data/how you put the values in it/where the values come from), I assumed this solution :
whenever I use a 'switch' statement with View Ids it tells me a very important warning which is
Resource IDs will be non-final in Android Gradle Plugin version 5.0, avoid using them in switch case statements
that means that view Ids doesn't have a stable id in some cases that you shouldn't consider them to be final.
I guess in your case when you get an id to your event view and then inflate the layout view, the inflate procedure involve changing the 'int' id value of the event view so whenever you query the old id it returns null since the event view inside the inflated layout view now has a new 'int' id value.
I don't know why it works in main activity since you didn't share full code where you get the id from.
so I guess the only solution is that you should find a way to get a fresh copy of the id after you inflate the view you want to work on in the method.
Update
the problem is with this block of code
View mainActivity = inflater.inflate(R.layout.activity_main, null);
everytime you inflate a new layout view thinking that you have the one used by main activity back there but instead you're creating an entirely new one with new views and new Id's.
that would be why you could get the view in main activity but you can't get it from a newly inflated view.
Update
if you need to remove this item using the id and have a static array stored in the view custom class, call a getter on the static array and remove the item from it instead of trying to remove the view itself.
a better implementation of the whole situation is to store these events in a database using room instead of the static array, that way you could delete/add/edit any event anytime with just one line of code, if you're willing to do this you can start by reading the developers documentation of room.
This is my first Android project. I am working on an app for tracking courses in a degree program (this is the project for a course I'm taking). I have set a RecyclerView inside a fragment. At one point the loaded items were not displayed anymore. It was working previously. I only made some changes to other parts of the app, but none to this part. I see no reason for these items to not be showing. I feel like the problem started after I clicked "Install" on an emulator update, which Android Studio suggested. I suspect the update is causing the issue, but I don't know for certain.
The code snippet below is from my fragment. I have downloaded the SQLite database and confirmed that the course.getTermId() matches selectectTerm.getTermId() for 4 items. When I open the fragment, I see a toast message that says "4 Courses loaded", so I know the issue is not with my logic here. I have tried inserting both adapter.notifyDataSetChanged(); and recyclerView.invalidate(); on the blank line right before the toast, but the items still do not show up. I have checked my layout to ensure the RecyclerView is properly sized and anchored. There's clearly something I am missing, but I am simply not seeing it. I have also confirmed that my CourseFlatAdapter does not have any logic errors. No exceptions are printed in Logcat. Does anyone see anything I need to do to fix this?
RecyclerView recyclerView = getView().findViewById(R.id.recyclerCourses);
recyclerView.setLayoutManager(new LinearLayoutManager(this.getContext()));
recyclerView.setHasFixedSize(true);
CourseFlatAdapter adapter = new CourseFlatAdapter();
recyclerView.setAdapter(adapter);
courseViewModel = ViewModelProviders.of(this).get(CourseViewModel.class);
courseViewModel.getAllCourses().observe(this, new Observer<List<Course>>() {
#Override
public void onChanged(List<Course> courses) {
ArrayList<Course> filteredCourseList = new ArrayList<>();
for(Course course : courses){
if(course.getTermId() == selectectTerm.getTermId()){
filteredCourseList.add(course);
}
}
adapter.setCourses(filteredCourseList);
Toast.makeText(ViewTermFragment.this.getContext(), filteredCourseList.size() + " Courses loaded", Toast.LENGTH_SHORT).show();
}
});
I figured it out. One of the changes I had made was to put the fragment container in a ScrollView so the whole thing would be scrollable when there is a large amount of information. The RecyclerView was anchored to the bottom of the fragment and had the height set to MatchParent while the fragment's height was set to WrapContent so the RecyclerView ended having it's height calculated as 0 regardless of how many items it was displaying.
I changed the RecyclerView's height to WrapContent and removed the bottom anchor. It now works as expected. Thank you to everyone who may have spent time on this.
I have a Fragment that shows different view by a AdapterViewFlipper.
The AdapterViewFlipper is being set with MyCustomAdapter that contains 'View 1', 'View 2', 'View 3', and 'View 4', and its in a layout resource file that I inflated in my own Fragment "onCreateView()".
The Problem I'm facing is whenever I rotate my device, the current view in the AdapterViewFlipper goes back to the first view that was added in MyCustomAdapter.
For Example: if the current view in the AdapterViewFlipper is showing 'View 2' and the user rotates the device, it returns back to 'View 1'.
So what I'm trying to do is to restore the current view in the AdapterViewFlipper and its state in the Fragment whenever I rotate my device.
Although I found this method that says I should declare the android:configChanges attribute at the element in the AndroidManifest and it worked like a charm but when I was reading about it Android didn't recommend it.
But this works fine in Activity.
So is there a way I can go through this myself?
So the first thing you need to do, is to make sure you are retaining the fragment itself. And not place a new instance every time your activity is re-created.
You can establish that with a simple check in your onCreate() method.
You can either check if the savedInstance Bundle parameter to onCreate() is null, in that case only you need to replace your fragment OR check if your fragment is already added to your FragmentManager.
if (savedInstanceState == null) {
// This is a brand new activity, and not a re-creation due to config change
getSupportFragmentManager().beginTransaction().replace(id, yourFragmentInstnace, stringTag);
}
OR
if (getSupportFragmentManager().findFragmentByTag(fragmentTag) == null) {
// This is a brand new activity, and not a re-creation due to config change
getSupportFragmentManager().beginTransaction().replace(id, yourFragmentInstnace, fragmentTag);
}
And you also need to call setRetainInstance(true) in your fragment's onCreate() or something.
That will retain the same instance of your fragment during a configuration change.
This should automatically allow your AdapterViewFlipper to maintain its UI state, which is the current item it's showing.
You can find a nice example here
I am using Android data binding in a MVVM framework. I have a ViewPager setup with a corresponding PagerAdapter. Some of the pages may contain videos. The issue that I am having is that the instantiateItem method in the PagerAdapter always executes the next view in the PagerAdapter instead of the current view. For example say page 1 has no video, but page 2 does. When the user views page 1, the video in page 2 starts playing. Here is the instantiateItem method:
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater layoutInflater = (LayoutInflater) getApplicationContext().getSystemService(Service.LAYOUT_INFLATER_SERVICE);
ViewDataBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.main_layout, null, false);
Post post = posts.get(position);
myViewModel = new PostModel(post);
binding.setVariable(BR.model, myViewModel);
container.addView(binding.getRoot());
return binding.getRoot();
}
How can I make the instantiateItem execute the current view instead of the next view? Is the issue related to setOffscreenPageLimit?
You can't because it's main idea of ViewPager - it preload next N pages in order to make swiping smooth. Also actually you can setup it in order to see part of next view.
I think you should use OnPageChangeListener, and manager start/stop of playing of your video when it needed here.
Btw, some advices - you can use that library in order to not write that boilerplate code at all. Here my example of usage.