FragmentTabHost randomly goes blank after configuration change (rotation) - android

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:
Everything is working fine
Rotating the screen: randomly the screen becomes partially empty
Switching to other tabs executes the OnCreate/OnCreateView of the relevant fragment, but nothing is displayed
Switching back to the first screen does not even show the button of the Fragment anymore
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);

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(...)
})

Related

How to dynamically show more or less fragments within 1 activity?

My app has a log_in activity, a main activity, and a settings activity.
The main activity is currently working by flowing between multiple fragments:
In the first fragment, the user selects a topic (from a dynamically-populated recyclerview of cardviews), the topic is then parceled and sent to the second fragment.
The second fragment displays the appropriate list (another dynamically-populated recyclerview of cardviews (formatted very differently, with different cardviews). The user can go back to the first fragment and choose another topic, and that choice is again parceled and sent to the second fragment to display the relevant list etc.
I want to set up the 2nd "page" so that it has 1 fragment at the top, which covers the top 80% of the screen, and 1 fragment at the bottom, which covers the bottom 20% of the screen, while still having only 1 fragment on the first "page" that covers 100% of the screen.
This is how I'm switching between each fragment in the flow right now:
main.java
bundle = new Bundle();
bundle.putParcelable("itemlist", itemlist);
fragment = new fragment_2();
fragment.setArguments(bundle);
fragmentManager = getSupportFragmentManager();
transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.main_container, fragment).commit();
And my activity_main.xml (for the main activity) has this layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/ColorBG"
tools:context="com.app.activity.main">
<FrameLayout
android:id="#+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>
</LinearLayout>
So main_container is the FrameLayout that I'm replacing with the relevant fragment.
I can't find any clean example of a way to do this, can anyone show me?
The only thing I can think of is to have 2 FrameLayouts in the activity_main.xml, and have the 2nd container start as layout_width="0" and layout_height="0", but I feel this is a bad way to do it. Is there any way to replace the LinearLayout itself with another LinearLayout that has 2 FrameLayouts in it, and then assign the appropriate fragments to those containers?
Or what is the best way to do it?
I want to do things "the right way" (so I do not want to have separate activities) and I'm targeting API 16, but I may be willing to go up to API 23/24.
You can use weightSum property of LinearLayout to divide in 80:20 ratio.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="3"
android:orientation="vertical">
<FrameLayout
android:id="#+id/frame1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"/>
<FrameLayout
android:id="#+id/frame2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
For using the same layout where you want to give 100% you can set the visibility of frame2 to GONE and can dynamically set the layout of frame1 to 3 (total of weightSum which will make it 100%).

Android View rendering errors on screen reorientation?

I'm getting this weird behaviour on a very basic app, where the views render wrongly after a screen reorientation.
The code in the app works as expected when the screen reorients (with activity destruction/creation, since it's not intercepting those events) except for the view display.
When the screen reorients, the view works normally, only with an overlayed ghost image of whatever it looked like when the screen reoriented. It looks like a "burned in" CRT ghost image.
Launching the app in any orientation works normally, the issue only occurs when the screen is reoriented while it is running.
The views are used in a Fragment, which is the only one in its Activity. It happens the same for ListView, DatePicker, and TextView.
The issue occurs in multiple devices:
- nexus 4 running 4.4.3 stock android
- samsung galaxy s running cyanogenmod 10.2.1 (android 4.3.1)
- "galaxy nexus" AVD with API level 18 (android 4.3)
Any idea what is going on here? What more information would be useful for troubleshooting this?
Screenshots below:
layout file for the ListView example below:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/list_expenses"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- Row for Buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="3dp" >
<Button
android:id="#+id/list_expenses_button_add"
android:layout_width="0dip"
android:layout_height="40dp"
android:layout_weight="1"
android:text="#string/label_button_listexpenses_add" />
</LinearLayout>
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/label_listexpenses_noexpenses"
android:gravity="center" />
</LinearLayout>
The issue was that, due to the following code in the Activity classes, the fragment was being repeatedly added to the layout, which resulted in overlapping views (not rendering errors, after all):
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_expenses);
if (getSupportFragmentManager().findFragmentById(R.id.list_expenses) == null) {
fragment = ListExpensesFragment.newInstance();
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment).commit();
} else {
fragment = getSupportFragmentManager().findFragmentById(R.id.list_expenses);
}
}
Solution: replaced the call to getSupportFragmentManager().beginTransaction().add() by getSupportFragmentManager().beginTransaction().replace().

navigate between conversations and see previous messages

I have list of conversations, and messenger UI.. what I want is: When an item of the list of conversations clicked, the messenger UI appear with that specific conversation messages..
my attempt: Until now I haven't code this part, I just look for an idea,is it good Idea to save each message immediately to DB, and when I navigate between the conversations I drop the appropriate messages?
Recently I've been working in something similar. For achieving this I've used a TabHost, with a FrameLayout associated to each one that might be converted to a TextView, which is what I suppose you want.
This approach has a handicap: You can access just to the currently opened tab's view (in this case, a TextView). If you don't plan to add text to an inactive tab, you don't need to do anything further as TabHost handles each tab's previous messages. But for instance, if you want to add messages to an inactive tab, you have to store them first in a queue and once the tab is activated, process them with an OnTabChangedListener event.
That's my definition for the TabHost structure:
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<LinearLayout
android:id="#+id/TabContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="99"
android:orientation="vertical">
<TabHost
android:id="#+android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/TabLinearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- This makes your TabHost horizontally scrollable if it reaches a certain number of tabs -->
<HorizontalScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
android:scrollbars="none">
<TabWidget
android:id="#+android:id/tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></TabWidget>
</HorizontalScrollView>
<!-- You may also want to make this FrameLayour vertically scrollable -->
<FrameLayout
android:id="#+android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"></FrameLayout>
</LinearLayout>
</TabHost>
</LinearLayout>
If you need to process events on a tabchange, you should have something like this:
final TabHost th = (TabHost) (this.findViewById(android.R.id.tabhost));
th.setOnTabChangedListener(new OnTabChangeListener() {
#Override
public void onTabChanged(final String tabId) {
// Here you do the stuff you need. tabId is the name (also called Indicator) of the activated tab
}
});
You should also be aware of controlling the buffer of each tab. That is, control that each tab doesn't have more than X messages, as if it grows without control your app may start being irresponsive.
These links might help you as they did to me when I was programming this part:
https://gist.github.com/jerolimov/618086
http://androidituts.com/android-tab-layout-example/
http://learnandroideasily.blogspot.com.es/2013/07/android-tabwidget-example.html
I guess saving convs in DB isn't a good idea. You should serialize the data or should you use files to save'em.? Or rather, you can use delimiters to have a random access.

Android: How do you mix ViewPager layout with non-ViewPager layout for different device orientations?

The goal is to display two Fragments (Frag A, Frag B) on the screen in a different layout depending on the screen orientation (portrait or landscape).
When the device is in Portrait mode, only one of Frag A and Frag B is displayed at a time using swiping to switch between UIs. ViewPager works perfectly for this.
When the device is in Landscape mode, Frag A is displayed on the left of the screen and Frag B is displayed on the right. A simple LinearLayout works perfectly for this.
The problem arises when trying to get both working in the same application. It seems like the ViewPager class must be referred to in the Java source code (in the onCreate method). This means that a UI element is defined in both the XML and the source code... which seems like a problem when the device is in Landscape mode and there should not be ViewPager object but one is referred to in the source code. Am I approaching this correctly? If one layout is using ViewPager, does every other layout need a ViewPager?
layout/activity_mymain.xml
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MyMainActivity" >
<!--
This title strip will display the currently visible page title, as well as the page
titles for adjacent pages.
-->
<android.support.v4.view.PagerTitleStrip
android:id="#+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#33b5e5"
android:paddingBottom="4dp"
android:paddingTop="4dp"
android:textColor="#fff" />
</android.support.v4.view.ViewPager>
layout-land/activity_mymain.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<fragment android:name="com.example.ActionFragment"
android:id="#+id/action_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.SummaryFragment"
android:id="#+id/summary_fragment"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
From MyMainActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_topmain);
// Create the adapter that will return a fragment for each of the three
// primary sections of the app.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
}
ViewPager is used to instantiate View objects at runtime, so it needs to be defined in the code.
What I was trying to do is very similar to the different tablet/handset layouts described in the Fragments Guide, where they recommend using separate Activities.
(If anyone knows of a way to handle swiping entirely in XML, that would be a much better solution)

Android - vertical orientation works, horizontal orientation blank

I am implementing a ViewPagerIndicator that works as expected in vertical orientation. When I test in horizontal orientation, either by switching at run-time (physically turning the phone) or when the app starts in horizontal mode (phone was horizontal before the app started) I get a blank white space where the ViewPagerIndicator should be. I have a layout/main.xml and layout-land/main.xml with a few buttons that are displaying as expected (stacked vertically in vertical mode, as a grid in horizontal mode). When I press the "edit" button, I launch the edit activity:
public class ATEditActivity extends Activity {
static final int OFFSCREEN_PAGE_LIMIT = 5;
ViewPager pager = null;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.edit);
int iPage = 2; // set the page based on caller's intentions
Bundle bndlExtras = getIntent().getExtras();
if (bndlExtras != null) {
iPage = bndlExtras.getInt("PAGE");
}
ATViewPagerAdapter adapter = new ATViewPagerAdapter(this);
pager = (ViewPager) findViewById(R.id.awesomepager);
pager.setOffscreenPageLimit(OFFSCREEN_PAGE_LIMIT);
pager.setAdapter(adapter);
TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.TitlePageIndicator);
indicator.setViewPager(pager);
indicator.setCurrentItem(iPage);
}
}
edit.xml:
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#FFFFFF"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp">
<EditText
android:id="#+id/etName"
android:inputType="textAutoComplete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/edit_text"
android:layout_margin="4dp"
android:hint="#string/name" />
<com.viewpagerindicator.TitlePageIndicator
android:id="#+id/TitlePageIndicator"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="#style/Widget.MyTitlepageIndicator" />
<android.support.v4.view.ViewPager
android:id="#+id/awesomepager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Any thoughts on why the ViewPagerIndicator would not display in horizontal mode? Is more information needed?
You probably need to override onConfigurationChanged() see reference as your app needs to retrieve the resources again and draw everything.
After three or four days of trying everything I could imagine, I finally decided there must have been something really messed up in my project so I created a new project that only had the minimal amount of code necessary to get the ViewPagerIndicator working. The new project is now working with both orientations "out-of-the-box". I have no idea what caused my old project to go haywire with only the horizontal orientation but I will get the new project on track and up to speed in short order now that it is working as expected.

Categories

Resources