FragmentTabHost randomly goes blank after configuration change (rotation)

39 Views Asked by At

I came across a frustrating glitch in my app.

I have a FragmentTabHost in my Activity. This tab host is hosting 5 fragments, each of these getting rendered correctly.

Now, the trick is that when I rotate the screen, sometimes the fragment is not being rendered. The tab widget is still being rendered and is clickable, but the view corresponding to that fragment is not displayed.

Here is an example below. Note that I put a red background on my realtabcontent FrameLayout:

  1. Everything is working fine enter image description here

  2. Rotating the screen: randomly the screen becomes partially empty enter image description here

  3. Switching to other tabs executes the OnCreate/OnCreateView of the relevant fragment, but nothing is displayed enter image description here

  4. Switching back to the first screen does not even show the button of the Fragment anymore enter image description here

Note that: - the first time I rotate, there is still a button showing up (it belongs to the fragment that should be rendered). - once the bug is triggered, I can switch to other tabs. The other tabs are loaded correctly (notice the action bar changing), but nothing is rendered in the FrameLayout - If I go back to the first tab, the button that was still being displayed is not even rendered anymore

Notice also a detail: the reddot of the tab disappears, even though the code that set the reddot is correctly executed. It's as if the tab host is not responding to the drawing requests.

Here is the code in charge of displaying the reddot:

    public void showReddotForTab(int tabIndx, Boolean showFlag){
        try {
            if (tabIndx < mTabHost.getTabWidget().getTabCount()) {
                RelativeLayout tabRootView         = (RelativeLayout) mTabHost.getTabWidget().getChildTabViewAt(tabIndx);
                WhovaNotificationBadge notifBadge  = tabRootView.findViewById(R.id.notif_badge);

                if (showFlag) {
                    notifBadge.setVisibility(View.VISIBLE);
                    notifBadge.setLabel(null);
                }
                else {
                    notifBadge.setVisibility(View.GONE);
                }
            }
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

Here is the layout of the activity containing the FragmentTabHost

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentTabHost
        android:id="@android:id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent">

            <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="0dp"
                android:layout_height="0dp" />

            <FrameLayout
                android:id="@+id/realtabcontent"
                android:background="@color/red"
                android:layout_width="fill_parent"
                android:layout_height="0dp"
                android:layout_weight="1" />

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="2px"
                android:background="@color/separate_gray"/>

            <TabWidget
                android:id="@android:id/tabs"
                android:orientation="horizontal"
                android:background="@color/new_whova_tab_bg"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:paddingTop="6dp"
                android:layout_weight="0"/>

        </LinearLayout>

    </androidx.fragment.app.FragmentTabHost>


    <TextView
        android:id="@+id/text_network"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="40dp"
        android:layout_alignParentTop="true"
        android:background="@color/orange"
        android:text="@string/network_off_indicator_msg"
        android:visibility="gone"
        android:textColor="@color/white"
        android:gravity="center"/>

</RelativeLayout>

Here is the code setting up the tabhost

        mTabHost = findViewById(android.R.id.tabhost);
        mTabHost.setOnTabChangedListener(tabId -> {
           ...
        })

        mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
        mTabHost.getTabWidget().setDividerDrawable(null);

        //! simplified below, but the code is simlar to it
        //! spec is basically TabHost.TabSpec spec = mTabHost.newTabSpec("someID").setImageResource(...).setIndicator(...).setContent(tag -> findViewById(R.id.realtabcontent));
        mTabHost.addTab(spec, FragmentXXXX.class, fragmentBundleXXXX);
        mTabHost.addTab(spec, FragmentXXXX.class, fragmentBundleXXXX);
        mTabHost.addTab(spec, FragmentXXXX.class, fragmentBundleXXXX);
        mTabHost.addTab(spec, FragmentXXXX.class, fragmentBundleXXXX);
        mTabHost.addTab(spec, FragmentXXXX.class, fragmentBundleXXXX);

1

There are 1 best solutions below

0
Simon Ninon On

Found how to solve it, but I have no idea what is the reason.

My actionbar is a toolbar containing an AppCompatSpinner. The spinner is only used for the tab showed in the pictures and has setOnItemSelectedListener set.

When rotating the screen, the listener is called (not sure why, because the initial selection is done before setting the listener and not selection occurred afterwhile.

Either way, sometimes when this listener is called, the data loaded from DB is still processing in the background. The weird thing being that once it is done, we refresh the UI but it stays blank and the other tabs are affected. I suspect that there is something wrong in the way the listener is called and it blocks the processing of other UI events, but not sure.

When I set the listener within a post, the problem disappear

spinner.post(() -> {
  spinner.setOnItemSelectedListener(...)
})