OnInternalLayout only shows added views on first call


#1

Greetings!

I’m seeing some odd behavior when using the override onInternalLayout(ShinobiChart shinobiChart) method on the ShinobiChart.OnInternalLayoutListener interface.

I saw this thread (https://forum.shinobicontrols.com/t/shinobichart-clips-annotation-views/273) about recommending to use the OnInternalLayoutListener with a view overlaid on top of the chart to hold custom views for your annotations. I’m doing something basically the same where I have a RelativeLayout that is on top of my chart and set to the same dimensions. I have buttons that reload the chart data and I’m adding views to this RelativeLayout every time the chart finishes rendering. See code below:

Layout XML:

<fragment
    class="com.shinobicontrols.charts.ChartFragment"
    android:id="@+id/chart"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf=parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

<RelativeLayout
    android:id="@+id/chartAnnotationsLayout"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="@id/chart"
    app:layout_constraintLeft_toLeftOf="@+id/chart"
    app:layout_constraintRight_toRightOf="@id/chart"
    app:layout_constraintTop_toTopOf="@id/chart">
</RelativeLayout>

Activity Class:

public class MainActivity extends AppCompatActivity implements ShinobiChart.OnInternalLayoutListener { 
    private ShinobiChart chart;
    private RelativeLayout chartAnnotationsLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ChartFragment chartFragment =
                (ChartFragment) getFragmentManager().findFragmentById(R.id.chart);
        chart = chartFragment.getShinobiChart();
        chart.setOnInternalLayoutListener(this);

        chartAnnotationsLayout = findViewById(R.id.chartAnnotationsLayout);

        //chart setup and styling goes here, also loads initial data
    }

    private void selectButton(Button button) {
        //reloads chart with different data
    }

    @Override
    public void onInternalLayout(ShinobiChart shinobiChart) {
        drawAnnotations();
    }

    public void drawAnnotations() {
        for (int i = 0; i < 5; i++) {
            TextView annotation = new TextView(this);
            annotation.setText(String.valueOf(i));
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(100, 100);
            params.leftMargin = i * 20;
            params.topMargin = i * 20;

            chartAnnotationsLayout.addView(annotation, params);
            Log.e("annotations count", String.valueOf(chartAnnotationsLayout.getChildCount()));
        }
    }
}

This seems to work the very first time the chart renders, but every time after when the chart data is reloaded causing the chart to re-layout (via the button selection), the RelativeLayout doesn’t show up with the newly added views. However, I can see that the views have been properly added to the RelativeLayout because when I log out how many children views it has, the number is increased to what I expect it to be. It’s like the views that were added after the very first onInternalLayout call are actually added and there in the the containing relative layout but just not visible.

When I move the code that adds the views to the RelativeLayout outside of the onInternalLayout method (for example, into the button selection method that triggers the chart data to reload), all the added views in the RelativeLayout are visible and work as expected. It seems to be something about adding the views when inside the onInternalLayout method that is causing the issue.

The weird thing is that if I wrap the code for adding the views within a 1 millisecond delay inside the onInternalLayout method, then all the added views do show up. I thought it may have been a threading issue, but I double checked and it appears that we’re on the main thread when adding the views inside the onInternalLayout method.

Weird 1 millisecond delay that fixes it:

    @Override
    public void onInternalLayout(ShinobiChart shinobiChart) {
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                drawAnnotations();
            }
        }, 1);
    }

Do you have any hunch what this strange invisible added views issue could be?


#2

Just following up on this, but after a little more investigating I suspect this is a threading issue because if I run my code that adds the annotations on the UI thread, then it works as expected.

Running on UI Thread also seems to fix it:

@Override
public void onInternalLayout(final ShinobiChart shinobiChart) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            drawAnnotations();
        }
    });
}

Should this be expected behavior that I would need to specifically run on the UI thread when using the onInternalLayout method of the OnInternalLayoutListener interface? If so, it would be good to document that this would be a requirement since by the name “internal layout” it seems fair to assume we’d already be on the main/UI thread but perhaps that isn’t the case.


#3

Hi,
Thanks for providing the update. We are not aware of any existing issue with our onInternalLayout code as you are the first to report this. I can confirm that we do not explicitly require the user to wrap their onInternalLayout code within a call to runOnUIThread, as you have done. Whilst we do delegate certain time consuming tasks to worker threads, onInternalLayout should certainly be being called by the main / UI thread.
Could you please tell me a little more about your application:
Are you using Animations?
Are you using gestures?
Are you using skip ranges on your axes?
Do you do any work on different threads yourself, for example background data loading?
I look forward to hearing from you.
Thanks,
Kai.