I've written a small Android app which'll collect data from a JSON API, displays a graph from that data and calculates the total of the data in the graph (the data is the output of a PV installation).
The app uses a TabLayout to display graphs for daily, monthly, yearly and total overview.
My idea is to have a datepicker to change the date for which the data is retrieved and displayed. For this I use a TextView with a coupled DatePicker. The new data is retrieved and calculated as I can see the sum being updated when changing the reference date.
However, when picking a different date, the graph doesn't immediately update with the new data and the graph is cleared/empty.
I have to 2 tabs further and then return to the correct tab or rotate the device to get the graph in that Tab to display the correct data.
Once the new data is visualised in the Tab, changing the data again clears the graph. Returning to the same data shows the correct graph.
The date also isn't shared across the Tabs. Changing date on 1 Tab doesn't change it on the next.
I notice'd while logging getItem
in MyPagerAdapter
that switching from tab 0 to tab 1 causes tab 2 to be requested, and going from tab 3 to tab 2 loads tab 1. I guess this is some sort of pre-load? Perhaps this causes the effect that changing dates in tab 0 doesn't reflect in tab 1 but does in tab 2 and that I have to go from tab 0 over tab 3 back to tab 1 to get that one to update it's date?
Following code snippets are modified slightly for readability. I.e. I reuse the Tab Fragment for the different data view (day/month/year/total) and use some switch or if/else's to e.g. set the correct labels. There are also some more methods in each class, which I don't think are relevant for this issue.
MainActivity.java
Links TabLayout/Pager in View to MyPagerAdapter
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ViewPager viewPager = findViewById(R.id.pager);
MyPagerAdapter myPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), this, new Date());
viewPager.setAdapter(myPagerAdapter);
TabLayout tabLayout = findViewById(R.id.tablayout);
tabLayout.setupWithViewPager(viewPager);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {...}
@Override
public boolean onOptionsItemSelected(MenuItem item) {...}
}
MyPagerAdapter.java
Returns Fragment object with frequency (day/month/year/total) and serialized Calendar Object passed as param.
public class MyPagerAdapter extends FragmentStatePagerAdapter {
private final int NUM_TABS = 4;
private String classtag = this.getClass().getSimpleName();
private Context _context;
private Calendar mCal;
MyPagerAdapter(FragmentManager fm, Context c, Date d) {
super(fm); // TODO: deprecated
_context = c;
mCal = Calendar.getInstance();
mCal.setTime(d);
}
@Override
public int getCount() {
return NUM_TABS;
}
@NonNull
@Override
public Fragment getItem(int position) {
Log.d(classtag, "DEBUG: getItem(position=" + position + ")");
Bundle args = new Bundle();
Fragment chartFragment = new Tab();
args.putSerializable("calendar", mCal);
switch (position) {
default:
case 0:
args.putInt("frequency", Misc.FREQ_DAILY);
break;
case 1:
args.putInt("frequency", Misc.FREQ_MONTHLY);
break;
case 2:
args.putInt("frequency", Misc.FREQ_YEARLY);
break;
case 3:
args.putInt("frequency", Misc.FREQ_TOTAL);
break;
}
chartFragment.setArguments(args);
return chartFragment;
}
@NonNull
@Override
public CharSequence getPageTitle(int position) {
Log.d(classtag, "DEBUG: getPageTitle(position=" + position + ")");
switch (position) {
default:
case 0:
return _context.getString(R.string.day);
case 1:
return _context.getString(R.string.week);
case 2:
return _context.getString(R.string.year);
case 3:
return _context.getString(R.string.total);
}
}
}
Tab.java
onCreateView
creates references to the objects in the Fragment/View, callscreateGraph
and thenupdateTab
createGraph
prepares the graph and the initial Serie (data)updateTab
is called after the graph creation, or when changing dates (through the Prev/Next button or the DatePicker). hereresetData
is called
public class Tab extends Fragment {
private String classtag = this.getClass().getSimpleName();
private Resources resources;
private Calendar mCal;
private int mFreq;
private View rootView;
private BaseSeries<DataPoint> mSeries;
private TextView customerInfo;
private TextView totalProduction;
private TextView datepicker;
private GraphView graph;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_tab, container, false);
assert getArguments() != null;
mFreq = getArguments().getInt("frequency", 0);
mCal = (Calendar) getArguments().getSerializable("calendar");
resources = rootView.getResources();
customerInfo = rootView.findViewById(R.id.lbl_customer_info);
totalProduction = rootView.findViewById(R.id.lbl_total);
datepicker = rootView.findViewById(R.id.lbl_datepicker);
graph = rootView.findViewById(R.id.graph);
Button btnPrev = rootView.findViewById(R.id.btn_prev);
Button btnNext = rootView.findViewById(R.id.btn_next);
// Custom configs for certain tabs omitted here >>>
btnPrev.setVisibility(View.VISIBLE);
btnNext.setVisibility(View.VISIBLE);
datepicker.setVisibility(View.VISIBLE);
// Change date with frequency according to tab (hop 1 month in Month Tab)
btnPrev.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Date _curDate = mCal.getTime();
mCal.setTime(Misc.getPrevDate(_curDate, mFreq));
updateTab();
}
});
btnNext.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Date _curDate = mCal.getTime();
mCal.setTime(Misc.getNextDate(_curDate, mFreq));
updateTab();
}
});
datepicker.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new DatePickerDialog(rootView.getContext(), new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
mCal.set(Calendar.YEAR, year);
mCal.set(Calendar.MONTH, month);
mCal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateTab();
}
}, mCal.get(Calendar.YEAR), mCal.get(Calendar.MONTH), mCal.get(Calendar.DAY_OF_MONTH)).show();
}
});
// >>> Custom configs for certain tabs omitted here
createGraph();
updateTab();
return rootView;
}
@Override
public void onResume() {
super.onResume();
updateTab();
}
private void createGraph() {
GridLabelRenderer gridLabel = graph.getGridLabelRenderer();
Viewport viewport = graph.getViewport();
LegendRenderer legend = graph.getLegendRenderer();
// [...] Ommitted some irrelevant code here
mSeries = new LineGraphSeries<>(getDataPoints());
// [...] Ommitted some irrelevant code here
graph.addSeries(mSeries);
// [...] Ommitted some irrelevant code here
}
private void updateTab() {
DataPoint[] dp;
datepicker.setText(Misc.getDate(mCal.getTime()));
// [...] Ommitted some irrelevant code here
double totalProd = Misc.sumDataPoints(dp);
totalProduction.setText(String.format(Locale.getDefault(), "%.1f", totalProd));
mSeries.resetData(dp); // This should update the graph series with the new data
graph.onDataChanged(false, false);
}
private Map<String, String> getCustomerInfo() {...}
private DataPoint[] getDataPoints() {...}
}
This is the Tab Fragment Layout. This Fragment is loaded in a Pager in the Main Activity Layout (which consists out of a toolbar, tablayout and a pager).
I thought about making seperate classed for the different frequencies, I.e. TabDay, TabMonth, TabYear, TabTotal as to not create multiple instances of the same Object, but I don't think that would have any effect and would cause lots of code duplicated.
In the same line I considered making a "master Tab" and extending it for the different frequencies, thorugh which I'd probably be able to keep the Calendar Object in the "master Tab" and hope it'd keep the Tabs synced on the displaying the date part.
On the redrawing the graph part, I have the feeling I need to force some redrawing to happen somehow, but I don't know how or where. The onResume
method calls that of it's upper class, which I'd think does the redrawing at least when switching tabs.