I'm working on a simple Android application (to-do list), and I'm stuck on attempting to implement a ViewPager
using a FragmentStatePagerAdapter
to navigate through days with tasks. For simplicity I added some tasks for current day, day before, and day after today (not dealing with days without tasks yet, just to keep it simple).
I'm not able to achieve loading proper days when screen is scrolled. When I first swipe left to day before, I get correct result - day before is shown. But then I'm not able to swipe left to get more days (I thought that my code will set the previous "left" date as current "middle" date and let me swipe left again). After trying to scroll to the right-most date and then to left again, I end up with NullPointerException
, but I can't figure out how should I change my code to refresh dates properly. Stack trace after sequence of swipes (left-right-right-left):
04-05 22:38:24.966 12441-12441/ptmprojects.com.quicktickcalendar2 E/AndroidRuntime: FATAL EXCEPTION: main
Process: ptmprojects.com.quicktickcalendar2, PID: 12441
java.lang.NullPointerException: Attempt to invoke virtual method 'android.support.v4.app.FragmentTransaction android.support.v4.app.FragmentTransaction.add(int, android.support.v4.app.Fragment)' on a null object reference
at android.support.v4.app.FragmentStatePagerAdapter.instantiateItem(FragmentStatePagerAdapter.java:123)
at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:1004)
at android.support.v4.view.ViewPager.populate(ViewPager.java:1186)
at android.support.v4.view.ViewPager.populate(ViewPager.java:1086)
at android.support.v4.view.ViewPager$3.run(ViewPager.java:267)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:933)
at android.view.Choreographer.doCallbacks(Choreographer.java:742)
at android.view.Choreographer.doFrame(Choreographer.java:671)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:919)
at android.os.Handler.handleCallback(Handler.java:761)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6605)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:999)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:889)
I created a TasksBank
, where all tasks are stored in List<SingleTask> mAllTasksList
, to get tasks from that bank to load them into ViewPager
when the proper day shows up on the screen. Tasks are loaded properly for each day as I can see in other Activity
started when clicking on Button
from ActionBar
. Here is a screenshot to confirm that tasks are loaded properly from TasksBank
for different days:
Tasks are represented by class SimpleTask
and there is a field mDate
in each SimpleTask
to store day assigned to each task (I know that Map<LocalDate, SimpleTask>
could be a better choice, but for simplicity I have chosen to start with ArrayList<SimpleTask>
- if it was a really bad idea, please correct me).
To make you better understand how does it look like, here is a screenshot of how SingleDayFragment
looks like (it's a list of SingleTask
s stored in RecyclerView
):
My thought of solving it was to create a List<LocalDate> mListOfDates
in DailyTaskPagerActivity
that would store 3 dates - date for task that would show up on left-swipe, date for middle screen and date for the one showing on right-swipe. After scrolling to another day, I should some way set the previous left or right fragment as a current middle one and add another day to list of days when needed. I also created fields: currentPosition
and currentDate
to store details of currently displayed screen.
I include .java
files that I think are relevant to problem that I'm facing, but I think the first one - DailyTaskPagerActivity.java
is the one that I have a problem with. If it's too much or not enough in that case, please let me know - I kindly include everything what is needed with proper edit or delete what shouldn't be there.
DailyTaskPagerActivity.java
(main class responsible for scrolling between days - I'm pretty sure here's a problem). I made some explanations of what is a purpose of including each element of code in comments:
public class DailyTaskPagerActivity extends AppCompatActivity {
private ViewPager mViewPager;
private List<LocalDate> mListsOfDates;
private TasksDaysStatePagerAdapter mAdapter;
int currentPosition;
LocalDate currentDate;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_daily_task_pager);
mViewPager = (ViewPager) findViewById(R.id.daily_task_view_pager);
mListsOfDates = new ArrayList<>();
mListsOfDates.add(new LocalDate().minusDays(1));
mListsOfDates.add(new LocalDate());
mListsOfDates.add(new LocalDate().plusDays(1));
currentPosition = 1; // current position on middle screen / today's date
currentDate = new LocalDate();
mAdapter = new TasksDaysStatePagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mAdapter);
mViewPager.setCurrentItem(1); // to start mViewPager from the middle date (to allow swipe to the left and
// to the right (not only to right if it were 0
}
private class TasksDaysStatePagerAdapter extends FragmentStatePagerAdapter {
public TasksDaysStatePagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
if (position > currentPosition) { // if position after swipe is higher than currentPosition(equal to 1)
// it means that the screen has been swiped to the right
currentPosition = position; // update current position field
currentDate = currentDate.plusDays(1); // update currentDate field
mListsOfDates.set(2, currentDate); // set updated date to mListsOfDates last element
notifyDataSetChanged(); // notify that change to mListsOfDates occured
return SingleDayFragment.newInstance(currentDate); // create new Fragment with updated date
} else if (position < currentPosition) {// if position after swipe is lower than currentPosition(equal to 1)
// it means that the screen has been swiped to the left
currentPosition = position;
currentDate = currentDate.minusDays(1);
mListsOfDates.set(0, currentDate);
notifyDataSetChanged();
return SingleDayFragment.newInstance(currentDate);
} else { // I'm not sure if I should include anything here ??
// currentPosition = position;
// mViewPager.setAdapter(new TasksDaysStatePagerAdapter(getSupportFragmentManager()));
return SingleDayFragment.newInstance(currentDate);
}
}
@Override
public int getCount() {
return mListsOfDates.size();
}
}
}
SingleDayFragment.java
(Fragment for one day, containing TextView
with date and RecyclerView
with tasks for single day)
public class SingleDayFragment extends Fragment {
private static final String ARG_DATE = "ptmprojects.com.quicktickcalendar.argDate";
private RecyclerView mTaskRecyclerView;
private TaskAdapter mAdapter;
private TextView mDateAboveRecyclerView;
DateTimeFormatter mDateTimeFormatter = DateTimeFormat.forPattern("dd MMMM yyyy");
private LocalDate date;
public static SingleDayFragment newInstance(LocalDate date) {
Bundle args = new Bundle();
args.putSerializable(ARG_DATE, date);
SingleDayFragment fragment = new SingleDayFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
date = getArguments().getSerializable(ARG_DATE) != null ? (LocalDate) getArguments().getSerializable(ARG_DATE) : new LocalDate();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_single_day, container, false);
mTaskRecyclerView = (RecyclerView) view.findViewById(R.id.single_day_recycler_view);
mTaskRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mDateAboveRecyclerView = view.findViewById(R.id.date_above_recycler_view);
mDateAboveRecyclerView.setText(getString(R.string.date_above_recycler_view, date.toString(mDateTimeFormatter)));
updateUI();
return view;
}
private void updateUI() { // updating RecyclerView containing tasks for one day
TasksBank tasksBank = TasksBank.get(getActivity());
List<SingleTask> tasksForSingleDay = tasksBank.getTasksForDate(date);
if (mAdapter == null) {
mAdapter = new TaskAdapter(tasksForSingleDay);
mTaskRecyclerView.setAdapter(mAdapter);
} else {
mAdapter.setTasks(tasksForSingleDay);
mAdapter.notifyDataSetChanged();
}
}
// private class TaskAdapter...
// private class TaskHolder...
}
AndroidManifest.xml
(deleted some lines that I think are not needed):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ptmprojects.com.quicktickcalendar">
<application
android:allowBackup="true"
<activity android:name=".DailyTaskPagerActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SingleDayActivity">
</activity>
</application>
</manifest>
TasksBank.java
(class to provide lists of task for each day):
public class TasksBank {
private Context mContext;
private static TasksBank sTasksBank;
private List<SingleTask> mAllTasksList;
public static TasksBank get(Context context) {
if (sTasksBank == null) {
sTasksBank = new TasksBank(context);
}
return sTasksBank;
}
private TasksBank(Context context) {
mAllTasksList = new ArrayList<>();
// Just a sample, to check if code is working
mContext = context.getApplicationContext();
mAllTasksList.add(new SingleTask(new LocalDate().minusDays(2), "Title2DaysBefore", "Description2DaysBefore", false));
mAllTasksList.add(new SingleTask(new LocalDate().minusDays(1), "Title1DayBefore", "Description1DayBefore", false));
mAllTasksList.add(new SingleTask(new LocalDate(), "Title2", "Description2", false));
mAllTasksList.add(new SingleTask(new LocalDate().plusDays(1), "Title1DayAfter", "Description1DayAfter", false));
mAllTasksList.add(new SingleTask(new LocalDate().plusDays(2), "Title2DaysAfter", "Description2DaysAfter", false));
mAllTasksList.add(new SingleTask(new LocalDate(2018, 03, 31), "Title3", "Description3", false));
}
public ArrayList<SingleTask> getTasksForDate(LocalDate date) {
ArrayList<SingleTask> tasksForDate = new ArrayList<>();
for(SingleTask task : getAllTasksList()) {
if (task.getDate().equals(date)) {
tasksForDate.add(task);
}
}
return tasksForDate;
}
public List<SingleTask> getAllTasksList() {
return mAllTasksList;
}
public void addTask(SingleTask task) {
mAllTasksList.add(task);
}
}
SingleTask.java
(class representing task, there are more fields, getters and setters, but I don't include it as I think it's irrelevant):
public class SingleTask {
private String mTitle;
private LocalDate mDate;
public SingleTask(LocalDate date, String title) {
mDate = date;
mTitle = title;
}
public LocalDate getDate() {
return mDate;
}
public void setDate(LocalDate date) {
mDate = date;
}
public String getTitle() {
return mTitle;
}
public void setTitle(String title) {
mTitle = title;
}
}
Just to let you know that I think that I made a proper amount of research, I include links to some questions that I found here at Stack Overflow, apart from looking outside of Stack Overflow too:
In many tutorials I have read that it's trivial to make ViewPager
work properly with only implementing getCount()
and getItem()
methods. How can I get this to work, or can anyone see why it doesn't work now?