Android - Problems with BackStack using FragmentStatePagerAdapter - android

I'm building an Android application using a FragmentStatePagerAdapter for tabbed navigation and dynamic content in each tab. Each tab has Fragment with content which is to be replaced upon user input (for example, the first tab has a Fragment containing a list of books, and upon clicking, you can access detailed information of the book, which is displayed using another Fragment
Problem: I haven't find a way of correctly handling the onBack events nor the BackStack, so when I'm reviewing any book's details, I can easily go back pressing the back button - I mean, popping the last state from the BackStack.
What I suspect: The way I'm switching Fragment objects may not the the best one, but except for the back button issue, it is working just as I want. I suspect some problem between the FragmentStatePagerAdapter's adapter, and the FragmentManager's own collection of Fragments; probably this is something with an easy solution I didn't see.
Unaswered question (not very detailed though): Adding Fragment to BackStack using FragmentStatePagerAdapter
The code:
// MAIN ACTIVITY, Just this simple.
public class MainActivity extends FragmentActivity {
public static final String TAG = "MainActivity";
// Whether the Log Fragment is currently shown
private boolean mLogShown;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
MainTabSliderFragment fragment = new MainTabSliderFragment();
transaction.replace(R.id.sample_content_fragment, fragment);
transaction.commit();
}
}
}
.
// THE SLIDE TAB FRAGMENT, which becomes the parent view of the tabs.
public class MainTabSliderFragment extends Fragment {
static final String LOG_TAG = MainTabSliderFragment.class.getSimpleName();
private SlidingTabLayout mSlidingTabLayout;
private ViewPager mViewPager;
private CustomFragmentStatePageAdapter cfspAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_sample, container, false);
return root;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
cfspAdapter = new CustomFragmentStatePageAdapter(getFragmentManager());
List<String> pageTitles = new ArrayList<>();
pageTitles.add(getString(R.string.page_one));
pageTitles.add(getString(R.string.page_two));
pageTitles.add(getString(R.string.page_three));
List<Fragment> pageFragments = new ArrayList<>();
final BookListPageFragment pageOne = BookListPageFragment.newInstance(new CustomFragmentStatePageAdapter.SwitchFragmentListener() {
#Override
public void onSwitchFragments(Class<? extends Fragment> clazz, Map<String, String> ... args) {
cfspAdapter.switchFragment(CustomFragmentStatePageAdapter.PagePosition.POSITION_PAGE_ONE, clazz, this, args);
}
});
CustomerPageFragment pageTwo = CustomerPageFragment.newInstance(...);
ForumPageFragment pageThree = ForumPageFragment.newInstance(...);
pageFragments.add(pageOne);
pageFragments.add(pageTwo);
pageFragments.add(pageThree);
cfspAdapter.addFragments(pageFragments, pageTitles);
mViewPager.setAdapter(cfspAdapter);
mSlidingTabLayout = (SlidingTabLayout) view.findViewById(R.id.sliding_tabs);
mSlidingTabLayout.setViewPager(mViewPager);
}
}
.
// THE FIRST TAB, In its initial state (the initial fragment).
public class BookListPageFragment extends Fragment {
private static final String TAG = BookListPageFragment.class.getSimpleName();
private BookListAdapter bAdapter;
private static CustomFragmentStatePageAdapter.SwitchFragmentListener switchFragmentListener;
public static BookListPageFragment newInstance(CustomFragmentStatePageAdapter.SwitchFragmentListener _switchFragmentListener) {
switchFragmentListener = _switchFragmentListener;
BookListPageFragment f = new BookListPageFragment();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.page_one_booklist, container, false);
final ListView lv = (ListView) v.findViewById(R.id.list);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BookRowData bRow = (BookRowData) lv.getItemAtPosition(position);
Log.i(TAG, "Clicked on book " + bRow.getBookId());
Map<String, String> param = new HashMap<>();
param.put("book_id", Long.toString(bRow.getBookId()));
switchFragmentListener.onSwitchFragments(ReviewBookPageFragment.class, new Map[]{param});
}
});
initializeTestList(v, lv); // Just add some books to the list.
return v;
}
.
// THE PAGE ADAPTER, used for handling tab's Fragment switching.
public class CustomFragmentStatePageAdapter extends FragmentStatePagerAdapter {
private final static String TAG = FragmentStatePagerAdapter.class.getSimpleName();
private FragmentManager fragmentManager;
private List<Fragment> fragmentList = new ArrayList<>();
private List<String> tabTitleList = new ArrayList<>();
public CustomFragmentStatePageAdapter(FragmentManager fm) {
super(fm);
fragmentManager = fm;
}
public void addFragments(List<Fragment> fragments, List<String> titles) {
fragmentList.clear();
tabTitleList.clear();
fragmentList.addAll(fragments);
tabTitleList.addAll(titles);
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
if (fragmentList.contains(object)) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
#Override
public Fragment getItem(int item) {
if (item >= fragmentList.size()) {
return null;
}
return fragmentList.get(item);
}
#Override
public int getCount() {
return fragmentList.size();
}
#Override
public CharSequence getPageTitle(int position) {
return tabTitleList.get(position);
}
/**
* Switching pages
*
* #param newFragment
*/
public void switchFragment(final PagePosition position, Class<? extends Fragment> newFragment, SwitchFragmentListener sfListener, Map<String, String> ... args) {
final Fragment old = fragmentList.get(position.getPagePosition());
fragmentManager.beginTransaction().remove(old).commit(); //FIRST VERSION: IF HITTING BACK, IT EXITS APP AT ONCE.
//fragmentManager.beginTransaction().addToBackStack("page_one").remove(old).commit(); //SECOND VERSION: NOW I NEED TO HIT BACK TWICE TO EXIT, BUT THE VIEW DOESN'T CHANGE AFTER HITTING THE FIRST TIME.
try {
Fragment f = (Fragment) newFragment.asSubclass(Fragment.class).getMethod("newInstance", SwitchFragmentListener.class, Map[].class).invoke(newFragment, new Object[]{sfListener, args});
fragmentList.set(position.getPagePosition(), f);
} catch (IllegalAccessException iae) {
Log.e(TAG, "Fragment class access exception");
} catch (NoSuchMethodException e) {
Log.e(TAG, "Fragment instantiation exception (reflection)");
} catch (InvocationTargetException e) {
Log.e(TAG, "Fragment instantiation exception (reflection: no public constructor)");
}
notifyDataSetChanged();
}
public interface SwitchFragmentListener {
void onSwitchFragments(Class<? extends Fragment> clazz, Map<String, String> ... args);
}
public enum PagePosition {
POSITION_PAGE_ONE (0),
POSITION_PAGE_TWO (1),
POSITION_PAGE_THREE (2);
private final int position;
PagePosition(int position) {
this.position = position;
}
public int getPagePosition() {
return this.position;
}
}
}
.
// AND FINALLY THE FRAGMENT I WANT TO GO BACK FROM; this is the book review Fragment, which is displayed also in the first tab when clicking on a book from the list. Second and third tabs are ommitted.
public class ReviewBookPageFragment extends Fragment {
private static final String TAG = ReviewBookPageFragment.class.getSimpleName();
private CommentsListAdapter cAdapter;
private Long bookId;
private static CustomFragmentStatePageAdapter.SwitchFragmentListener switchFragmentListener;
public static ReviewBookPageFragment newInstance() {
ReviewBookPageFragment f = new ReviewBookPageFragment();
return f;
}
public static ReviewBookPageFragment newInstance(CustomFragmentStatePageAdapter.SwitchFragmentListener _sfListener, Map<String, String> ... args) {
switchFragmentListener = _sfListener;
Bundle b = BundlePacker.packMaps(args); // Custom util class for packing the params into a bundle.
ReviewBookPageFragment f = new ReviewBookPageFragment();
f.setArguments(b);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.page_review_book, container, false);
Bundle bookIdBundle = this.getArguments();
Long bId = Long.parseLong(bookIdBundle.getString("book_id"));
Log.i(TAG, "Book ID: " + bId);
initializeTestList(v); // Just fill the book's reviews with test data.
return v;
}
}
So, that's the bunch of code. The idea, as a summary, is to switch from the books list view (shown on tab one), to the book's reviews when tapping on any book from the list; the reviews are also shown on the first tab, and I want to go back to the books list when pressing back. Currently, it closes the application hitting back ONCE, and if I add the transaction to the backstack (see my CustomFragmentStatePageAdapter), TWICE (but the view doesn't change after hitting back the first time.
Any help with the issue will be greatly appreciated.

For fixing the popback issue you can use this code in your activity class,
#Override
public void onBackPressed() {
// if there is a fragment and the back stack of this fragment is not empty,
// then emulate 'onBackPressed' behaviour, because in default, it is not working
FragmentManager fm = getSupportFragmentManager();
for (Fragment frag : fm.getFragments()) {
if (frag.isVisible()) {
FragmentManager childFm = frag.getChildFragmentManager();
if (childFm.getBackStackEntryCount() > 0) {
childFm.popBackStack();
return;
}
}
}
super.onBackPressed();
}

I did somethink like this:
private View _view;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(_view==null){
_view = inflater.inflate(R.layout.page_review_book, container, false);
// your_code
}
return _view;
}

Related

Android TabLayout with ViewPager duplicates fragment contents when rotating

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.

Fragment in ViewPager using FragmentPagerAdapter is blank the first time activity is loaded

I'm creating an app with vertical page adapter using FragmentStatePagerAdapter. The big issue i'm facing is, data is not displayed on the textview on first app launch but is displayed on scrolling the page. I believe the fragment view is delaying to create textview because, on my LoadAlbumDataCompleted() function inside Fragmentone.class, i'm able to print the data returned or also output via toast but is not getting populated to the textview.
Kindly help.
MainActivity.class
public class MainActivity extends FragmentActivity implements LoadAalbumsTotalListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new MainActivityVSlideAdapter(this, getSupportFragmentManager(), NUMBER_OF_PAGES);
mPager.setAdapter(mAdapter);
LoadTotalAlbumsNum.BindAlbumsTotalListener(this);
}
#Override
public void OnLoadAlbumsCompleted(String total) {
if(total.trim().equalsIgnoreCase("")){
NUMBER_OF_PAGES=0;
}else{
NUMBER_OF_PAGES=Integer.parseInt(total.trim());
}
mAdapter = new MainActivityVSlideAdapter(this, getSupportFragmentManager(), NUMBER_OF_PAGES);
mPager.setAdapter(mAdapter);
}
}
MainActivityVSlideAdapter.class Adapter
public class MainActivityVSlideAdapter extends FragmentStatePagerAdapter {
static int NUMBER_OF_PAGES;
private Context con;
public MainActivityVSlideAdapter(Context con, FragmentManager fm, int NUMBER_OF_PAGES) {
super(fm);
this.con=con;
this.NUMBER_OF_PAGES=NUMBER_OF_PAGES;
}
#Override
public int getCount() {
return NUMBER_OF_PAGES;
}
#Override
public Fragment getItem(int position) {
return FragmentOne.newInstance(position);
}
}
Fragmentone.class
public class FragmentOne extends Fragment implements LoadAlbumDataListener {
private static final String MY_NUM_KEY = "num";
private int mNum;
private TextView SaloonName;
private TextView location;
// You can modify the parameters to pass in whatever you want
public static FragmentOne newInstance(int num) {
FragmentOne f = new FragmentOne();
Bundle args = new Bundle();
args.putInt(MY_NUM_KEY, num);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//get argument from
mNum = getArguments() != null ? getArguments().getInt(MY_NUM_KEY) : 0;
session=new Session(getActivity());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_one, container, false);
Methods methods=new Methods(getActivity());
// v.setBackgroundColor(mColor);
SaloonName = v.findViewById(R.id.SaloonName);
location=v.findViewById(R.id.location);
new LoadAlbumData(getActivity()).execute(getString(R.string.urlAddress)+"load-album-data.php", String.valueOf(mNum));
LoadAlbumData.BindLoadAlbumDataListener(this);
return v;
}
#Override
public void LoadAlbumDataCompleted(String s) {
JSONArray jsonPicsArray = null;
JSONObject jsonObj;
String BusinessLocation=null;
try {
jsonPicsArray = new JSONArray(s);
businessName = jsonObj.getString("businessName");
BusinessLocation = jsonObj.getString("location");;
}
Toast.makeText(getActivity(), businessName, Toast.LENGTH_LONG).show();
SaloonName.setText(businessName);
location.setText(BusinessLocation);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
From your code, it looks like, you are not setting the LoadAlbumDataListener correctly. Instead of statically setting the listener. Pass reference in constructor of your LoadAlbumData Asynctask
remove this line
LoadAlbumData.BindLoadAlbumDataListener(this);
replace
new LoadAlbumData(getActivity()).execute(getString(R.string.urlAddress)+"load-album-data.php", String.valueOf(mNum));
with
new LoadAlbumData(getActivity(), this).execute(getString(R.string.urlAddress)+"load-album-data.php", String.valueOf(mNum));
Modify your asynctask to have reference of LoadAlbumDataListener
Also as a good practice, never store strong reference of activity or fragment in your asynctask. Use WeakReference.

Communicating between fragments

Below is an screenshot from my app.
This screen is a fragment that has sliding tabs layout. It will hold another fragment that will show data in listview. The problem is, in order to load data the value selected from the spinner need to pass within the fragment in tab. I am not getting idea how to do this. One approach would be the tab fragment would implement a callback and within that callback data should be loaded. But I am not getting how to register that callback in onItemSelected of spinner.
Note: All fragments within the tab will show data in listview only, so I have created a common fragment.
This is my code so far:
Fragment for the screenshot
public class BuyListingFragment2 extends BaseFragment {
private Context ctx;
private Spinner vehicle_type;
private ArrayList<ListingTabModel> mListingTabs = new ArrayList<ListingTabModel>();
private ArrayAdapter<String> spinnerAdapter;
private ArrayList<String> vehicleTypeSpinnerlist;
private int spinnerPosition;
private SlidingTabLayout sliding_tabs;
private BuyListingPagerAdapter buyListingPagerAdapter;
public static BuyListingFragment2 newInstance(String category,
int position, String preselectedFilters) {
BuyListingFragment2 fragment = new BuyListingFragment2();
Bundle args = new Bundle();
args.putString("vehicle_type", category);
args.putInt("spinner_position", position);
fragment.setArguments(args);
return fragment;
}
public BuyListingFragment2() {
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.vehicleType = getArguments().getString("vehicle_type");
this.selectedVehicle = this.vehicleType;
this.spinnerPosition = getArguments().getInt("spinner_position");
ArrayList<CategoryType> vehicleTypeList = RegistrationResponse
.getInstance().getVehicleTypeList();
spinnerAdapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, vehicleTypeList);
buyListingPagerAdapter = new BuyListingPagerAdapter(
getChildFragmentManager(), mListingTabs);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ctx = getActivity();
vehicle_type = (Spinner) view.findViewById(R.id.vehicle_type);
vehicle_type.setAdapter(spinnerAdapter);
vehicle_type.setSelection(spinnerPosition, false);
if (mListingTabs.isEmpty()) {
String[] tabNames = getResources().getStringArray(
R.array.listing_tab_names);
for (int i = 0; i < tabNames.length; i++) {
String tabName = tabNames[i];
ListingTabModel mListingTabModel = new ListingTabModel();
mListingTabModel.setTagName(tabName);
mListingTabs.add(mListingTabModel);
}
}
buyListingPagerAdapter.notifyDataSetChanged();
listing_layout_viewpager = (ViewPager) view
.findViewById(R.id.listing_layout_viewpager);
listing_layout_viewpager.setAdapter(buyListingPagerAdapter);
sliding_tabs = (SlidingTabLayout) view.findViewById(R.id.sliding_tabs);
sliding_tabs.setDistributeEvenly(true);
sliding_tabs.setViewPager(listing_layout_viewpager);
vehicle_type.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
spinnerPosition = position;
//How to register listener here
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
}
Common Fragment inside Tab
public class ListingFragment extends BaseFragment implements
OnSpinnerDataSelected {
private InfiniteListView mListView;
private BuyListingListAdapter buyListingAadapter;
private RobotoLightTextView emptyMessage;
private int currentPageNumber = 1;
private int totalPages;
private HashMap<String, String> params = new HashMap<String, String>();
private int apiCallCount = 0;
private Context ctx;
private String vehicleType;
private ProgressBar progressBar;
public ListingFragment() {
}
public static ListingFragment newInstance(ListingTabModel mListingTabModel) {
ListingFragment mFragment = new ListingFragment();
Bundle bundle = new Bundle();
// bundle.putBoolean("is_grid_view", mListingTabModel.isShowGridView());
// bundle.putString("vehicle_type", mListingTabModel.getVehicleType());
mFragment.setArguments(bundle);
return mFragment;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ctx = getActivity();
emptyMessage = (RobotoLightTextView) view
.findViewById(R.id.empty_message);
mListView = (InfiniteListView) view.findViewById(R.id.lstVw_buy);
boolean isGrid = getArguments().getBoolean("is_grid_view");
vehicleType = getArguments().getString("vehicle_type");
buyListingAadapter = new BuyListingListAdapter(ctx,
mVehicleListingList, isGrid);
mListView.setAdapter(buyListingAadapter);
progressBar = new ProgressBar(ctx);
}
#Override
public int getLayoutId() {
return R.layout.layout_messages;
}
#Override
public void onSpinnerDataSelected(String vehicleCategory) {
// TODO: fetch listing data
}
}
Callback implemented by the ListingFragment
public interface OnSpinnerDataSelected {
void onSpinnerDataSelected(String vehicleCategory);
}
FragmentStatePagerAdapter
public class BuyListingPagerAdapter extends FragmentStatePagerAdapter {
ArrayList<ListingTabModel> mFragmentsList;
public BuyListingPagerAdapter(FragmentManager fm,
ArrayList<ListingTabModel> mFragmentsList) {
super(fm);
this.mFragmentsList = mFragmentsList;
}
#Override
public Fragment getItem(int index) {
ListingFragment listingFragment = ListingFragment
.newInstance(mFragmentsList.get(index));
return listingFragment;
}
#Override
public int getCount() {
return mFragmentsList.size();
}
#Override
public CharSequence getPageTitle(int position) {
String tagName = mFragmentsList.get(position).getTagName();
tagName = tagName.replace("_", " ");
return tagName;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return object == view;
}
}
When using one activity and multiple fragments, I suggest to let the Fragment manage the UI and use the Activity has a controller/model.
Workflow for a spinner to communicate with other fragments :
Register the spinner listener in Frag1
Register a data listener from Frag2 in Activity
OnItemSelected from Frag1 prevent Activity from the Spinner value change
Activity received the spinner change value
Activity call Frag2 listener to prevent Frag2 of the spinner change
Frag2 receive spinner change, do your stuff
Here is a litle schema
I would base everything on an event bus like Otto. IMHO, Fragments were meant to be decoupled from hosting activities and such, but all the interfaces and callbacks end up creating spaghetti code. Otto lets you post event on a common bus -- the receiver doesn't need to be tied to the sender via some listener/callback mechanism. Plus, it works great in conjunction with dependency injection, see Dagger.

Crash when returning to fragment as a tab. View with same id

My main activity has an ActionBar with two tabs. Tab 1 is a ListFragment and Tab 2 is a fragment that uses the HeaderListView library. When you open the app, tab 1 shows. When you switch to tab 2, then back to tab 1, and finally return to tab 2 the app crashes with the following:
09-12 13:19:22.440 21772-21772/com.iosharp.android.smoothstreams E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.iosharp.android.smoothstreams, PID: 21772
java.lang.IllegalArgumentException: Wrong state class, expecting View State but received class android.widget.AbsListView$SavedState instead. This usually happens when two views of different type have the same id in the same hierarchy. This view's id is id/header_list_view. Make sure other views do not use the same id.
id/header_list_view is only used in tab 2 and only in the onCreateView() method and isn't inflated in tab 1. I'm perplexed as to where to go from here. Also for what its worth I am using android.app.v4.support.Fragment
Tab 2's fragment's relevant parts:
public class ProgrammeListFragment extends Fragment {
private static final String TAG = ProgrammeListFragment.class.getSimpleName();
private ArrayList<Programme> mProgrammes;
private ArrayList<Day> mDays;
private Date mNow;
private HeaderListView mHeaderListView;
static private SectionAdapter mAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNow = new Date();
mProgrammes = ProgrammeStore.get(getActivity()).getProgrammes();
Collections.sort(mProgrammes, new Programme());
mProgrammes = trimProgrammes(mProgrammes);
mDays = setupDays(mProgrammes);
}
#Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.progamme_header_list, container, false);
mHeaderListView = (HeaderListView) v.findViewById(R.id.header_list_view);
mAdapter = new SectionAdapter() {
#Override
public int numberOfSections() {
return mDays.size();
}
#Override
// TODO: sometimes -1 is passed as int section, this needs to be looked at later
public int numberOfRows(int section) {
if (section > -1) {
return mDays.get(section).getLineUp().size();
} else {
return 1;
}
}
#Override
public View getRowView(int section, int row, View convertView, ViewGroup parent) {
convertView = getActivity().getLayoutInflater().inflate(
R.layout.programme_list_item, null);
//set views
return convertView;
}
#Override
public Day getSectionHeaderItem(int section) {
return mDays.get(section);
}
#Override
public int getSectionHeaderItemViewType(int section) {
return super.getSectionHeaderItemViewType(section);
}
#Override
public boolean hasSectionHeaderView(int section) {
return true;
}
#Override
public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {
convertView = getActivity().getLayoutInflater().inflate(R.layout.programme_list_header, null);
// set views
return convertView;
}
#Override
public Programme getRowItem(int section, int row) {
return mDays.get(section).getLineUp().get(row);
}
};
mHeaderListView.setAdapter(mAdapter);
return v;
}
}
The relevant parts of MainActivity that instantiate the fragments and the tabs:
public class MainActivity extends ActionBarActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final String KEY_TAB = "tab";
private Fragment fragmentChannels = new ChannelListFragment();
private Fragment fragmentProgrammes = new ProgrammeListFragment();
private int mCurrentTab;
private ActionBar mActionBar;
private ArrayList<Channel> mChannels;
private ArrayList<Programme> mProgrammes;
private ProgressDialog mProgressDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
mActionBar = getSupportActionBar();
mActionBar.setTitle(R.string.app_name);
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
mActionBar.addTab(mActionBar.newTab().setText(R.string.channels_title).setTabListener(new TabListener(fragmentChannels)));
mActionBar.addTab(mActionBar.newTab().setText(R.string.programmes).setTabListener(new TabListener(fragmentProgrammes)));
// Get singletons
mProgrammes = ProgrammeStore.get(getApplication()).getProgrammes();
mChannels = ChannelStore.get(getApplicationContext()).getChannels();
}
}
And just in case two methods from the TabListener class I have:
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
fragmentTransaction.replace(R.id.fragmentContainer, fragment);
}
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
fragmentTransaction.remove(fragment);
}
This is an issue in HeaderListView. Setting an id on the listview (listView.setId(1)) to any integer solves the problem.

How can i use different listviews in fragments with viewpager

I am using PagerSlidingTabs. I have two fragment and each fragment has one listview. I added TabListener, because i want to go first item in listview when user reselect the same tab. This is working only on the second fragment(tab). Problem is second listview's smoothScrollToPosition(0) method working but first listview's smoothScrollToPosition(0) method(all methods) not working. First listview is showing properly on screen but it's id seems to second listview's id.
PagerAdapter:
public class PagerAdapter extends FragmentPagerAdapter {
private Context mContext;
private int mPageCount;
public PagerAdapter(Context context, FragmentManager fm, int pageCount) {
super(fm);
this.mContext = context;
this.mPageCount = pageCount;
}
#Override
public int getCount() {
return mPageCount;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return Fragment1.newInstance(position);
case 1:
return Fragment2.newInstance(position);
default:
return null;
}
}}
Fragment1:
public class Fragment1 extends Fragment {
private static final String ARG_POSITION = "position";
private ListView mListView1;
private ListAdapter mAdapter;
public static Fragment1 newInstance(int position) {
Fragment1 f = new Fragment1();
Bundle b = new Bundle();
b.putInt(ARG_POSITION, position);
f.setArguments(b);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mRootView == null) {
mRootView = inflater.inflate(R.layout.fragment1, container, false);
mListView1 = (ListView) mRootView.findViewById(R.id.listview1);
.
.
if (mAdapter == null) {
mAdapter = new ListAdapter(getActivity(), mListView1, R.layout.list_item, mDatas);
mListView1.setAdapter(mAdapter);
} else {
mAdapter.notifyDataSetChanged();
}
.
.
((HomeActivity) getActivity()).getPagerSlidingTabStrip().setOnTabListener(new TabListener() {
#Override
public void onTabReselected(View tab, int postion) {
mListView1.smoothScrollToPosition(0);
//mListView1's id seems to mListView2's id
//when reselect first tab, application acting like i reselect second tab.
}
});
} else {
((ViewGroup) mRootView.getParent()).removeView(mRootView);
}
return mRootView;
}}
Fragment2:
public class Fragment2 extends Fragment {
private static final String ARG_POSITION = "position";
private ListView mListView2;
private ListAdapter mAdapter;
public static Fragment2 newInstance(int position) {
Fragment2 f = new Fragment2();
Bundle b = new Bundle();
b.putInt(ARG_POSITION, position);
f.setArguments(b);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mRootView == null) {
mRootView = inflater.inflate(R.layout.fragment2, container, false);
mListView2 = (ListView) mRootView.findViewById(R.id.listview2);
.
.
if (mAdapter == null) {
mAdapter = new ListAdapter(getActivity(), mListView2, R.layout.list_item, mDatas);
mListView2.setAdapter(mAdapter);
} else {
mAdapter.notifyDataSetChanged();
}
.
.
((HomeActivity) getActivity()).getPagerSlidingTabStrip().setOnTabListener(new TabListener() {
#Override
public void onTabReselected(View tab, int postion) {
mListView2.smoothScrollToPosition(0); //this line is working fine
}
});
} else {
((ViewGroup) mRootView.getParent()).removeView(mRootView);
}
return mRootView;
}}
Fragment1 and Fragment2 have only listview(id:listview1, id:listview2).
HomeActivity have PagerSlidingTabs view and viewpager.
How can i fix this problem?
When you set the listener from the second fragment, that replaces the listener from the first. That's why all the calls seem to be going to the second tab: its listener was the last one added, so it's receiving all the callbacks. One potential solution is to declare an interface, let's call it OnTabReselctedListener, that has a method onTabReselected(into position). Have your activity maintain a list of those interfaces, have your fragments implement that interface, and register those fragments as listeners in the activity. When the activity receives a reselection event, it can pass that to its children fragments; those fragments check if the position matches their own position and scroll to the top if they do. I hope that made sense; I'm writing this from my phone so I can't easily wrote sample code. If you want me to write some, let me know!

Categories

Resources