I have a Fragment that contains a RecyclerView to display events for a given day. I am using a ViewPager to separate the Fragments into multiple days; A Fragment for Saturday's events and a Fragment for Sunday's events.
However, it appears that both Fragments are referencing the same RecyclerView and/or Adapter, as it is only the last tab (in this case, Sunday) whose events are shown.
In my specific case, Saturday has two events, and Sunday has no events. Both Fragments have empty RecyclerViews. To confirm my theory that it was caused by the last tab, I switched the date. This caused both RecyclerViews to have two events (the ones from Saturday).
Here is the relevant code for the individual Fragments:
public class EventListFragment extends Fragment{
private EventAdapter mEventAdapter;
private static final String DATE_ARG = "eventDate";
public static EventListFragment newInstance(LocalDate date){
EventListFragment eventListFragment = new EventListFragment();
Bundle args = new Bundle();
args.putSerializable(DATE_ARG, date);
eventListFragment.setArguments(args);
return eventListFragment;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_event_list, container, false);
// Setup recyclerview
RecyclerView eventRecyclerView = (RecyclerView) view.findViewById(R.id.event_recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
eventRecyclerView.setLayoutManager(layoutManager);
// Get date
LocalDate eventDate = (LocalDate) getArguments().getSerializable(DATE_ARG);
// Set adapter
mEventAdapter = new EventAdapter(getActivity(), getEvents(eventDate));
eventRecyclerView.setAdapter(mEventAdapter);
return view;
}
}
getEvents() is just a private function to return events for a given date. I have used the debugger as well as unit tests to verify that it works properly. The debugger shows that it pulls the proper list for each Fragment, but as I explained they are not displayed properly.
Here is the relevant code for the parent Fragment:
public class EventFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_event, container, false);
// Get and set up viewpager
final ViewPager viewPager = (ViewPager) view.findViewById(R.id.event_view_pager);
EventFragmentAdapter eventFragmentAdapter = new EventFragmentAdapter(getFragmentManager(), getEventDates());
viewPager.setAdapter(eventFragmentAdapter);
// Get and set up tablayout
final TabLayout tabLayout = (TabLayout) view.findViewById(R.id.event_tabs);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.post(new Runnable() {
#Override
public void run() {
tabLayout.setupWithViewPager(viewPager);
}
});
return view;
}
}
Similar to the last one, getEventDates() just pulls the dates that events are taking place. For testing purposes at the moment, I am hard coding a returned list of dates as we don't have our database set up yet. I pulled this method out because I want the app to be able to function again in 2016, which may have different dates:
private List<LocalDate> getEventDates(){
List<LocalDate> eventDates = new ArrayList<>();
eventDates.add(new LocalDate(2015, 10, 17));
eventDates.add(new LocalDate(2015, 10, 18));
return eventDates;
}
The last bit of relevant code is for the FragmentStatePagerAdapter I am using for my ViewPager:
public class EventFragmentAdapter extends FragmentStatePagerAdapter {
private List<LocalDate> mEventDates;
public EventFragmentAdapter(FragmentManager fragmentManager, List<LocalDate> eventDates){
super(fragmentManager);
this.mEventDates = eventDates;
}
#Override
public Fragment getItem(int i) {
return EventListFragment.newInstance(mEventDates.get(i));
}
#Override
public int getCount() {
return mEventDates.size();
}
#Override
public CharSequence getPageTitle(int position) {
return mEventDates.get(position).dayOfWeek().getAsText();
}
}
Any ideas why both lists are always the same, and are based on the last tab in the ViewPager? I assume that somehow they are referencing the same RecyclerView or the same RecyclerViewAdapter, but I don't have any static fields for those so I am not sure how it is happening.
A long hunt and an anti-climactic solution(as with most difficult bugs). Also a bit unfair since the bug isn't in the code posted above, I had to hunt down your git project to figure it out. The bug is in EventAdapter:
public class EventAdapter extends RecyclerView.Adapter<EventAdapter.ViewHolder> {
private static final List<Event> mEvents;
private final Context mContext;
public EventAdapter(Context context, List<Event> events){
this.mContext = context;
mEvents = events;
}
...
}
mEvents is static!... so it's shared across all instances of mEvents. This explains the bug perfectly since updates to it last will set the values for all EventAdapters.
It looks like you made mEvents static so that you could access it within your ViewHolders. Instead you can just store the individual Event within the ViewHolder and drop the dangerous static modifier. On the flip-side, hooray for Open Source Projects!
I've seen your code and I totally agree with Travor - you're using a static member, that is replaced every time you create a new Adapter (and so it gets just the last page data). I've modified your project a little bit, to make it work properly. Take a look at it, hope it can be useful.
EventFragment: replace getFragmentManager with getChildFragmentManager since you need EventListFragment to be handle by EventFragment fragment manager and not by the activity fragment manager
public class EventFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_event, container, false);
// Get and set up viewpager
final ViewPager viewPager = (ViewPager) view.findViewById(R.id.event_view_pager);
EventFragmentAdapter eventFragmentAdapter = new EventFragmentAdapter(getChildFragmentManager(), getEventDates());
viewPager.setAdapter(eventFragmentAdapter);
// Get and set up tablayout
final TabLayout tabLayout = (TabLayout) view.findViewById(R.id.event_tabs);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.post(new Runnable() {
#Override
public void run() {
tabLayout.setupWithViewPager(viewPager);
}
});
return view;
}
/**
* Retrieves the event dates for the hackathon so that the proper events can be displayed.
* #return
*/
private List<LocalDate> getEventDates(){
List<LocalDate> eventDates = new ArrayList<>();
eventDates.add(new LocalDate(2015, 10, 17));
eventDates.add(new LocalDate(2015, 10, 18));
return eventDates;
}
}
EventListFragment - I've modified the query, since the sqlite query doesn't work with my locale (italian)
public class EventListFragment extends Fragment{
private EventAdapter mEventAdapter;
private static final String TAG = EventListFragment.class.getSimpleName();
private static final String DATE_ARG = "eventDate";
public static EventListFragment newInstance(LocalDate date){
EventListFragment eventListFragment = new EventListFragment();
Bundle args = new Bundle();
args.putSerializable(DATE_ARG, date);
eventListFragment.setArguments(args);
return eventListFragment;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_event_list, container, false);
// Setup recyclerview
RecyclerView eventRecyclerView = (RecyclerView) view.findViewById(R.id.event_recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
eventRecyclerView.setLayoutManager(layoutManager);
// Get date
LocalDate eventDate = (LocalDate) getArguments().getSerializable(DATE_ARG);
// Set adapter
mEventAdapter = new EventAdapter(getEvents(eventDate));
eventRecyclerView.setAdapter(mEventAdapter);
Log.v(TAG, eventRecyclerView.toString());
return view;
}
/**
* Retrieves the events for the given date for the fragment.
*/
private List<Event> getEvents(LocalDate date){
List<Event> returnList = new ArrayList<>();
String dateString = Utility.getDBDateString(date);
List<String> dateList = new ArrayList<>();
Cursor cursor = getActivity().getContentResolver().query(
GHContract.EventEntry.CONTENT_URI,
new String[]{ "*", "substr(" + GHContract.EventEntry.COLUMN_TIME + ",0,11)" },
"substr(" + GHContract.EventEntry.COLUMN_TIME + ",0,11) = ? ",
new String[]{dateString},
GHContract.EventEntry.COLUMN_TIME
);
while(cursor.moveToNext()){
returnList.add(new Event(cursor));
dateList.add(cursor.getString(cursor.getColumnIndex(GHContract.EventEntry.COLUMN_TIME)));
}
cursor.close();
return returnList;
}
}
EventAdapter - removed the static keyword and the reference to the activity context (you need to get the context somewhere else)
public class EventAdapter extends RecyclerView.Adapter<EventAdapter.ViewHolder> {
private List<Event> mEvents;
public EventAdapter(List<Event> events){
mEvents = events;
}
/**
* Inflates the view for Event items.
*/
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_event, parent, false);
return new ViewHolder(view);
}
/**
* Binds the data for an event to its view.
*/
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Event event = mEvents.get(position);
holder.timeView.setText(Utility.getTimeString(event.getTime()));
holder.titleView.setText(event.getTitle());
holder.locationView.setText(event.getLocation());
// If reminder time is not null, show check mark. If it is, show plus.
if(event.getReminderTime() != null){
holder.alarmView.setImageDrawable(holder.itemView.getResources().getDrawable(R.drawable.ic_alarm_on));
} else{
holder.alarmView.setImageDrawable(holder.itemView.getResources().getDrawable(R.drawable.ic_add_alarm));
}
}
/**
* Returns the size of the adapter.
*/
#Override
public int getItemCount() {
return mEvents.size();
}
/**
* Retains a reference to the view so `findViewById` calls are only made once for the adapter.
*/
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public final TextView timeView;
public final TextView titleView;
public final TextView locationView;
public final ImageView alarmView;
public ViewHolder(View view){
super(view);
timeView = (TextView) view.findViewById(R.id.event_time);
titleView = (TextView) view.findViewById(R.id.event_title);
locationView = (TextView) view.findViewById(R.id.event_location);
alarmView = (ImageView) view.findViewById(R.id.event_add_reminder);
alarmView.setOnClickListener(this);
}
/**
* Handles the click a user makes on the alarm image view.
*/
#Override
public void onClick(View v) {
OnEventReminderClickListener activity = (OnEventReminderClickListener) v.getContext();
activity.onEventReminderClicked(mEvents.get(getPosition()));
}
}
/**
* Interface to call back to the activity when an alarm is clicked for an event item.
*/
public interface OnEventReminderClickListener{
void onEventReminderClicked(Event event);
}
}
And finally the app/build.gradle, since you need to get the same version for all support libraries (recycler, card and so on)
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.adammcneilly.grizzhacks"
minSdkVersion 15
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'joda-time:joda-time:2.7'
compile 'com.android.support:recyclerview-v7:22.2.1'
compile 'com.android.support:cardview-v7:22.2.1'
compile 'com.android.support:design:22.2.1'
}
Could you try this?
public class EventFragmentAdapter extends FragmentStatePagerAdapter {
private List<LocalDate> mEventDates;
private List<EventListFragment> mFragments;
public EventFragmentAdapter(FragmentManager fragmentManager, List<LocalDate> eventDates){
super(fragmentManager);
this.mEventDates = eventDates;
this.mFragments = new ArrayList<>;
for (LocalDate date : this.mEventDates) {
this.mFragments.add(EventListFragment.newInstance(date));
}
}
#Override
public Fragment getItem(int i) {
return mFragments.get(i);
}
#Override
public int getCount() {
return mEventDates.size();
}
#Override
public CharSequence getPageTitle(int position) {
return mEventDates.get(position).dayOfWeek().getAsText();
}
}
Then you check into each item of mFragents if they have the expected content.
Related
Hey guys I really need your help. I've spent like 5 days trying to get my recyclerview to update only when the user presses OK on a dialogbox, that appears from the menu actionbar. I've tried every possible method I could think of, every method I've seen on stackoverflow, YouTube, etc. and none of them worked for me.
How do I get the recyclerview in a fragment to update after dialogbox closes? I know there are similar questions regarding updating the menu, and (recyclerviews with dialogfragments), but none of them have a combination.
Out of the countless attempts, the current code configuration posted below isn't causing any errors, however, the recyclerview remains blank. The closest attempt I had to finding a solution, was creating an adapter and setting up the recycler in onOptionsItemSelected. But obviously, it updates only when the user clicks the button, and the initial click would create a blank recyclerview.
Fragment:
(There's a lot of repeated commented code from different attempts)
public class ExerciseRoutine extends Fragment implements ExerciseRoutine_Dialog.RoutineDialogListener{
private String Routine_name, Routine_split;
private ArrayList<ExerciseRoutine_Information> Routine_information = new ArrayList<>();
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
private RecyclerView.LayoutManager layoutManager;
#Override
public void sendInput(String name, String split, RecyclerView.Adapter DialogAdapter) {
Routine_name = name;
Routine_split = split;
adapter = DialogAdapter;
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.exercise_routine_fragment, container, false);
//Report that this fragment would like to participate in populating the options menu by
//receiving a call to onCreateOptionsMenu(Menu, MenuInflater) and related methods.
//If true, the fragment has menu items to contribute.
setHasOptionsMenu(true);
recyclerView = view.findViewById(R.id.ExerciseRoutine_Recycler);
//BuildRecyclerView();
//recyclerView.setHasFixedSize(true); //If the Recyclerview is static
/*Routine_information.add(new ExerciseRoutine_Information(Routine_name, Routine_split));
recyclerView = view.findViewById(R.id.ExerciseRoutine_Recycler);
//recyclerView.setHasFixedSize(true); //If the Recyclerview is static
layoutManager = new LinearLayoutManager(getActivity());
adapter = new ExerciseRoutineAdapter(Routine_information);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);*/
return view;
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.exercise_routine_menu, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case R.id.action_addRoutine:
ExerciseRoutine_Dialog routineDialog = new ExerciseRoutine_Dialog();
routineDialog.setTargetFragment(ExerciseRoutine.this, 1);
routineDialog.show(getFragmentManager(), "Routine Dialog");
//Routine_information.add(new ExerciseRoutine_Information(Routine_name, Routine_split));
BuildRecyclerView();
//adapter.notifyItemInserted(Routine_information.size());
//if(!Routine_name.equals("") && !Routine_split.equals("")) {
//}
}
return super.onOptionsItemSelected(item);
}
public void BuildRecyclerView(){
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
}
public void BuildAdapter(){
//adapter = new ExerciseRoutineAdapter(getContext(),Routine_information);
adapter.notifyItemInserted(Routine_information.size());
}
}
My Dialog Fragment:
public class ExerciseRoutine_Dialog extends DialogFragment{
private TextView ActionOK, ActionCANCEL;
private EditText Routine_name, Routine_split;
private RoutineDialogListener activityCommander;
private ArrayList<ExerciseRoutine_Information> Routine_information = new ArrayList<>();
private RecyclerView.Adapter adapter;
public interface RoutineDialogListener{
void sendInput(String name, String split, RecyclerView.Adapter DialogAdapter);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try{
activityCommander = (RoutineDialogListener) getTargetFragment();
}catch(ClassCastException e){
throw new ClassCastException(context.toString() + "Must Implement RoutineDialogListener");
}
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.exercise_routine_dialog, container, false);
Routine_name = view.findViewById(R.id.ExerciseRoutine_DialogNameInput);
Routine_split = view.findViewById(R.id.ExerciseRoutine_DialogSplitInput);
ActionOK = view.findViewById(R.id.ExerciseRoutine_DialogAction_OK);
ActionCANCEL = view.findViewById(R.id.ExerciseRoutine_DialogAction_CANCEL);
//recyclerView = view.findViewById(R.id.ExerciseRoutine_Recycler);
ActionCANCEL.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getDialog().dismiss();
}
});
ActionOK.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String name = Routine_name.getText().toString();
String split = Routine_split.getText().toString();
if(!name.equals("") && !split.equals("")) {
Routine_information.add(new ExerciseRoutine_Information(name, split));
adapter = new ExerciseRoutineAdapter(getContext(), Routine_information);
activityCommander.sendInput(name, split, adapter);
adapter.notifyItemInserted(Routine_information.size());
}
getDialog().dismiss();
}
});
return view;
}
}
Your current approach seems to be to pass the RecyclerView.Adapter to the DialogFragment and try to insert the new data on-the-spot. I think this is a problematic setup. The dialog's purpose is to offer some means for the users to enter the required data, period. It should not be tasked with the job of managing the RecyclerView or its Adapter because that way your components will be very tightly coupled:
Imagine that first you use a ListView in your implementation, and suddenly someone decides to ban every ListView from your app (maybe for performance reasons) and has you exchange them all for RecyclerViews. Then your approach would force you to change the code for the DialogFragment (it would have to cater to a different type of Adapter). A more loosely coupled implementation would enable you to make changes to one component without having to rewrite too many others.
That's why I won't try to make your code work as-is, instead I'd like to show you another way:
Because the RecyclerView is part of the Fragment's UI, the Fragment is the place where code related to managing the RecyclerView belongs. It is basically possible to have the Adapter as an inner class of the Fragment but I prefer having it as a standalone class if the code gets a little longer, and also because it enforces "decoupling".
Interfaces play a very important part in good architecture, so the DialogFragment will still make use of an interface to send its data. But it's up to the class which actually implements the interface (here: the Fragment) to pass the data to any interested third parties, e.g. the RecyclerView.Adapter (The Adapter in turn could have its own interface to publish important events like clicks on list items).
Having said that, here are some code snippets:
The DialogFragment
public class ExerciseRoutine_Dialog extends DialogFragment {
private EditText Routine_name, Routine_split;
public interface RoutineDialogListener{
/**
* There is some new ExerciseRoutine_Information
*/
void sendInput(String name, String split);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.exercise_routine_dialog, container, false);
Routine_name = view.findViewById(R.id.ExerciseRoutine_DialogNameInput);
Routine_split = view.findViewById(R.id.ExerciseRoutine_DialogSplitInput);
TextView actionOK = view.findViewById(R.id.ExerciseRoutine_DialogAction_OK);
TextView actionCANCEL = view.findViewById(R.id.ExerciseRoutine_DialogAction_CANCEL);
actionCANCEL.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getDialog().dismiss();
}
});
actionOK.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String name = Routine_name.getText().toString();
String split = Routine_split.getText().toString();
if(!name.equals("") && !split.equals("")) {
// just send the input to the main Fragment
RoutineDialogListener listener = getListener();
if(listener != null) {
listener.sendInput(name, split);
}
}
getDialog().dismiss();
}
});
return view;
}
/**
* Tries to find a suitable listener, examining first the hosting Fragment (if any) and then the Activity.
* Will return null if this fails
* #return x
*/
private RoutineDialogListener getListener(){
RoutineDialogListener listener;
try{
Fragment onInputSelected_Fragment = getTargetFragment();
if (onInputSelected_Fragment != null){
listener = (RoutineDialogListener) onInputSelected_Fragment;
}
else {
Activity onInputSelected_Activity = getActivity();
listener = (RoutineDialogListener) onInputSelected_Activity;
}
return listener;
}catch(ClassCastException e){
Log.e("Custom Dialog", "onAttach: ClassCastException: " + e.getMessage());
}
return null;
}
}
The Fragment:
public class ExerciseRoutine extends Fragment implements ExerciseRoutine_Dialog.RoutineDialogListener{
public static final String ROUTINE_DIALOG = "Routine Dialog";
private ArrayList<ExerciseRoutine_Information> routineInformations = new ArrayList<>();
private RecyclerView.Adapter adapter;
public static ExerciseRoutine instance(){
return new ExerciseRoutine();
}
#Override
public void sendInput(String name, String split) {
routineInformations.add(new ExerciseRoutine_Information(name, split));
adapter.notifyItemInserted(routineInformations.size());
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.exercise_routine_fragment, container, false);
setHasOptionsMenu(true);
RecyclerView recyclerView = view.findViewById(R.id.ExerciseRoutine_Recycler);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
adapter = new ExerciseRoutineAdapter(getContext(), routineInformations);
// So far you have a RecyclerView with an empty List.
recyclerView.setAdapter(adapter);
return view;
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.exercise_routine_menu, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case R.id.action_addRoutine:
showDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showDialog(){
ExerciseRoutine_Dialog routineDialog = new ExerciseRoutine_Dialog();
routineDialog.setTargetFragment(ExerciseRoutine.this, 1);
routineDialog.show(getFragmentManager(), ROUTINE_DIALOG);
}
}
I'm trying to pass a custom ArrayList from an activity to a fragment but don't understand what's wrong with the code. There's no error but even them I'm unable to get the expected output.
And I'm completely lost after trying hard to fix it for almost 3 days(Yes, but I'm just a beginner). Please help me fix it out. The purpose is that I want to use the fragment for another activity.
Here's my Activity Class:
public class CityDetailsActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create a list of City Details
ArrayList<CityDetails> cityDetail = new ArrayList<CityDetails>();
cityDetail.add(new CityDetails("information", "near", "local food", R.drawable.img1, R.drawable.img1, R.drawable.img6));
Bundle args = new Bundle();
args.putParcelableArrayList("key", cityDetail);
CityInformationFragment myFragment = new CityInformationFragment();
myFragment.setArguments(args);
// Set the content of the activity to use the activity_main.xml layout file
setContentView(R.layout.fragment_main);
ViewPager vpPager = (ViewPager) findViewById(R.id.vpPager);
FragmentPagerAdapter adapterViewPager;
adapterViewPager = new CityDetailsPagerAdapter(getSupportFragmentManager());
vpPager.setAdapter(adapterViewPager);
// Give the TabLayout the ViewPager
TabLayout tabLayout = (TabLayout) findViewById(R.id.sliding_tabs);
tabLayout.setupWithViewPager(vpPager);
}
}
And my Custom Parcelable Class is as under:
public class CityDetails implements Parcelable {
private String mInformation;
private String mNearPlace;
private String mLocalFood;
private int mItemImageResourceId;
private int mNear_place_image_ResourceId;
private int mLocal_food_image_ResourceId;
public CityDetails(String information, String nearPlace, String localFood, int itemImageResourceId
, int near_place_image_ResourceId, int local_food_image_ResourceId) {
mInformation = information;
mNearPlace = nearPlace;
mLocalFood = localFood;
mItemImageResourceId = itemImageResourceId;
mNear_place_image_ResourceId = near_place_image_ResourceId;
mLocal_food_image_ResourceId = local_food_image_ResourceId;
}
#Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(mInformation);
out.writeString(mNearPlace);
out.writeString(mLocalFood);
out.writeInt(mItemImageResourceId);
out.writeInt(mNear_place_image_ResourceId);
out.writeInt(mLocal_food_image_ResourceId);
}
private CityDetails(Parcel in) {
mItemImageResourceId = in.readInt();
mNear_place_image_ResourceId = in.readInt();
mLocal_food_image_ResourceId = in.readInt();
mNearPlace = in.readString();
mLocalFood = in.readString();
mInformation = in.readString();
}
#Override
public int describeContents() {
return 0;
}
// After implementing the `Parcelable` interface, we need to create the
// `Parcelable.Creator<MyParcelable> CREATOR` constant for our class;
// Notice how it has our class specified as its type.
public static final Parcelable.Creator<CityDetails> CREATOR
= new Parcelable.Creator<CityDetails>() {
// This simply calls our new constructor (typically private) and
// passes along the unmarshalled `Parcel`, and then returns the new object!
#Override
public CityDetails createFromParcel(Parcel in) {
return new CityDetails(in);
}
// We just need to copy this and change the type to match our class.
#Override
public CityDetails[] newArray(int size) {
return new CityDetails[size];
}
};
public String getInformation() {
return mInformation;
}
public String getNearPlace() {
return mNearPlace;
}
public String getLocalFood() {
return mLocalFood;
}
public int getItemImageResourceId() {
return mItemImageResourceId;
}
public int getNear_place_image_ResourceId() {
return mNear_place_image_ResourceId;
}
public int getLocal_food_image_ResourceId() {
return mLocal_food_image_ResourceId;
}
}
And lastly my Fragment is :
public class CityInformationFragment extends Fragment {
public CityInformationFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.city_list, container, false);
Bundle bundle = getArguments();
if ((bundle != null)) {
ArrayList<CityDetails> city = getArguments().getParcelable("key");
// Create an {#link WordAdapter}, whose data source is a list of {#link Word}s. The
// adapter knows how to create list items for each item in the list.
CityDetailsAdapter adapter = new CityDetailsAdapter(getActivity(), city);
// Find the {#link ListView} object in the view hierarchy of the {#link Activity}.
// There should be a {#link ListView} with the view ID called list, which is declared in the
// word_list.xml layout file.
ListView listView = (ListView) rootView.findViewById(R.id.city_list);
// Make the {#link ListView} use the {#link WordAdapter} we created above, so that the
// {#link ListView} will display list items for each {#link Word} in the list.
listView.setAdapter(adapter);
}
return rootView;
}
}
EDIT:
Here is my FragmentPagerDapter
public class CityDetailsPagerAdapter extends FragmentPagerAdapter {
final int PAGE_COUNT = 1;
private String tabTitles[] = new String[] { "City Information"};
private Context context;
public CityDetailsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return new CityInformationFragment();
}
#Override
public CharSequence getPageTitle(int position) {
// Generate title based on item position
return tabTitles[position];
}
#Override
public int getCount() {
return PAGE_COUNT;
}
}
And, here's my CityDetailsAdapter:
public class CityDetailsAdapter extends ArrayAdapter<CityDetails> {
// View lookup cache
private static class ViewHolder {
TextView cityInformation;
ImageView city_item_image;
TextView city_near_place_Information;
ImageView city_item_near_place_image;
TextView city_local_food_Information;
ImageView city_item_local_food_image;
}
public CityDetailsAdapter(Context context, ArrayList<CityDetails> cityDetails) {
super(context, 0, cityDetails);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
CityDetails currentCity = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
ViewHolder viewHolder; // view lookup cache stored in tag
if (convertView == null) {
// If there's no view to re-use, inflate a brand new view for row
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.city_list_item, parent, false);
/*Find the TextView and ImageView and set them on the VIewHolder*/
viewHolder.cityInformation = (TextView) convertView.findViewById(R.id.cityInformation);
viewHolder.city_near_place_Information = (TextView) convertView.findViewById(R.id.city_near_place_Information);
viewHolder.city_local_food_Information = (TextView) convertView.findViewById(R.id.city_local_food_Information);
viewHolder.city_item_image = (ImageView) convertView.findViewById(R.id.city_item_image);
viewHolder.city_item_near_place_image = (ImageView) convertView.findViewById(R.id.city_item_near_place_image);
viewHolder.city_item_local_food_image = (ImageView) convertView.findViewById(R.id.city_item_local_food_image);
// Cache the viewHolder object inside the fresh view
convertView.setTag(viewHolder);
} else {
// View is being recycled, retrieve the viewHolder object from tag
viewHolder = (ViewHolder) convertView.getTag();
}
// Populate the data into the template view using the data object
viewHolder.cityInformation.setText(currentCity.getInformation());
viewHolder.city_item_image.setImageResource(currentCity.getItemImageResourceId());
viewHolder.city_near_place_Information.setText(currentCity.getNearPlace());
viewHolder.city_local_food_Information.setText(currentCity.getLocalFood());
viewHolder.city_item_near_place_image.setImageResource(currentCity.getNear_place_image_ResourceId());
viewHolder.city_item_local_food_image.setImageResource(currentCity.getLocal_food_image_ResourceId());
return convertView;
}
Be as specific as possible to the mistake I'm making. Because being a beginner I may be unable to understand some concepts, but I'll try. Anyway, this is my first question here, so maybe I didn't tell the problem right.
And your help is most welcome.
Thanks.
The problem seems to be with your FragmentPagerAdapter class. You're not passing any arguments through the fragment returned by getItem() method it just returns the fragment object without any arguments passed to it.
So modify the method to below
#Override
public Fragment getItem(int position) {
CityInfomationFragment fragment = new CityInformationFragment();
Bundle args = new Bundle();
args.putParcelableArrayList("key", your_array_list);
fragment.setArguments(args);
return fragment;
}
Pass the array_list to the constructor of the FragmentPagerAdapter class from the activity and save it using a field variable and reference it in the getItem() method
EDIT
The fragment instance you have created in the activity has no effect at all unless you do any fragment transaction with it. You can pass that fragment instance to the FragmentPagerAdater and return the same from getItem() method to show that fragment in the ViewPager.
Try below line
ArrayList<CityDetails> city = getArguments().getParcelableArrayList("key");
I am trying to use recyclerView which is inside a fragment.This is fragment is nested inside the viewPager.
public class UniversityDetail extends Fragment {
RecyclerView universityDetailView;
//need to set Adapter
public static UniversityDetail newInstance(){
return new UniversityDetail();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view = inflater.inflate(R.layout.fragment_university_lsit,container,false);
universityDetailView = (RecyclerView)view;
setupViews();
return view;
}
private void setupViews(){
//set the adapter
UniversityDetailAdapter detailAdapter = new UniversityDetailAdapter(new ArrayList<UniversityDetails>());
universityDetailView.addItemDecoration(new RecyclerListDecorater(getActivity()));
universityDetailView.setAdapter(detailAdapter);
universityDetailView.setLayoutManager(new LinearLayoutManager(getActivity()));
universityDetailView.setHasFixedSize(false);
}
public RecyclerView getRecyclerView(){
return this.universityDetailView;
}
}
This is the fragment which i want to be inside the viewPager.It returns a recyclerView from onCreateView.
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scroll"
android:paddingBottom="8dp"
android:paddingTop="?attr/actionBarSize"
android:scrollbars="vertical">
fragment_university_lsit.xml
recyclerView uses below adapter.
public class UniversityDetailAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<UniversityDetails> universityDetails;
private static Map<String,String> admissionRecommendation;
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((DetailHolder)holder).bind(universityDetails.get(position));
}
#Override
public int getItemCount() {
return universityDetails.size();
}
public UniversityDetailAdapter(List<UniversityDetails> details){
this.universityDetails = details;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,int viewType){
Log.d("CreateUniveristyDetail", "onCreateViewHolder: detail called");
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.detail_fragment,parent,false);
return new DetailHolder(itemView);
}
}
DetailHolder is the class extends ReyclerView.ViewHolder and its implementation is irrelevant here.
The adapter list is upated from handler and notifyDataSetChanged()
private void setup(){
mHandler = new Handler(Looper.getMainLooper()){
#Override
public void handleMessage(Message message){
if(message.what == ConnectionHandler.DETAILMSG){
Log.d("detail handle message", "handleMessage: Called");
List<UniversityDetails> details = (List<UniversityDetails>)message.obj;
UniversityDetailAdapter adapter = (UniversityDetailAdapter)universityDetail.getRecyclerView().getAdapter();
adapter.addAll(details);
adapter.notifyDataSetChanged();
Log.d("size", "handleMessage: " + adapter.getItemCount());
}
}
};
}
Fragment is created in activity..
private void setupNavigation(){
ViewPagerAdapter pagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
universityDetail = UniversityDetail.newInstance();
pagerAdapter.addFragment(universityDetail);
mViewPager.setAdapter(pagerAdapter);
//setup the fragment transaction.
headerTab.setViewPager(mViewPager,0);
//no need to add to back stack
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.view_pager,universityDetail);
transaction.commit();
}
Implementation of DetailHolder
public class DetailHolder extends RecyclerView.ViewHolder{
CardView admissionGeneral;
public View root;
public DetailHolder (View itemView){
super(itemView);
root = itemView;
admissionGeneral = (CardView)root.findViewById(R.id.admission_general);
}
public void bind(UniversityDetails detail){
Log.d("detail bind", "bind: binding to recyclerView");
}
}
here the log of adapter.getItemCount(); return 3; thats means something is being added and onCreateViewHolder is supposed to be called but it never is.
The weird thing is i have implemented another recyclerAdapter is the sameProject and its working perfectly fine.
This problem would be easier to answer if you could show how the universityDetail was assigned from whatever class contains the Handler usage.
It's also not clear how message.obj is able to be cast to a List.
I believe you are using a different instance of the Fragment there than the one that you want to update.
So, you know this line is good
List<UniversityDetails> details = (List<UniversityDetails>)message.obj;
because you see 3 in the output when you do this
Log.d("size", "handleMessage: " + adapter.getItemCount());
Now, these lines could likely be the problem.
UniversityDetailAdapter adapter = (UniversityDetailAdapter)universityDetail.getRecyclerView().getAdapter();
adapter.addAll(details);
adapter.notifyDataSetChanged();
Firstly, there is no addAll method to RecyclerView.Adapter. So I'm not sure how that compiled. Anyways...
Try to keep cross-class references to a minimum. Here, we expose only the method to add the details rather than the entire RecyclerView.
Then, for RecyclerViews, you should notify only the data that was inserted rather than all of it using notifyItemRangeInserted
public class UniversityDetailFragment extends Fragment {
private RecyclerView universityDetailView;
private UniversityDetailAdapter detailAdapter;
private List<UniversityDetails> details = new ArrayList<UniversityDetails>();
...
public void setupViews() {
detailAdapter = new UniversityDetailAdapter(details);
// etc...
}
public void addDetails(List<UniversityDetails> details) {
int curSize = detailAdapter.getItemCount();
this.details.addAll(details);
detailAdapter.notifyItemRangeInserted(curSize, details.size());
}
}
And then just use the instance of your Fragment
if(message.what == ConnectionHandler.DETAILMSG){
Log.d("detail handle message", "handleMessage: Called");
List<UniversityDetails> details = (List<UniversityDetails>) message.obj;
universityDetail.addDetails(details);
Another recommendation would be to use the correct generic type for the holder class to avoid unnecessary casts.
public class UniversityDetailAdapter extends RecyclerView.Adapter<DetailHolder>
I have a fragment A, containing a listview. To this listview I add a listheader containing a ViewPager that pages through childfragments.
When the user clicks an item in the list, the listfragment A gets replaced by a detail-view-fragment of that listitem.
I want the user to be able to go back to the list by clicking the back button.
So far everything works, except when the user presses the back button to pop the detail fragment from the stack to get back to the listview fragment A, the app crashes with an
java.lang.IllegalArgumentException: No view found for id 0x7f06002e (com.makamedia.hockeyweb:id/news_header_pager) for fragment NewsHeaderFragment{41f7b6f8 #0 id=0x7f06002e android:switcher:2131099694:0}
My suspicion is, that maybe the nested fragments for the viewpager in the listheader get recreated before the viewpager gets recreated, thus crashing the app, but I am not sure.
Any help is appreciated!
My ViewPagerAdapter for the listheader-viewpager (removed some unrelated code):
public class NewsHeaderAdapter extends FragmentPagerAdapter {
private int mCount;
public final NewsListAdapter mListAdapter;
public NewsHeaderAdapter(FragmentManager fm, int count, long autoSwipeInterval, NewsListAdapter adapter) {
super(fm);
this.mCount = count;
this.mListAdapter = adapter;
}
#Override
public Fragment getItem(int pos) {
return NewsHeaderFragment.getNew(this.mListAdapter.getItem(pos));
}
public void setCount(int newCount){
if(newCount < 1){
this.mCount = 1;
} else if(newCount >= this.mListAdapter.getCount()){
this.mCount = this.mListAdapter.getCount();
} else {
this.mCount = newCount;
}
}
#Override
public int getCount() {
return mCount;
}
#Override
public CharSequence getPageTitle(int position) {
return this.mListAdapter.getItem(position).getTitle();
}
}
My news detail fragment (pretty straight forward):
public class NewsHeaderFragment extends Fragment {
private NewsItem mNewsItem;
private TextView mHeaderNewsBigTitle;
private ImageView mHeaderNewsBigImage;
// Convenience method for creating a new fragment with parameters
public static NewsHeaderFragment getNew(NewsItem item){
NewsHeaderFragment fragment = new NewsHeaderFragment();
Bundle args = new Bundle();
args.putSerializable(Constants.SIG_NEWS_ITEM, item);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.row_big_news, container, false);
Bundle newsHeaderArgs = getArguments();
mNewsItem = (NewsItem)newsHeaderArgs.getSerializable(Constants.SIG_NEWS_ITEM);
setupUI(rootView);
fillUI();
return rootView;
}
private void fillUI() {
mHeaderNewsBigTitle.setText(mNewsItem.getTitle());
Picasso.with(getActivity()).load(mNewsItem.getImageBig2x()).into(mHeaderNewsBigImage);
}
private void setupUI(View rootView) {
mHeaderNewsBigTitle = (TextView) rootView.findViewById(R.id.news_big_title);
mHeaderNewsBigImage = (ImageView) rootView.findViewById(R.id.news_big_img);
}
}
My viewpager is declared in xml in a row-layout and added like so:
private void addHeaderPager(int count) {
if(mNewsListAdapter != null && mNewsListAdapter.getCount()>0) {
if (count >= mNewsListAdapter.getCount()) {
count = mNewsListAdapter.getCount() - 1;
}
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mHeader = (RelativeLayout) inflater.inflate(R.layout.row_big_news_pager, null);
mHeaderPager = (ViewPager) mHeader.findViewById(R.id.news_header_pager);
mHeaderPagerAdapter = new NewsHeaderAdapter(getChildFragmentManager(), count, 6000, mNewsListAdapter);
mHeaderPager.setOffscreenPageLimit(count);
mHeaderPager.setAdapter(mHeaderPagerAdapter);
// Bind the title indicator to the adapter
CirclePageIndicator circleIndicator = (CirclePageIndicator) mHeader.findViewById(R.id.news_header_pager_indicator);
circleIndicator.setViewPager(mHeaderPager);
mNewsListView.addHeaderView(mHeader);
}
}
Are you sure tha you use the right FragmentManager in addHeaderPager()?
I normally use getFragmentManager() and if there is a parent fragment I have to use getParentFragment().getFragmentManager() - if I don't I get the same error ("No view found for id") when trying to replace the current visible fragment.
The header for my listview is pretty complex. It is a ViewPager / FragmentStatePagerAdapter with 5 fragments. The strange thing is that when the view first loads the whole listview is laggy. Once you scroll past the header the listview becomes smooth even when you scroll back up to the header. Any ideas on what may be happening? Thanks for any information
Fragment That contains ListView
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View featuredView = inflater.inflate(R.layout.fragment_featured_series, container, false);
ButterKnife.inject(this, featuredView);
View headerView = getHeaderView()
featuredList.addHeaderView(featuredListHeaderView);
sAdapter = new SeriesAdapter(getActivity(), new ArrayList<SeriesItem>());
featuredList.setAdapter(sAdapter);
return featuredView;
}
RelativeLayout getHeaderView() {
featuredListHeaderView = new FeaturedListHeaderView(getActivity(), this, rHeight, recentHeight);
featuredListHeaderView.setLayoutParams(params);
featuredListHeaderView.setVisibility(View.INVISIBLE);
return featuredListHeaderView;
}
}
The Header contains a Fragment which contains the pager
...
pagerAdapter = new FeaturedPagerAdapter(getActivity().getSupportFragmentManager(), new ArrayList<SeriesItem>());
viewPager.setAdapter(pagerAdapter);
pageIndicator.setViewPager(viewPager);
private class FeaturedPagerAdapter extends FragmentStatePagerAdapter {
private List<SeriesItem> seriesItems = new ArrayList<SeriesItem>();
public FeaturedPagerAdapter(FragmentManager fm, List<SeriesItem> sItems) {
super(fm);
this.seriesItems = sItems;
}
#Override
public Fragment getItem(int i) {
FeaturedPageFragment pf = new FeaturedPageFragment(seriesItems.get(i));
return pf;
}
#Override
public int getCount() {
return seriesItems.size();
}
#Override
public CharSequence getPageTitle(int position) {
return seriesItems.get(position).getName();
}
public void setItems(List<SeriesItem> items) {
this.seriesItems = items;
notifyDataSetChanged();
pageIndicator.notifyDataSetChanged();
}
}
FeaturedPageFragment just contains a image with some click listeners