List Fragment inside FragmentTabHost not updating when adapter changed - android

I have a FragmentActivity that contains a Fragment.
this fragment contains a FragmentTabHost.
The tab host contains three tabs that each one is a ListFragment.
The lists are configured with custom adapter that getting updates from the main FragmentActivity, and calling notifyDatasetChanged on update.
My problem is that it seems the ListFragment UI never updates the view when it's shown.
If I change tabs - I can see the list updates, but I can't see the updates on real time when a list is shown on the screen.
That's the definition of the Fragment containing the FragmentTabHost:
public class SubOptionFragmentManager extends Fragment
{
FragmentTabHost mTabHost;
SoiListFragment mFrag1;
SoiListFragment mFrag2;
SoiListFragment mFrag3;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.soi_container, container, false);
}
public void init(FragmentManager fm, int optionInstanceID)
{
mOptionInstanceID = optionInstanceID;
// find and setup the tabhost
mTabHost = (FragmentTabHost)getActivity().findViewById(android.R.id.tabhost);
mTabHost.setup(getActivity(), getChildFragmentManager(), R.id.realtabcontent);
// add the three tabs
mTabHost.addTab(mTabHost.newTabSpec("1")
.setIndicator("1"),
SoiListFragment.class,
getTabBundle("1", optionInstanceID));
mTabHost.addTab(mTabHost.newTabSpec(dsTag)
.setIndicator("2"),
SoiListFragment.class,
getTabBundle("2", optionInstanceID));
mTabHost.addTab(mTabHost.newTabSpec("3")
.setIndicator("3"),
SoiListFragment.class,
getTabBundle("3", optionInstanceID));
// get reference to the fragments added
mFrag1 = (SoiListFragment)getChildFragmentManager().findFragmentByTag("1");
mFrag2 = (SoiListFragment)getChildFragmentManager().findFragmentByTag("2");
mFrag3 = (SoiListFragment)getChildFragmentManager().findFragmentByTag("3");
}
public void HandleSoiItemUpdate(SubOptionInstanceItem soi, int fragID)
{
Log.i(TAG, "ItemUpdate:" + soi.toString());
updateItemRunnable ur = new updateItemRunnable(soi, int fragID);
getActivity().runOnUiThread(ur);
}
class updateItemRunnable implements Runnable
{
SubOptionInstanceItem mItem;
int mFragID;
public updateItemRunnable (SubOptionInstanceItem item, int fragID)
{
mItem = item;
}
public void run()
{
switch (mFragID)
{
// the second parameter - true means it calls notifyDatasetChanged() after updating the item
case 1:
mFrag1.getAdapter().updateItem(mItem, true);
break;
case 2:
mFrag2.getAdapter().updateItem(mItem, true);
break;
case 3:
mFrag3.getAdapter().updateItem(mItem, true);
break;
}
}
}
}
Also included in that class is the ListFragment object:
public static class SoiListFragment extends ListFragment
{
private int mOptionInstanceID;
private int mPageType;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mOptionInstanceID = getArguments().getInt(FIELD_OPTION_INSTANCE_ID);
mPageType = getArguments().getInt(FIELD_PAGE_TYPE);
}
public SubOptionInstanceAdapter getAdapter()
{
return (SubOptionInstanceAdapter)getListAdapter();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// inflate the list view and return it
View v = inflater.inflate(R.layout.soi_list, container, false);
init();
return v;
}
public void init()
{
SubOptionInstanceAdapter mAdapter = null;
switch(mPageType)
{
case 1:
mAdapter = new SubOptionInstanceAdapter(getActivity(),
R.layout.soi_item,
Globals.getList1());
break;
case 2:
mAdapter = new SubOptionInstanceAdapter(getActivity(),
R.layout.soi_item,
Globals.getList2());
break;
case 3:
mAdapter = new SubOptionInstanceAdapter(getActivity(), R.layout.soi_item, Globals.getList3());
break;
}
setListAdapter(mAdapter);
}
The fragment layout "soi_container.xml" (simple layout containing FragmentTabHost):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#color/background" >
<android.support.v4.app.FragmentTabHost
android:id="#android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0" />
</android.support.v4.app.FragmentTabHost>
<FrameLayout
android:id="#+id/realtabcontent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

My first problem was the init() placement on the onCreateView part.
That caused the init() to occur each time a tab was loaded or changed - that's why I thought that the tab is being updated only when not visible.
Also, getting the ListFragments reference on init() in the container was wrong, as the fragments were not created yet on that point.
Finally my solution was to create the tabs on the container fragment during the OnStart event, getting the list fragment reference and initialize them with adapter in the onTabCreated event then updating them using the reference I got before.
EDIT: code sample for Ravi - getting the list fragment reference
public void onTabCreated(int pageType)
{
// if I don't already have the reference for the fragment
if (mFrag1 == null)
{
mFrag1 = (SoiListFragment)getFragmentManager().findFragmentByTag("1");
}
}

Related

Options menu inflate wrongly in viewpager2

I have 4 fragments but for each fragments it has different different menu options in my app with viewpager2 so the user swipe through these all fragments and when the app starts the menu option for the first fragment showing the wrong which is 4th fragment menu option item that wrongly inflated in 1st fragment whenever I open the app it only occur when I open the app freshly but when I swipe to 2nd fragment and come back to first then it work correctly but at start/opening the app firstly then it shows wrong menu item btw I using setOffScreenPageLimit(4) this problem occurs when I use this method I hope anyone could solve this problem
I guess this is my situation
I tried this ->
How to correctly inflate menus for an action bar from viewpager fragments
but it shouldn't work
I am giving you here full source code for your problem. Give it a try and hope it might help you. Create an options menu according to your requirement.
Follow below steps
1 - activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2 - MainActivity.java
public class ViewPagerActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.viewpager_activity);
initViewPager();
}
private void initViewPager() {
ViewPager2 viewPager = findViewById(R.id.viewPager);
ViewpagerAdapter adapter = new ViewpagerAdapter(this);
viewPager.setAdapter(adapter);
// Add menu items without overriding methods in the Activity
addMenuProvider(new MenuProvider() {
#Override
public void onCreateMenu(#NonNull Menu menu, #NonNull MenuInflater menuInflater) {
// Add menu items here
switch (viewPager.getCurrentItem()) {
case 0:
getMenuInflater().inflate(R.menu.menu_one, menu);
break;
case 1:
getMenuInflater().inflate(R.menu.menu_two, menu);
break;
case 2:
getMenuInflater().inflate(R.menu.menu_three, menu);
break;
case 3:
getMenuInflater().inflate(R.menu.menu_four, menu);
break;
}
}
#Override
public boolean onMenuItemSelected(#NonNull MenuItem menuItem) {
return false;
}
});
}
}
3 - Your viewpager2 adapter
public class ViewpagerAdapter extends FragmentStateAdapter {
public ViewpagerAdapter(#NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
#NonNull
#Override
public Fragment createFragment(int position) {
return MyFragment.newInstance(position);
}
#Override
public int getItemCount() {
return 4;
}
}
4 - Your fragment
public class MyFragment extends Fragment {
private int fragmentPos = 0;
public MyFragment(int fragmentPos) {
this.fragmentPos = fragmentPos;
}
public static MyFragment newInstance(int fragmentPos) {
Bundle args = new Bundle();
MyFragment fragment = new MyFragment(fragmentPos);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_view, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setHasOptionsMenu(true);
AppCompatTextView label = view.findViewById(R.id.tvFragPos);
label.setText("Current fragment index \n" + fragmentPos);
}
}
Output

How to prevent creating 2 instances of same fragment?

I have an activity (MainActivity) that contains MasterFragment which contains a viewpager with FragmentA and FragmentB in portrait screen orientation.
In landscape mode the viewpager contains only FragmentA on left side of a split screen, with FragmentB on the right side.
So basically FragmentB is moved to the right of the viewpager in landscape mode.
Although FragmentB is only shown once in each rotation, two instances are created at the same time after rotation.
The problem is that FragmentB is in reality a map, and I need to prevent 2 instances to be created at the same time. I need the first instance to be destroyed before the next instance is created.
What happens is the FragmentStateManager recreates FragmentB when calling setContentView in MainActivity.
How do I prevent that?
One solution would be to use super.onCreate(null) in MainActivity, but that is clearly an overkill.
How can I prevent recreating fragments in ViewPager2?
Another solution would be to use the recreated fragment instance and move it from the viewpager to the framlayout and vice versa. How can I move it?
MasterFragment.java
public class MasterFragment extends Fragment
{
NewPagerAdapter mSectionsPagerAdapter;
ViewPager2 mViewPager;
boolean mSplitView;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.masterfragment, container, false);
if (isLandScape())
{
mSplitView = true;
getChildFragmentManager().beginTransaction().replace(R.id.container, new FragmentB(), FragmentB.TAG).commit();
}
else if (isLandScape())
{
LinearLayout masterlayout = view.findViewById(R.id.masterlayout);
masterlayout.removeViewAt(1);
}
mViewPager = view.findViewById(R.id.pager);
mSectionsPagerAdapter = new NewPagerAdapter(getChildFragmentManager(), getLifecycle());
mViewPager.setOffscreenPageLimit(7);
mViewPager.setAdapter(mSectionsPagerAdapter);
return view;
}
public boolean isLandScape()
{
int orientation = getResources().getConfiguration().orientation;
return orientation == Configuration.ORIENTATION_LANDSCAPE;
}
public boolean backOnePage()
{
if(mViewPager == null)
return false;
int page = mViewPager.getCurrentItem();
if(page > 0)
{
mViewPager.setCurrentItem(page - 1);
return true;
}
return false;
}
public void viewFragmentB()
{
if(!mSplitView)
mViewPager.setCurrentItem(1);
}
public class NewPagerAdapter extends FragmentStateAdapter
{
public NewPagerAdapter(#NonNull FragmentManager fragmentManager, #NonNull Lifecycle lifecycle)
{
super(fragmentManager, lifecycle);
}
#NonNull
#Override
public Fragment createFragment(int position)
{
if(position == 0)
return new FragmentA();
return new FragmentB();
}
#Override
public int getItemCount()
{
return mSplitView ? 1 : 2;
}
}
}
masterfragment.xml (Portrait)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff000000"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/pager"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
/>
</LinearLayout>
masterfragment.xml (Landscape)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/masterlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:background="#ff000000">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/pager"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="0.5"
/>
<FrameLayout
android:id="#+id/container"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="0.5"/>
</LinearLayout>
MainActivity.java
public class MainActivity extends FragmentActivity
{
MasterFragment mMasterFragment;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
Fragment fragment = savedInstanceState != null
? getSupportFragmentManager().getFragment(savedInstanceState, "MasterFragment")
: null;
mMasterFragment = fragment instanceof MasterFragment
? (MasterFragment)fragment
: (MasterFragment)getSupportFragmentManager().findFragmentById(R.id.masterfragment);
}
#Override
public void onBackPressed()
{
if(mMasterFragment != null && mMasterFragment.backOnePage())
return;
super.finish();
}
}
FragmentA
public class FragmentA extends Fragment
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_home, container, false);
view.findViewById(R.id.title).setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view)
{
MasterFragment.getInstance().viewFragmentB();
}
});
return view;
}
}
FragmentB
public class FragmentB extends Fragment
{
public static String TAG = "OrderFragment";
static int COUNTER;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_crew, container, false);
COUNTER++; // COUNTER BECOMES 2
return view;
}
#Override
public void onDestroy()
{
COUNTER--;
super.onDestroy();
}
}
main.xml
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:name="com.mobile.MasterFragment"
android:id="#+id/masterfragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
I think your problem is that you're always doing new FragmentB() or new Fragment...() instead of checking if it's already in the fragmentManager.
You have to do something like (please excuse my kotlin pseudocode)
var fragmentB = fragmentManager.findFragmentByTag("FragB")
if fragmentB == null {
fragmentB = // create new instance
}
fragmentManager.replace(..., fragmentB, "FragB") //use the same tag you'll use later to search for it
You need to use setRetainInstance in fragment that you want to retain over Activity configuration change.
Source
Edit
Thanks #martin for pointing that setRetainInstance is deprecated.
The requirement to prevent fragment recreation over configuration change is unachievable as per my understanding. I would suggest to maintain the fragment state via view model instance as per Android Doc's suggestion
Seems I need to answer my own question.
As some mentioned this is not directly supported by the Android platform.
Some possibilities are:
super.onCreate(null) in MainActivity (which will disable all state restore)
Write custom viewpager/adapter that omit the fragment in saving state (a lot of work)
After rotation, move fragment from/to Viewpager to/from Framelayout, but this will require customer viewpager/adapter that allows removing fragment view without destroying it (see moving view Android Fragment - move from one View to another?), which is presumably a lot of work

ViewPager's Fragment's view lost when ViewPager's parent Fragment hidden then shown

I've been seeing some strange behavior with my ViewPager along with my own FragmentStatePagerAdapter.
My View hierarchy goes like this:
-> (1) Fragment root view (RelativeLayout)
-> (2) ViewPager
-> (3) ViewPager's current fragment view
When the Fragment that is responsible for the Fragment root view (1) gets hidden (using .hide() in a fragment transaction) and then shown (with .show()), the fragment view that was currently showing in the ViewPager (3) becomes null, although the fragment still exists. Basically, my ViewPager becomes completely blank/transparent.
The only way I have found to fix this is to call
int current = myViewPager.getCurrentItem();
myViewPager.setAdapter(myAdapter);
myViewPager.setCurrentItem(current);
after the parent fragment is shown. This somehow triggers the views to be recreated and appear on screen. Unfortunately, this occasionally causes exceptions dealing with the pager adapter calling unregisterDataSetObserver() twice on an old observer.
Is there a better way to do this? I guess what I am asking is:
Why are my fragment views inside my ViewPager getting destroyed when the parent fragment of the ViewPager is hidden?
Update: this also happens when the application is "minimized" and then "restored" (by pressing the home action key and then returning).
Per request, here's my pager adapter class:
public class MyInfoSlidePagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<MyInfo> infos = new ArrayList<MyInfo>();
public MyInfoSlidePagerAdapter (FragmentManager fm) {
super(fm);
}
public MyInfoSlidePagerAdapter (FragmentManager fm, MyInfo[] newInfos) {
super(fm);
setInfos(newInfos);
}
#Override
public int getItemPosition(Object object) {
int position = infos.indexOf(((MyInfoDetailsFragment)object).getMyInfo());
return position > 0 ? position : POSITION_NONE;
}
#Override
public CharSequence getPageTitle(int position) {
return infos.get(position).getName();
}
#Override
public Fragment getItem(int i) {
return infos.size() > 0 ? MyInfoDetailsFragment.getNewInstance(infos.get(i)) : null;
}
#Override
public int getCount() {
return infos.size();
}
public Location getMyInfoAtPosition(int i) {
return infos.get(i);
}
public void setInfos(MyInfo[] newInfos) {
infos = new ArrayList<MyInfo>(Arrays.asList(newInfos));
}
public int getPositionOfMyInfo(MyInfo info) {
return infos.indexOf(info);
}
}
I've renamed some variables but other than that it is exactly what I have.
You're not providing enough info for your specific issue, so I built a sample project that tries to reproduce your issue: the app has an activity that holds a fragment (PagerFragment) within a relative layout and below this layout I have a button that hides & shows above PagerFragment. PagerFragment has a ViewPager and each fragment within pager adapter simply displays a label - this fragment is named DataFragment. The label list is created in parent activity and passed to PagerFragment and then through its adapter to each DataFragment. Changing the PagerFragment visibility is done with no issues and each time it's becoming visible again it shows the previous shown label.
The key of the issue:
Use Fragment#getChildFragmentManager() when you're creating the viewpager adapter and not getFragmentManager!
Maybe you can compare this simple project with what you have and check where are the differences. So here goes (top-down):
PagerActivity (the only activity in the project):
public class PagerActivity extends FragmentActivity {
private static final String PAGER_TAG = "PagerActivity.PAGER_TAG";
#Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.pager_activity);
if (savedInstance == null) {
PagerFragment frag = PagerFragment.newInstance(buildPagerData());
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction().add(R.id.layout_fragments, frag, PAGER_TAG).commit();
}
findViewById(R.id.btnFragments).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
changeFragmentVisibility();
}
});
}
private List<String> buildPagerData() {
ArrayList<String> pagerData = new ArrayList<String>();
pagerData.add("Robert de Niro");
pagerData.add("John Smith");
pagerData.add("Valerie Irons");
pagerData.add("Metallica");
pagerData.add("Rammstein");
pagerData.add("Zinedine Zidane");
pagerData.add("Ronaldo da Lima");
return pagerData;
}
protected void changeFragmentVisibility() {
Fragment frag = getSupportFragmentManager().findFragmentByTag(PAGER_TAG);
if (frag == null) {
Toast.makeText(this, "No PAGER fragment found", Toast.LENGTH_SHORT).show();
return;
}
boolean visible = frag.isVisible();
Log.d("APSampler", "Pager fragment visibility: " + visible);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (visible) {
ft.hide(frag);
} else {
ft.show(frag);
}
ft.commit();
getSupportFragmentManager().executePendingTransactions();
}
}
its layout file pager_activity.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="4dp" >
<Button
android:id="#+id/btnFragments"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="Hide/Show fragments" />
<RelativeLayout
android:id="#+id/layout_fragments"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/btnFragments"
android:layout_marginBottom="4dp" >
</RelativeLayout>
</RelativeLayout>
Observe that I am adding the PagerFragment when the activity is first shown - and the PagerFragment class:
public class PagerFragment extends Fragment {
private static final String DATA_ARGS_KEY = "PagerFragment.DATA_ARGS_KEY";
private List<String> data;
private ViewPager pagerData;
public static PagerFragment newInstance(List<String> data) {
PagerFragment pagerFragment = new PagerFragment();
Bundle args = new Bundle();
ArrayList<String> argsValue = new ArrayList<String>(data);
args.putStringArrayList(DATA_ARGS_KEY, argsValue);
pagerFragment.setArguments(args);
return pagerFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
data = getArguments().getStringArrayList(DATA_ARGS_KEY);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.pager_fragment, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
pagerData = (ViewPager) view.findViewById(R.id.pager_data);
setupPagerData();
}
private void setupPagerData() {
PagerAdapter adapter = new LocalPagerAdapter(getChildFragmentManager(), data);
pagerData.setAdapter(adapter);
}
}
its layout (only the ViewPager that takes full size):
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager_data"
android:layout_width="match_parent"
android:layout_height="match_parent" />
and its adapter:
public class LocalPagerAdapter extends FragmentStatePagerAdapter {
private List<String> pagerData;
public LocalPagerAdapter(FragmentManager fm, List<String> pagerData) {
super(fm);
this.pagerData = pagerData;
}
#Override
public Fragment getItem(int position) {
return DataFragment.newInstance(pagerData.get(position));
}
#Override
public int getCount() {
return pagerData.size();
}
}
This adapter creates a DataFragment for each page:
public class DataFragment extends Fragment {
private static final String DATA_ARG_KEY = "DataFragment.DATA_ARG_KEY";
private String localData;
public static DataFragment newInstance(String data) {
DataFragment df = new DataFragment();
Bundle args = new Bundle();
args.putString(DATA_ARG_KEY, data);
df.setArguments(args);
return df;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
localData = getArguments().getString(DATA_ARG_KEY);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.data_fragment, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
view.findViewById(R.id.btn_page_action).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getActivity(), localData, Toast.LENGTH_SHORT).show();
}
});
((TextView) view.findViewById(R.id.txt_label)).setText(localData);
}
}
and DataFragment's layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="4dp" >
<Button
android:id="#+id/btn_page_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="Interogate" />
<TextView
android:id="#+id/txt_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
Enjoy coding!
maybe it will help mViewPager.setOffscreenPageLimit(5)
Set the number of pages that should be retained to either side of the
current page in the view hierarchy in an idle state. Pages beyond this
limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number
of pages you will need to support or have lazy-loading mechanisms in
place on your pages, tweaking this setting can have benefits in
perceived smoothness of paging animations and interaction. If you have
a small number of pages (3-4) that you can keep active all at once,
less time will be spent in layout for newly created view subtrees as
the user pages back and forth.
You should keep this limit low, especially if your pages have complex
layouts. This setting defaults to 1.
View Pager is pretty adamant in keeping keeping its Fragments fresh always and thus optimizing the performance by freeing up memory when a fragment is not used. Clearly that is a valid useful trait in a mobile system. But due to this persistent deallocation of resources the fragment is created everytime it gains focus.
mViewPager.setOffscreenPageLimit(NUMBEROFFRAGMENTSCREENS);
Here is the documentation.
this Old Post has an interesting Solution for your problem.. Please Refer
For me i changed to getChildFragmentManager() instead of getFragmentManager()
and works good.
Ex:
pagerAdapt = new PagerAdapt(getChildFragmentManager());
I had the same problem. My app (FragmentActivity) has a pager (ViewPager) with 3 framgents. While swiping between the fragments they are destroyed and recreated all the time. Actually it makes no problem in functionality (expect unclosed Cursors), but I was also wondering about this question.
I do not know if there is a workaround to change the behavior of the ViewPager, but I suggest to have a configuration object (maybe a static on) and before destroy save your myViewPager object at the config object.
public class App extends FragmentActivity {
static MyData data;
#Override
protected void onCreate(Bundle savedInstanceState) {
data = (MyData) getLastCustomNonConfigurationInstance();
if (data == null) {
data = new MyData();
data.savedViewPager = myViewPager;
} else {
myViewPager = data.savedViewPager;
}
}
#Override
public Object onRetainCustomNonConfigurationInstance() {
Log.d("onRetainCustomNonConfigurationInstance", "Configuration call");
return data;
}
}
public class MyData {
public ViewPager savedViewPager;
}
With this way, you can save the reference to the an object which won't be destroyed hence there is reference to it and you can reload all your crucial objects.
I hope you find my suggestion useful!

Two list view at a time with half sliding viewpager android

Can it be possible to slide the viewpager half of the screen?
My ultimate goal is to display two list view at a time, after first page slide, left list would be the previous list.
So Like as below..
list1,list2
list2,list3
list3,list4
Any solutions?
Thanks
Okay, I am going to take a stab at this. I accomplished what (I think) you are trying to do. My application has 3 ListViews, and each list contains different content fetched from an online source and populates a ViewPager using custom adapters and ListViews. The custom adapter is then assigned to a fragment on a PagerAdapter. I copied a lot of my code from a Google resource, and will try to outline what I did.
First, I added a ViewPager to my layout for my MainActivity
activity_main.xml:
<android.support.v4.view.ViewPager
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- add a PagerTitleStrip -->
<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.support.v4.view.ViewPager>
Then, I created a separate ListView layout I could use for my custom adapters:
listview.xml
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#E6E6E6"
android:background="#E6E6E6"
tools:context=".MainActivity" />
After I had these set, I dug into my activity. The rest takes place within MainActivity.java:
First, lay out some variables:
public class MainActivity extends FragmentActivity implements OnNavigationListener {
// your pager adapter
SectionsPagerAdapter mSectionsPagerAdapter;
ViewPager mViewPager;
// your custom adapters (look this up on your own if you do not understand)
ArrayList<ListEntry> listOneArrayList = null;
ArrayList<ListEntry> listTwoArrayList = null;
CustomAdapterListOne customAdapterListOne = null;
CustomAdapterListTwo customAdapterListTwo = null;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// more on that in the next block...
}
}
Now, let's get into onCreate() and start creating!
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// set up your pager adapter
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.viewpager);
mViewPager.setAdapter(mSectionsPagerAdapter);
// if you want to set a default view:
mViewPager.setCurrentItem(0);
// now, run some AsyncTasks to load up our lists
// I use AsyncTasks because I fetch my data from a server
new generateListOne().execute();
new generateListTwo().execute();
}
/*
* Get the entries and create a list adapter
*/
private class generateListOne extends AsyncTask<String, Void, Object> {
#Override
protected Object doInBackground(String... args) {
listOneArrayList = new ArrayList<ListEntry>();
// this is where I would do all of my networking stuff
// and populate my arraylist
return null;
}
#Override
protected void onPostExecute(Object result) {
// you have to create a new xml layout for 'listview_row' to use here v
customAdapterListOne = new CustomAdapterListOne(self, R.layout.listview_row, listOneArrayList);
/** Very important! This is where you specify where the list goes: **/
// * Note: Fragment pages start at 0!
ListSectionFragment fragment = (ListSectionFragment) getSupportFragmentManager().findFragmentByTag(
"android:switcher:"+R.id.viewpager+":0"); // <- this is where you specify where the list goes
if (fragment != null) { // <- Could be null if not instantiated yet
if(fragment.getView() != null) {
customAdapterListOne.notifyDataSetChanged();
fragment.updateListOneDisplay(customAdapterListOne);
}
}
}
}
I'm not going to write out generateListTwo(), but hopefully you understand the concept from generateListOne(). Pay very close attention to what is happening in onPostExecute(). Now, we have to write out the FragmentPagerAdapter and our ListSection Fragment. Also, we have to include our custom list Adapter. All of that stuff follows:
/*
* Your Custom Adapter Class
*/
private class CustomAdapterListOne extends ArrayAdapter<ListEntry> {
/*
* Read up on the rest of this for custom adapter if you
* are unfamilar. There are plenty of resources..
*
* I am not going to type it all out.
*/
}
/*
* SectionsPagerAdapter class for FragmentPagerAdapter title
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new ListSectionFragment();
Bundle args = new Bundle();
args.putInt(ListSectionFragment.ARG_SECTION_NUMBER, i + 1);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
// make sure this is correct
int yourNumberOfLists = 5;
return yourNumberOfLists;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0: return "First List";
case 1: return "Second List";
//case 2: etc..
}
return null;
}
public boolean onInterceptTouchEvent(MotionEvent event) {
return false;
}
}
/*
* ListSectionFragment class for ListFragment(s)
*/
public static class ListSectionFragment extends ListFragment {
public static final String ARG_SECTION_NUMBER = "section_number";
public static int CURRENT_SECTION = 0;
static ListSectionFragment newInstance(int num) {
ListSectionFragment fragment = new ListSectionFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
public void updateListOneDisplay(ArrayAdapter<ListEntry> listOneAdapter) {
setListAdapter(listOneAdapter);
}
public void updateListTwoDisplay(ArrayAdapter<ListEntry> listTwoAdapter) {
setListAdapter(listTwoAdapter);
}
// etc..
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle args = getArguments();
CURRENT_SECTION = args.getInt(ARG_SECTION_NUMBER);
// note, we are using your listview here v
View view = inflater.inflate(R.layout.listview, container, false);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// and just for kicks:
Log.i(TAG, "Item clicked: " + position);
}
}
Don't forget your last } to close out the MainActivity.java class. Hopefully this helps someone, I know it took me forever to figure out. The effect that this code provides is similar to that of the Android Place application.
Edit: I forgot to mention when the list loads. When a list gains focus, it also loads the previous and next list. This makes it possible to transition to it and have it already be there ready to go. For example:
You go to list 2 and list 1 and list 3 are loaded. You then go to list 3 (and it transitions smoothly because it is loaded already), and list 4 and list 2 are loaded. This ensures that when you transition to a new list, it is already loaded or in the process of being generated.

Getting error while adding fragments dynamically -java.lang.IllegalStateException:

I'm working on 3.0 for the first time.
I want to add fragments dynamically , but its shown error:-
10-18 18:29:11.215: ERROR/AndroidRuntime(3550): java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
XML code
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/frags">
<ListView
android:id="#+id/number_list"
android:layout_width="250dip"
android:layout_height="match_parent" />
<FrameLayout
android:id="#+id/the_frag"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Activity
public class FragmentExampleActivity extends Activity implements
OnItemClickListener {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView l = (ListView) findViewById(R.id.number_list);
ArrayAdapter<String> numbers = new ArrayAdapter<String>(
getApplicationContext(), android.R.layout.simple_list_item_1,
new String[] { "one", "two", "three", "four", "five", "six" });
l.setAdapter(numbers);
l.setOnItemClickListener(this);
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fragment f = new FragmentExample();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.the_frag, f);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
}
public class FragmentExample extends Fragment {
private int nAndroids=1;
public FragmentExample() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved) {
int n;
View l = inflater.inflate(R.layout.textlay,container);
TextView tv = (TextView) l.findViewById(R.id.textView1);
tv.setText("value "+nAndroids);
return l;
}
}
Plz put some light on , where i'm going wrong :(
Instead of using
View l = inflater.inflate(R.layout.textlay,container);
you should use
View l = inflater.inflate(R.layout.textlay, container, false);
or alternatively
View l = inflater.inflate(R.layout.textlay, null );
I strongly suggest you to use, the version of inflate which takes three parameters. Android use the container only for the layout's params purpose. Passing false as third parameter, prevents the addition of textlay, to container and fixes your issue
if in onCreateView() , you want to reuse view , then you can deal with onDestroyView()
#Override
public void onDestroyView() {
super.onDestroyView();
if (view != null) {
ViewGroup parentViewGroup = (ViewGroup) view.getParent();
if (parentViewGroup != null) {
parentViewGroup.removeAllViews();
}
}
}
or you can use third argument and pass it as a false.
inflater.inflate(R.layout.sample_fragment, container,false);
it means that don't attach current view to root.
The following solves multiple problems:
public static View contentView;
public static class MenuCardFrontFragment extends Fragment {
public MenuCardFrontFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
try
{
LinearLayout frontLayout = (LinearLayout)inflater.inflate(R.layout.content_card, container, false);
// modify layout if necessary
return contentView = frontLayout;
} catch (InflateException e) {
return contentView;
}
}
#Override
public void onDestroyView() {
super.onDestroyView();
if (contentView != null) {
ViewGroup parentViewGroup = (ViewGroup) contentView.getParent();
if (parentViewGroup != null) {
parentViewGroup.removeAllViews();
}
}
}
}
If fragments shows with animation and user forced to repeat the animation before it's over. ex: pressing menu button twice before the menu fragment finishes animating and appear. (this got solved by the onDestryView from user2764384)
If inflate method called with specifying the container to get container lay outing. it looks like that cases a problem because at the 2nd time that happened the old container already have a view. so if that exception happen it will be caught and returns the old view contentView.

Categories

Resources