3

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, calls createGraph and then updateTab
  • 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). here resetData 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). Tab Fragment Layout

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.

GIF of issue

BlueCacti
  • 9,729
  • 3
  • 20
  • 24

0 Answers0