I've got a ListFragment which provides a ContextMenu for its elements. When I put this fragment in an xml-layout everything works fine but when I add this ListFragment programmatically via the FragmentManager then this works only until the first screen-rotation. After rotating the screen I can see in the debugger that Android restores the old ListFragment AND creates a new one due to
CustomListFragment fragment = new CustomListFragment();
fragmentTransaction.add(R.id.customFragmentContainer, fragment);
where a new ListFragment gets created. When I long-press an item to open the ContextMenu, then the onCreateContextMenu method of the new ListFragment gets called and the result is passed to the onContextItemSelected method of the old ListFragment.
I think it gets clearer when I post a bit of code:
Here's my ListFragment:
public class CustomListFragment extends ListFragment {
private LayoutInflater layoutInflater;
private OnSelectedListener<String> selectionListener;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
layoutInflater = inflater;
return inflater.inflate(R.layout.list_fragment_layout, null);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(new CustomListAdapter());
registerForContextMenu(getListView());
}
public void setOnSelectedListener(OnSelectedListener<String> listener) {
selectionListener = listener;
}
private static final String item0 = "item0";
private static final String item1 = "item1";
#Override
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
menu.add(0, 0, 0, item0);
menu.add(0, 1, 0, item1);
}
#Override
public boolean onContextItemSelected(MenuItem menuItem) {
String selection;
if (menuItem.getItemId()==0)
selection = item0;
else
selection = item1;
if (selectionListener!=null)
selectionListener.onSelected(selection);
return true;
}
private class CustomListAdapter extends BaseAdapter {
private List<String> elemente = new ArrayList<String>();
public CustomListAdapter() {
elemente.add("one");
elemente.add("two");
elemente.add("three");
}
#Override
public int getCount() {
return elemente.size();
}
#Override
public Object getItem(int position) {
return elemente.get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView==null)
convertView = layoutInflater.inflate(R.layout.element, null);
TextView tv = (TextView)convertView;
tv.setText(elemente.get(position));
return tv;
}
}
}
With this Activity it works fine:
public class CustomFragmentActivity extends FragmentActivity implements OnSelectedListener<String> {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_layout);
FragmentManager fragmentManager = getSupportFragmentManager();
CustomListFragment fragment = (CustomListFragment)fragmentManager.findFragmentById(R.id.customFragment);
fragment.setOnSelectedListener(this);
}
#Override
public void onSelected(String selection) {
Toast.makeText(this, selection, Toast.LENGTH_LONG).show();
}
}
But with this Activity it only works until the first screen-rotation:
public class CustomFragmentContainerActivity extends FragmentActivity implements OnSelectedListener<String> {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_container_layout);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
CustomListFragment fragment = new CustomListFragment();
fragmentTransaction.add(R.id.customFragmentContainer, fragment);
fragmentTransaction.commit();
fragment.setOnSelectedListener(this);
}
#Override
public void onSelected(String selection) {
Toast.makeText(this, selection, Toast.LENGTH_LONG).show();
}
}
The OnSelectedListener is only an inteface which provides one single public method. After a screen-rotation the result (the selected item) is passed to the old ListFragment. But this old ListFragment is recreated by the Android system and the selectionListener is null, so nothing happens. The interface looks like this:
public interface OnSelectedListener<V> {
public void onSelected(V selection);
}
And finally, maybe I should mention that I'm using the v4 support library.
Ok, the problem is, that there's a memory leak in the support library and the onContextItemSelected()-method of the old fragments gets called. And as I return true, the newer fragments don't get this method call.
Related
My app has a tablayout with a view pager. Each page has a fragment. There are 4 different fragments, three of them are basically the same for now (I'm in the development phase right now). One of them has a RecyclerView with a basic list.
I am implementing the Two-pane template in the fragment with the RecyclerView.
Everything seems to be works]ing well. While I move across the tabs the fragments are loaded fine.
But, when I rotate the device and tap on the first tab, and then go back to the one with the recyclerview, I can see the previous intance below. See attached images.
I decided to use static final instances of the fragments in the page adapter and in the recyclerview fragment.
How can I get rid of this problem?
Thanks in advance stackers!
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
TabLayout tabLayout = findViewById(R.id.tab_layout);
tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.icon1).setText(R.string.dashboard));
tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.icon2).setText(R.string.fragment2));
tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.icon3).setText(R.string.fragmentDualPane));
tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.icon4).setText(R.string.frag4));
final ViewPager viewPager = findViewById(R.id.pager);
final PagerAdapter pageAdapter = new TabPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(pageAdapter);
tabLayout.setupWithViewPager(viewPager);
} // protected void onCreate
} // public class MainActivity
TabPagerAdapter has static final intances of the fragments
public class TabPagerAdapter extends FragmentPagerAdapter {
static final Fragment tabs[] = {new DashboardFragment(),
new Fragment2(),
new ExpensesFragment(),
new Fragment4()
};
public TabPagerAdapter(#NonNull FragmentManager fm) {
super(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
} // public TabPagerAdapter
#NonNull
#Override
public Fragment getItem(int position) {
if (position<tabs.length)
return tabs[position];
else
return null;
} // public Fragment getItem
#Override
public int getCount() {
return this.tabs.length;
} // public int getCount
} // class TabPagerAdapter
General fragment template for dashboard, fragment2, and fragment4
public class DashboardFragment extends Fragment {
public DashboardFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
} // onCreate
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_dashboard, container, false);
}
}
This is the code for the fragment with the dual pane. Look that it uses the OnItemSelected implementation of fragments communications.
This fragment loads another fragment with the recyclerview.
public class ExpensesFragment extends Fragment
implements IOnItemSelected {
#Override
public void onAccountSelected(Account item) {
System.out.println("Clicking on " + item.getTitle() + ", and isTwoPane=" + isTwoPane);
} // public void onAccountSelected
public static final String TAG="Expenses Fragment";
private boolean isTwoPane = false; // Let's assume we're on a phone
private FragmentManager fragmentManager;
private View fragmentView = null;
public ExpensesFragment() {
// Required empty public constructor
} // ExpensesFragment()
public static final ExpensesListFragment lListFragment = new ExpensesListFragment();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
} // onCreate
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
fragmentView = inflater.inflate(R.layout.fragment_expenses, container, false);
isTwoPane = fragmentView.findViewById(R.id.expensesDetailContainer) != null;
fragmentManager = getChildFragmentManager();
if (savedInstanceState==null) {
if ( !lListFragment.isAdded() ) {
fragmentManager.
beginTransaction().
add(R.id.expensesListContainer,lListFragment).
commit();
} // if ( !lListFragment.isAdded() )
} // if (savedInstanceState==null)
if ( isTwoPane ) {
fragmentManager.
beginTransaction().
replace(R.id.expensesDetailContainer,new EmptyFragment()).
commit();
} // if ( isTwoPane )
return fragmentView;
} // onCreateView
} // ExpensesFragment
And this is the fragment with the recyclerview:
public class ExpensesListFragment extends Fragment {
private IOnItemSelected mCallback;
private RecyclerView rv;
private RecyclerView.LayoutManager rvlm;
private RecyclerAdapterAccounts rva;
public ExpensesListFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCallback = (IOnItemSelected)getParentFragment();
} // onCreate
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View fragmentView = inflater.inflate(R.layout.fragment_expenses_list, container, false);
if ( isVisible() ) return fragmentView;
FragmentManager fragmentManager = getChildFragmentManager();
// Setting the recyclerview environment
rv = fragmentView.findViewById(R.id.expensesRV); // recycler view
rvlm = new LinearLayoutManager(getActivity());
rv.setLayoutManager(rvlm);
rva = new RecyclerAdapterAccounts();
rva.setCallBackFunction(mCallback);
rv.setAdapter(rva);
// Setting the floating action button and snackbar
FloatingActionButton fab = fragmentView.findViewById(R.id.fabAdd);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Snackbar.make(view, "Load a Create Item frag", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
return fragmentView;
} // onCreateView
} // public class ExpensesListFragment
The RecyclerAdapterAccounts creates a generic set of data:
public class RecyclerAdapterAccounts extends
RecyclerView.Adapter<RecyclerAdapterAccounts.ViewHolderAccounts> {
private IOnItemSelected callBackFunction;
public IOnItemSelected getCallBackFunction() {return callBackFunction;}
public void setCallBackFunction(IOnItemSelected callBackFunction) {this.callBackFunction = callBackFunction;}
class ViewHolderAccounts extends RecyclerView.ViewHolder {
ImageView icon, isRepeating, isAlert;
TextView title, total;
public Account getAccount() {return account;}
public void setAccount(Account account) {this.account = account;}
Account account;
public ViewHolderAccounts(View itemView) {
super(itemView);
icon = itemView.findViewById(R.id.list_item_ico_account);
isRepeating = itemView.findViewById(R.id.list_item_isrepeating);
isAlert = itemView.findViewById(R.id.list_item_isalert);
title = itemView.findViewById(R.id.list_item_title_account);
total = itemView.findViewById(R.id.list_item_desc_account);
account = null; // The account needs to be set using the setter/getter method
} // ViewHolderAccounts
} // class ViewHolderAccounts
List<Account> accts = new ArrayList<Account>();
ViewGroup parent;
#NonNull
#Override
public ViewHolderAccounts onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_account,parent,false);
this.parent = parent;
ViewHolderAccounts vh = new ViewHolderAccounts(v);
return vh;
} // onCreateViewHolder
#Override
public void onBindViewHolder(#NonNull ViewHolderAccounts holder, int position) {
// Look into the list the item with id=position
Optional<Account> la = accts.stream()
.filter(ac->ac.getId()==(long)position)
.findFirst();
if ( la.isPresent() ) {
int res = parent.getResources().getIdentifier(la.get().getIcon(), "drawable", "com.almonisolutions.elgddt");
holder.icon.setImageResource(res);
holder.isRepeating.setImageResource(R.drawable.automatic);
holder.isAlert.setImageResource(R.drawable.notifications);
holder.title.setText(la.get().getTitle());
holder.total.setText(la.get().getDescription());
holder.setAccount(la.get());
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (holder.getAccount() != null) {
callBackFunction.onAccountSelected(holder.getAccount());
} // if (account != null)
} // public void onClick
});
} // if
} // onBindViewHolder
#Override
public int getItemCount() {
return accts.size();
} // getItemCount
RecyclerAdapterAccounts() {
super();
for(int i=0;i<16;i++) {
Account la = new Account();
la.setId((long) i);
la.setTitle("The item number " + i);
la.setDescription("$" + (1000*i));
switch(i%3) {
case 0: la.setIcon("imaged"); break;
case 1: la.setIcon("person_old"); break;
case 2: la.setIcon("pet"); break;
default: la.setIcon("add");
} // switch
accts.add(la);
} // for
} // RecyclerAdapterAccounts
} // class RecyclerAdapterAccounts
At first, In the ExpensesFragment I was getting an Exception that throw the message "Fragment already added". When I changed the ExpensesListFragment to static final, that error was gone.
Again, to recreate the error, you need to run in portrait mode, move through the tabs. Finish on anyone but the first one. Them rotate the device. Tap on the first tab. Then tap on the 3rd one, the one with the recyclerview. Swipe through the list and you will see it is double.
Any help will be appreciated.
Thanks in advance!!!
So I found the answer. ADM (see comment above) sent me to a previous article where part of the solution was to extend ViewPager and override instantiateItem. But I did not want to extend ViewPager.
However, in the same article was another link to this other article where there was the following explanation:
Blockquote By default, [FragmentPagerAdapter] will only preload one Fragment in front and behind the current position (although it does not destroy them unless you are using FragmentStatePagerAdapter).
So, I made TabPagerAdapter extend FragmentStatePagerAdapter instead of FragmentPageAdapter... and that was it!!
Thanks ADM for pointing to the right series of articles.
I'm having some problems when I try to replace one Fragment with another one in a ViewPager.
Current situation
I have a ViewPager with 3 pages, each one is a Fragment. In first page, I have a ListView inside a ListFragment ("FacturasFragment"). When I click on an item of that list, I use onListItemClick method for handle that event.
What I want
When list item is clicked, I want to replace ListFragment (contains a list of invoices) with another Fragment ("DetallesFacturaFragment", contains details of invoice).
When I'm in "DetallesFacturaFragment" and press Back Button, should return to ListFragment.
Scrolling between pages should not change Fragment displayed in first one. It is, if I'm in first page with "DetallesFacturaFragment" and scroll to second page, when return to first one should continue displaying "DetallesFacturaFragment".
Code
FragmentActivity
public class TabsFacturasActivity extends SherlockFragmentActivity {
private MyAdapter mAdapter;
private ViewPager mPager;
private PageIndicator mIndicator;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_pager);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mIndicator = (TitlePageIndicator)findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
}
private static class MyAdapter extends FragmentPagerAdapter {
private String[] titles = { "VER FACTURAS", "VER CONSUMO", "INTRODUCIR LECTURA" };
private final FragmentManager mFragmentManager;
private Fragment mFragmentAtPos0;
private Context context;
public MyAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
mFragmentManager = fragmentManager;
}
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0
return new FacturasFragment();
case 1: // Fragment # 1
return new ConsumoFragment();
case 2:// Fragment # 2
return new LecturaFragment();
}
return null;
}
#Override
public int getCount() {
return titles.length;
}
#Override
public int getItemPosition(Object object)
{
if (object instanceof FacturasFragment && mFragmentAtPos0 instanceof DetallesFacturaFragment)
return POSITION_NONE;
return POSITION_UNCHANGED;
}
}
}
ListFragment
public class FacturasFragment extends ListFragment {
private ListView lista;
private ArrayList<TuplaFacturaWS> facturas;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.activity_facturas, container, false);
facturas = myApplication.getFacturas();
lista = (ListView) view.findViewById(id.list);
MyAdapter myAdaptador = new MyAdapter(this, facturas);
setListAdapter(myAdaptador);
return view;
}
public void onListItemClick (ListView l, View v, int position, long id) {
myApplication.setFacturaActual(position);
mostrarDatosFactura();
}
private void mostrarDatosFactura() {
final DetallesFacturaFragment fragment = new DetallesFacturaFragment();
FragmentTransaction transaction = null;
transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.pager, fragment); //id of ViewPager
transaction.addToBackStack(null);
transaction.commit();
}
private class MyAdapter extends BaseAdapter {
private final FacturasFragment actividad;
private final ArrayList<TuplaFacturaWS> facturas;
public MyAdapter(FacturasFragment facturasActivity, ArrayList<TuplaFacturaWS> facturas) {
super();
this.actividad = facturasActivity;
this.facturas = facturas;
}
#Override
public View getView(int position, View convertView,
ViewGroup parent) {
LayoutInflater inflater = actividad.getLayoutInflater(null);
View view = inflater.inflate(R.layout.list_row, null, true);
//Set data to view
return view;
}
#Override
public int getCount() {
return facturas.size();
}
#Override
public Object getItem(int pos) {
return facturas.get(pos);
}
#Override
public long getItemId(int position) {
return position;
}
private OnClickListener checkListener = new OnClickListener()
{
#Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
}
};
}
}
Fragment
public class DetallesFacturaFragment extends SherlockFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.activity_factura, container, false);
//Set data to view
return view;
}
}
At the moment, when I click on list item, white view appears in first page. I've verified and onCreateView method of "DetallesFacturaFragment" is executed, but nothing appears on that page.
And first time I click on list item, it shows that white screen. But after coming back to list, I have to click twice to a list item for showing white screen.
I've been googling and looking at some many questions but couldn't find anyone solved with completed code.
After so many hours spent, I've found correct solution modifying code in that answer.
It replaces Fragment in first page of ViewPager with another one, and if you return back from second Fragment, first Fragment is correctly displayed. Doesn't matter Fragment displayed in first page, if you swipe from one page to another, it doesn't change its Fragment.
Here is my code:
FragmentActivity
public class TabsFacturasActivity extends SherlockFragmentActivity {
public void onBackPressed() {
if(mPager.getCurrentItem() == 0) {
if (mAdapter.getItem(0) instanceof DetallesFacturaFragment) {
((DetallesFacturaFragment) mAdapter.getItem(0)).backPressed();
}
else if (mAdapter.getItem(0) instanceof FacturasFragment) {
finish();
}
}
}
private static class MyAdapter extends FragmentPagerAdapter {
private final class FirstPageListener implements
FirstPageFragmentListener {
public void onSwitchToNextFragment() {
mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
.commit();
if (mFragmentAtPos0 instanceof FacturasFragment){
mFragmentAtPos0 = new DetallesFacturaFragment(listener);
}else{ // Instance of NextFragment
mFragmentAtPos0 = new FacturasFragment(listener);
}
notifyDataSetChanged();
}
}
private String[] titles = { "VER FACTURAS", "VER CONSUMO", "INTRODUCIR LECTURA" };
private final FragmentManager mFragmentManager;
public Fragment mFragmentAtPos0;
private Context context;
FirstPageListener listener = new FirstPageListener();
public MyAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
mFragmentManager = fragmentManager;
}
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0
if (mFragmentAtPos0 == null)
{
mFragmentAtPos0 = new FacturasFragment(listener);
}
return mFragmentAtPos0;
case 1: // Fragment # 1
return new ConsumoFragment();
case 2:// Fragment # 2
return new LecturaFragment();
}
return null;
}
#Override
public int getCount() {
return titles.length;
}
#Override
public int getItemPosition(Object object)
{
if (object instanceof FacturasFragment &&
mFragmentAtPos0 instanceof DetallesFacturaFragment) {
return POSITION_NONE;
}
if (object instanceof DetallesFacturaFragment &&
mFragmentAtPos0 instanceof FacturasFragment) {
return POSITION_NONE;
}
return POSITION_UNCHANGED;
}
}
}
FirstPageFragmentListener
public interface FirstPageFragmentListener {
void onSwitchToNextFragment();
}
FacturasFragment (FirstFragment)
public class FacturasFragment extends ListFragment implements FirstPageFragmentListener {
static FirstPageFragmentListener firstPageListener;
public FacturasFragment() {
}
public FacturasFragment(FirstPageFragmentListener listener) {
firstPageListener = listener;
}
public void onListItemClick (ListView l, View v, int position, long id) {
firstPageListener.onSwitchToNextFragment();
}
}
DetallesFacturaFragment (SecondFragment)
public class DetallesFacturaFragment extends SherlockFragment {
static FirstPageFragmentListener firstPageListener;
public DetallesFacturaFragment() {
}
public DetallesFacturaFragment(FirstPageFragmentListener listener) {
firstPageListener = listener;
}
public void backPressed() {
firstPageListener.onSwitchToNextFragment();
}
}
To solve minor issue that Android suggest not using non-default constructor for fragment. You should change the constructor to something like this:
in FacturasFragment :
public static FacturasFragment createInstance(FirstPageFragmentListener listener) {
FacturasFragment facturasFragment = new FacturasFragment();
facturasFragment.firstPageListener = listener;
return facturasFragment;
}
Then you can call it from getItem function like this :
mFragmentAtPos0 = FacturasFragment.createInstance(listener);
in DetallesFacturaFragment :
public static DetallesFacturaFragment createInstance(FirstPageFragmentListener listener) {
DetallesFacturaFragment detallesFacturaFragment= new DetallesFacturaFragment();
detallesFacturaFragment.firstPageListener = listener;
return detallesFacturaFragment;
}
Then you can call it from FirstPageListener.onSwitchToNextFragment() function like this :
mFragmentAtPos0 = DetallesFacturaFragment.createInstance(listener);
I found two ways to replace fragment in viewpager.
Way 1: By updating ViewPagerAdapter
Way 2: By using a root fragment
Way1:
In this way, we need to update the fragment from viewpager adapter's fragment list and notify the fragment about the change.
For example:
private void changefragment(int position) {
Fragment newFragment = CategoryPostFragment.newInstance();
adapter.fragments.set(position, newFragment);
adapter.notifyDataSetChanged();
viewPager.setCurrentItem(position);
}
Here is the viewpager adapter code for this
import java.util.List;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.PagerAdapter;
import com.trickbd.trickbdapp.ui.activity.homeactivity.fragments.FavFragment;
import com.trickbd.trickbdapp.ui.activity.homeactivity.fragments.HomePostFragment;
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
public List<Fragment> fragments;
public ViewPagerAdapter(FragmentManager manager, List<Fragment> fragments) {
super(manager);
this.fragments = fragments;
}
#NonNull
#Override
public Fragment getItem(int position) {
if (fragments.get(position)!=null){
return fragments.get(position);
}else {
if (position==0){
return new HomePostFragment();
}else {
return new FavFragment();
}
}
}
#Override
public int getCount() {
return fragments.size();
}
//method to add fragment
public void addFragment(Fragment fragment) {
fragments.add(fragment);
}
#Override
public int getItemPosition(#NonNull Object object) {
if (object instanceof FavFragment) {
return POSITION_UNCHANGED; // don't force a reload
} else {
// POSITION_NONE means something like: this fragment is no longer valid
// triggering the ViewPager to re-build the instance of this fragment.
return POSITION_NONE;
}
}
}
I was doing fine with this code. I become concerned when the super(manager) method of FragmentStatePagerAdapter deprecated in api 28.
When I use "super(manager,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);" along with first way of replacing fragment the application crash on runtime.
Also, there is a back draw that when we called adapter.notifydatasetchanged() from the viewpager adapter it's not efficient. It's recreating the whole viewpager while we are trying to change only a specific fragment.
So, way no 2 come with the solution of all problems.
Way 2:
Without adding the actual fragment to the viewpager we should add a root fragment to the viewpager. In root fragment there will be actual fragment added(previously we added in the viewpager. Then we can replace any fragment easily without the help of FragmentStatePagerAdapter.
Code of the root fragment:
Rootfragment.java
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentTransaction;
import com.trickbd.trickbdapp.R;
import dagger.android.support.DaggerFragment;
public class RootFragment extends DaggerFragment {
public RootFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.root_layout, container, false);
if (getFragmentManager() != null) {
FragmentTransaction transaction = getFragmentManager()
.beginTransaction();
transaction.replace(R.id.root_frame, new HomePostFragment());
transaction.commit();
}
return view;
}
}
root_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/root_frame">
</FrameLayout>
So previous replacing code will be updated like below
private void changefragment(int position) {
Fragment newFragment = CategoryPostFragment.newInstance();
replacefragment(newFragment);
viewPager.setCurrentItem(position);
}
public void replacefragment(Fragment fragment){
getSupportFragmentManager();
FragmentTransaction trans = getSupportFragmentManager()
.beginTransaction();
trans.replace(R.id.root_frame, fragment);
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
trans.addToBackStack(null);
trans.commit();
}
By this way, we can replace the fragment more efficiently.
To get the currently added fragment in rootfragment we may use this code.
if (getSupportFragmentManager().findFragmentById(R.id.root_frame) instanceof HomePostFragment){
HomePostFragment postFragment = (HomePostFragment) getSupportFragmentManager().findFragmentById(R.id.root_frame);
}
You may learn more about this here
I am using the following library for my sliding menu (https://github.com/bk138/LibSlideMenu) in my app.
In my app the sliding menu works. I can slide from right to left and the menu will appear. But the problem is that when I am in the menu I can't slide back to the fragment were I came from.
The only way to get back is using back button. Also when you are in the menu I don't have the padding on the right where you see the previous fragment on the background.
I am searching for it like days. I have searched the example for the problem but couldn't find the essential thing that i am forgetting.
My main activity:
public class MainActivity extends SlidingFragmentActivity {
private Fragment rFrag;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setBehindContentView(R.layout.menu_frame);
if (savedInstanceState != null)
rFrag = getSupportFragmentManager().getFragment(savedInstanceState, "mContent");
if (rFrag == null)
rFrag = new RecentGridFragment();
FragmentTransaction fragment = getSupportFragmentManager().beginTransaction();
fragment.replace(R.id.content_frame, rFrag);
MenuFragment mFrag = new MenuFragment();
fragment.replace(R.id.menu_frame, mFrag);
fragment.commit();
//Sliding menu
SlidingMenu sMenu = new SlidingMenu(this);
sMenu.setBehindOffsetRes(R.dimen.slidingmenu_offset);
sMenu.setShadowWidthRes(R.dimen.shadow_width);
sMenu.setShadowDrawable(R.drawable.shadow);
sMenu.setBehindScrollScale(0.25f);
sMenu.setFadeDegree(0.25f);
sMenu.setSlidingEnabled(true);
sMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
return super.onCreateOptionsMenu(menu);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, "mContent", rFrag);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
toggle();
}
return super.onOptionsItemSelected(item);
}
public void switchContent(final Fragment inputFrag) {
rFrag = inputFrag;
FragmentTransaction fragment = getSupportFragmentManager().beginTransaction();
fragment.replace(R.id.content_frame, inputFrag);
fragment.commit();
Handler h = new Handler();
h.postDelayed(new Runnable() {
public void run() {
getSlidingMenu().showContent();
}
}, 50);
}}
Menu:
public class MenuFragment extends ListFragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.list, null);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String[] birds = getResources().getStringArray(R.array.birds);
ArrayAdapter<String> colorAdapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, android.R.id.text1, birds);
setListAdapter(colorAdapter);
}}
The mainfragment that has the content
public class RecentGridFragment extends Fragment {
private int mImgRes;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mImgRes = R.drawable.peacock;
GridView gv = (GridView) inflater.inflate(R.layout.list_grid, null);
gv.setBackgroundResource(android.R.color.black);
gv.setAdapter(new GridAdapter());
return gv;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
private class GridAdapter extends BaseAdapter {
#Override
public int getCount() {
return 30;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getActivity().getLayoutInflater().inflate(R.layout.grid_item, null);
}
ImageView img = (ImageView) convertView.findViewById(R.id.grid_item_img);
img.setImageResource(mImgRes);
return convertView;
}
}}
I have found my mistake:
I need to get the slidingactivity instead of making a new one.
SlidingMenu sMenu = this.getSlidingMenu();
I've been going around in circles trying to do something that seems pretty basic. I have a DialogFragment that accepts a users input, then, on submission, refreshes a ListView in a Fragment that is part of a ViewPager.
I have everything working except the Fragment with the ListView does not refresh itself. It's a little confusing though, because it does refresh the data, but I have to swipe a couple views, then back again to see the updated data.
After doing some research, I'm supposed to use getItemPosition and notifyDataSetChanged on the ViewPager and it should work. The problem is that calling notifyDataSetChanged results in a Recursive entry to executePendingTransactions exception being thrown:
Main Activity
public class Main extends SherlockFragmentActivity implements MyListFragment.OnRefreshAdapterListener, DialogConfirmation.OnRefreshKeywordsListener //Updated Code
{
private static List<Fragment> fragments;
#Override
public void onCreate(final Bundle icicle)
{
setContentView(R.layout.main);
}
#Override
public void onResume()
{
mViewPager = (ViewPager)findViewById(R.id.viewpager);
fragments = new ArrayList<Fragment>();
fragments.add(new MyListFragment()); //fragment with the ListView
fragments.add(MyDetailFragment.newInstance(0));
fragments.add(MyDetailFragment.newInstance(1));
fragments.add(MyDetailFragment.newInstance(2));
mMyFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mMyFragmentPagerAdapter);
}
private static class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
return fragments.get(index);
}
#Override
public int getCount() {
return 4;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
#Override
public void onRefreshAdapterListener() {
this.mMyFragmentPagerAdapter.notifyDataSetChanged();
}
//Updated Code
#Override
public void onRefreshTextListener() {
MyListFragment tf = (MyListFragment)getSupportFragmentManager().findFragmentById(R.id.fragmentText);
if (tf == null)
tf = (MyListFragment)this.fragments.get(0);
tf.RefreshText();
}
}
ListFragment
public class MyListFragment extends SherlockListFragment
{
OnRefreshAdapterListener mRefreshAdapter;
#Override
public void onActivityCreated(Bundle savedState) {
adapter = new CustomAdapter();
/*code to add items to adapter */
this.setListAdapter(adapter);
adapter.notifyDataSetChanged();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (getArguments() != null && getArguments().getString("text").length() > 0)
{
SaveText(getArguments().getString("text"));
this.mRefreshAdapter.onRefreshAdapterListener(); //this line causes a "java.lang.IllegalStateException: Recursive entry to executePendingTransactions" exception
}
return inflater.inflate(R.layout.listing, container, false);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mRefreshAdapter = (OnRefreshAdapterListener)activity;
}
public interface OnRefreshAdapterListener {
public void onRefreshAdapterListener();
}
#Override
public void onDialogTextAdd(final String text) {
}
}
DialogFragment
public class DialogTextAdd extends DialogFragment implements OnEditorActionListener {
private EditText mText;
OnRefreshTextListener mTextKeywords; //Updated Code
public interface DialogTextAddListener {
void onDialogTextAdd(final String inputText);
}
public DialogTextAdd() {
// Empty constructor required for DialogFragment
}
//Updated Code
#Override
public void onAttach(Activity act) {
super.onAttach(act);
mTextKeywords = (OnRefreshTextListener)act;
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.dialog_edit, container);
mText = (EditText)view.findViewById(R.id.text_add);
getDialog().setTitle("Add Text");
// Show soft keyboard automatically
mText.requestFocus();
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
mText.setOnEditorActionListener(this);
return view;
}
#Override
public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) {
if (EditorInfo.IME_ACTION_DONE == actionId) {
MyListFragment mf = new MyListFragment();
Bundle args = new Bundle();
args.putString("text", mText.getText().toString());
mf.setArguments(args);
//this seems to be intefering with the notifyDataSetChanged in the listing fragment
getActivity().getSupportFragmentManager().beginTransaction().add(mf, "my_fragment").commit();
mTextKeywords.onRefreshTextListener(); //Updated Code
this.dismiss();
return true;
}
return false;
}
}
I have everything working except the Fragment with the ListView does
not refresh itself.
There is no point on creating and adding to the FragmentActivity a new instance of MyListFragment. From your code it appears that you store the fragments that you use in a list so you have references to them(also, just out of curiosity, did you setup the fragments in portrait, did a rotation of the phone and retried to use the DialogFragment?). Having references to those fragment means you could always get them from the list and use them to call a refresh/update method.
I have a fragment activity that uses a ViewPager to display a set of fragments. On the fragment activity I have a button that when clicked, it sends a message to the current fragment to refresh its contents. Everything works ok (activity / current fragment communication) except the fact that I cannot refresh the fragment's view. Accessing the current view by getView() does not work as this function returns null; it seems that after the fragment is created (on ViewCreated is called) getView gets destroyed. Am I missing something here? How to I cause a fragment to redraw its contents programmatically? It seems that the only way this works is when the fragment is created from the parent activity. Do I have to remove and re-add the fragment again to do this?
Here is the code:
The main activity:
public class MainActivity extends FragmentActivity {
private MyAdapter mAdapter;
private static ViewPager mPager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupViewPager();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
return super.onPrepareOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_test:
updateFragment();
return true;
default: return true;
}
}
private void updateFragment() {
for (int i=0; i< mAdapter.getCount(); i++) {
SampleFragment fragment = (SampleFragment) mAdapter.getItem(i);
fragment.update();
}
}
private void setupViewPager() {
try {
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(this.mAdapter);
} catch (Exception e) {
e.printStackTrace();
}
}
public class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
SampleFragment fragment = new SampleFragment(position);
return fragment;
}
#Override
public int getCount() {
return 5;
}
}
}
and the fragment class:
public class SampleFragment extends Fragment{
private int myPosition = -1;
public SampleFragment(int position) {
this.myPosition = position;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, container, false);
update(view, "Updated from onCreateView");
return view;
}
#Override
public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.findViewById(R.id.textTitle).setOnClickListener(myClickListener);
}
private OnClickListener myClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.textTitle:
break;
}
}
};
public void update() {
update(getView(), "Updated from main");
}
private void update(View view, String subtitleText) {
TextView title = (TextView) view.findViewById(R.id.textTitle);
TextView subtitle = (TextView) view.findViewById(R.id.textSubtitle);
title.setText("Fragment " + myPosition);
subtitle.setText(subtitleText);
}
}
The error happens on view.FindViewById (view is null) when called from the menu item in the main activity.
You can take a look at this article which explains how to keep references to the fragments in your ViewPager.
There are two methods described on the page. The first one involves setting a tag when you add the fragment using the FragmentManager. Then you can retrieve the fragment using findFragmentByTag(). However, I did not see how to make this work using FragmentPagerAdapter or FragmentStatePagerAdapter, since these implementations add the fragments for you. If you are using your own custom PagerAdapter, this may work for you.
The other method, which does work for FragmentPagerAdapter or FragmentStatePagerAdapter, involves keeping a map of all your fragments, updating inside your getItem() and destroyItem() implementations. This method has worked for me.
Once you have a reference to the current fragment, you can just call a method in your fragment to refresh its view.