ViewPager not updating fragments - java

I'm trying to replace fragments in ViewPager, but I'm facing a problem I've been unable to fix for several days. The relevant code and specific problem, as I understand it, are described below:
public class ViewPageAdapter extends FragmentStatePagerAdapter {
int mNumOfTabs;
FragmentManager mFragmentManager;
Fragment0 currentFragment0;
Fragment1 currentFragment1;
Fragment2 currentFragment2;
boolean getItemNeverCalled = true;
public ViewPageAdapter(FragmentManager fm, int numOfTabs){
super(fm);
mFragmentManager = fm;
this.mNumOfTabs = numOfTabs;
}
#Override
public Fragment getItem(int position){
switch (position){
case 0:
if(currentFragment0 == null){
Fragment0 tab0 = new Fragment0();
currentFragment0 = tab0;
return currentFragment0;
}
else {
mFragmentManager.beginTransaction().remove(currentFragment0).commit();
int value = selectedPlant.getMoistureFrag().getStat().getOptimalLevel();
currentFragment0 = Fragment0.newInstance(key0, value);
notifyDataSetChanged(); // calls getItem(0).
return currentFragment0;
}
case 1:
if(currentFragment1 == null){
LightFragment tab1 = new Fragment1();
currentFragment1 = tab1;
return currentFragment1;
}
else {
mFragmentManager.beginTransaction().remove(currentFragment1).commit();
int value = selectedPlant.getLightFrag().getStat().getOptimalLevel();
currentFragment1 = currentFragment1.newInstance(key1, value);
notifyDataSetChanged();
return currentFragment1;
}
case 2:
if(currentFragment2 == null){
Fragment2 tab2 = new Fragment2();
currentFragment2 = tab2;
return currentFragment2;
}
else {
mFragmentManager.beginTransaction().remove(currentFragment2).commit();
int value = selectedPlant.getTempFrag().getStat().getOptimalLevel();
currentFragment2 = Fragment2.newInstance(key2, value);
notifyDataSetChanged();
return currentFragment2;
}
default:
return null;
}
}
#Override
public int getCount(){
return mNumOfTabs;
}
#Override
public int getItemPosition(Object object){
return POSITION_NONE;
}
I've overriden the getItemPosition(Object object) method to always return POSITION_NONE, and called notifyDataSetChanged() when appropriate (I think). What ends up happening is that notifyDataSetChanged() calls getItem(0), which calls `notifyDataSethanged()... and so on. This causes a TransactionTooLargeException and crashes the app.
Just to give some background to the if/else statements in each case: the if is meant to load a blank Moisture/Light/etc Fragment onto the screen. This is intended to happen on start-up. The else statement is executed when a user presses on a item in the navigation drawer, which has some data. This data is then extracted and set as arguments for the fragments that are meant to replace the initial blank fragment.
I genuinely appreciate any help. This problem is driving me crazy.

Why in the world are you recreating fragments, when you can just update the old ones?
Also, when you are calling notifyDataSetChanged during getItem then you are forcing a new call to getItem which will force a new call...so you are actually creating a circular call!
Since you are always keeping the same fragment class in each position, and you are holding on to the fragment, then you should not replace fragment. Just change the fragment you are holding to show the new values. The code you are using is only needed if you want to change different fragment classes for position.

Related

Save state when navigating between fragments

I'm working on an app and I have a menu with a NavigationDrawer to navigate between fragments. In one of the fragments I make a call to the backend and then save the results in a list. When I navigate to another fragment and back, the results are gone, but I'd like to save the contents of the list temporarily. I wanted to use onSaveInstanceState(), but that method doesn't seem to get called ever. I also looked if the data is still in the fields when I return to the fragment, but that also wasn't the case. I think I'm doing something wrong with the FragmentManager, but I'm not sure about it.
This is the method used for the transactions for the Fragments:
private void openFragment(Class fragmentClass) {
Fragment fragment;
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return;
}
contentFrame.removeAllViews();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.contentFrame,fragment).commit();
}
I use a switch case to determine the Fragment's class and send that to this method.
I could probably figure out a hacky-snappy way to fix this, but I'd like to fix this without too much hacky-snappy code.
I hope someone has an idea on how to fix this. Thanks in advance.
EDIT:
Here is my fragment class:
public class LGSFragment extends Fragment {
#BindView(R.id.rvLGS)
RecyclerView rvLGS;
private List<LGS> lgsList;
private LGSAdapter adapter;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//I debugged here and all fields were null at this point
View view = inflater.inflate(R.layout.fragment_lgs,container,false);
ButterKnife.bind(this, view);
lgsList = new ArrayList<>();
LinearLayoutManager manager = new LinearLayoutManager(getContext());
rvLGS.setLayoutManager(manager);
adapter = new LGSAdapter(lgsList);
rvLGS.setAdapter(adapter);
getDatabaseLGSs();
return view;
}
/**
* Method to load in the LGSs from the database
*/
private void getDatabaseLGSs() {
String collection = getString(R.string.db_lgs);
FireStoreUtils.getAllDocumentsConverted(collection, LGS.class, new OperationCompletedListener() {
#Override
public void onOperationComplete(Result result, Object... data) {
if (result == Result.SUCCESS) {
lgsList.clear();
List<LGS> newLGSs = (List<LGS>) data[0];
List<String> ids = (List<String>) data[1];
int i = 0;
for (LGS lgs : newLGSs) {
lgs.setId(ids.get(i));
lgsList.add(lgs);
i++;
}
adapter.notifyDataSetChanged();
}
}
});
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
}
}
onSaveInstanceState is not called because there is no reason to, when you navigate between fragments, the older fragment doesn't get destroyed till the OS need the space they use (low Memory).
First of all create a back stack to keep fragments or just call addtoBackStack at the end of fragmentTransaction and then move the list initiation and data request to onCreate so it only called when the fragment is created:
lgsList = new ArrayList<>();
getDatabaseLGSs();
and after that every time you get back to fragment the view is recreated with available data.
Update:
Instead of keeping an reference on your own, you can add the fragment to the backstack and then retrieve it using corresponding tag. This let's fragmentManager manages the caching by itself. And the second time you access a fragment, it doesn't gets recreated:
#Override
public void onNavigationDrawerItemSelected(#NonNull MenuItem item) {
if (item.isChecked())
return;
item.setChecked(true);
setTitle(item.getTitle());
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
Fragment currentlyShown = fragmentManager.findFragmentByTag(currentlyShownTag);
Fragment dest;
switch (item.getItemId()){
case R.id.nav_lgs:
dest = fragmentManager.findFragmentByTag(LGSFragment.class.getName());
if (dest == null) {
Log.d("TRANSACTION", "instanciating new fragment");
dest = new LGSFragment();
currentlyShownTag = LGSFragment.class.getName();
transaction.add(R.id.contentFrame, dest, LGSFragment.class.getName());
}
break;
...
}
if(currentlyShown != null)
transaction.hide(currentlyShown);
transaction.show(dest);
transaction.commit();
drawerLayout.closeDrawers();
return true;
}
EDIT:
Although this solution works fine, this solution uses some bad practices, I recommend using the accepted solution instead.
I've solved the problem with the help of Keivan Esbati and denvercoder9 (Thanks for that!)
Since I only have 4 fragments I keep an instance of each of them in the MainActivity, I also have a variable to track the current Fragment. Everytime I open a fragment, I hide the current fragment using the FragmentManager and calling .hide() in the transaction. Then, if the Fragment is a new Fragment I call .add() in the transaction, otherwise I call .show in the transaction.
The code for the onNavigationItemSelected() method (which triggers when a user selects an item in the menu):
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
if (!item.isChecked()) {
item.setChecked(true);
setTitle(item.getTitle());
switch (item.getItemId()) {
case R.id.nav_lgs: {
if (lgsFragment == null) {
lgsFragment = new LGSFragment();
openFragment(lgsFragment, FragmentTag.LGS.toString());
} else {
openFragment(lgsFragment, "");
}
currentFragmentTag = FragmentTag.LGS;
break;
}
case R.id.nav_users: {
if (userFragment == null) {
userFragment = new UserFragment();
openFragment(userFragment, FragmentTag.USERS.toString());
} else {
openFragment(userFragment, "");
}
currentFragmentTag = FragmentTag.USERS;
break;
}
case R.id.nav_profile: {
if (profileFragment == null) {
profileFragment = new ProfileFragment();
openFragment(profileFragment, FragmentTag.PROFILE.toString());
} else {
openFragment(profileFragment, "");
}
currentFragmentTag = FragmentTag.PROFILE;
break;
}
case R.id.nav_my_lgs: {
if (myLGSFragment == null) {
myLGSFragment = new MyLGSFragment();
openFragment(myLGSFragment, FragmentTag.MY_LGS.toString());
} else {
openFragment(myLGSFragment, "");
}
currentFragmentTag = FragmentTag.MY_LGS;
break;
}
default: {
if (lgsFragment == null) {
lgsFragment = new LGSFragment();
openFragment(lgsFragment, FragmentTag.LGS.toString());
} else {
openFragment(lgsFragment, "");
}
currentFragmentTag = FragmentTag.LGS;
break;
}
}
}
drawerLayout.closeDrawers();
return true;
}
The openFragment() method used above:
private void openFragment(Fragment fragment, String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
if (currentFragmentTag != FragmentTag.NO_FRAGMENT) {
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(currentFragmentTag.toString())).commit();
}
if (!tag.equals("")) {
fragmentManager.beginTransaction().add(R.id.contentFrame,fragment,tag).commit();
} else {
fragmentManager.beginTransaction().show(fragment).commit();
}
}
Set up in onCreate():
currentFragmentTag = FragmentTag.NO_FRAGMENT;
if (lgsFragment == null) {
lgsFragment = new LGSFragment();
openFragment(lgsFragment, FragmentTag.LGS.toString());
} else {
openFragment(lgsFragment, "");
}
currentFragmentTag = FragmentTag.LGS;

How to update All Pages in Page Viewer?

I have a Page Viewer with three pages with ArrayLists in them, represented as "Tomorrow" "Today" and "Yesterday". I also have a Drawer that allows to change settings of the Lists.
When the Drawer closes, I want the ListsViews (or the entire page fragment) to update to show three new ArrayLists that were created after the new settings were applied.
So far, I managed to be able to update "Yesterday" and "Tomorrow" (When sliding to yesterday, tomorrow updates and vice versa), I think that is because "Today" never gets destroyed.
Either way, I would really like to see all three update as soon as the Drawer closes.
Here is the code for my Adapter:`
private class MyPagerAdapter extends FragmentPagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
ListFragmentOfTomorrow torrowFragment =new ListFragmentOfTomorrow();
ListFragmentOfToday todayFragment = new ListFragmentOfToday();
ListFragmentOfYesterday yestFragment = new ListFragmentOfYesterday();
public MyPagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int pos)
{
switch (pos) {
case 0:
torrowFragment.newInstance(tomorrowArrayList, MainActivity.this);
registeredFragments.put(0, torrowFragment);
return torrowFragment;
case 1:
todayFragment.newInstance(todayArrayList, MainActivity.this);
registeredFragments.put(1, todayFragment);
return todayFragment;
case 2:
yestFragment.newInstance(yesterdayArrayList, MainActivity.this);
registeredFragments.put(2, yestFragment);
return yestFragment;
default:
todayFragment.newInstance(todayArrayList, MainActivity.this);
registeredFragments.put(3, todayFragment);
return todayFragment;
}
}
#Override
public int getCount() {
return 3;
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}`
This is the code for one of the page Fragment (all three are basically the same):
public class ListFragmentOfToday extends Fragment
{
static ExpandListAdapter ExpAdapter;
static ExpandableListView expndList;
static Context context;
static ArrayList<Game> todayArrayList;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment_list_of_today, container, false);
expndList = new ExpandableListView(context);
expndList = (ExpandableListView)v.findViewById(R.id.FragmentedExpandableListView);
ExpAdapter = new ExpandListAdapter(context,todayArrayList);
expndList.setAdapter(ExpAdapter);
return v;
}
public static ListFragmentOfToday newInstance(ArrayList<Game> todayArrayListIn, Context contextIn)
{
ListFragmentOfToday todayFragment = new ListFragmentOfToday();
context = contextIn;
todayArrayList = todayArrayListIn;
return todayFragment;
}
public void RefreshList(ArrayList<Game> todayArrayListIn)
{
todayArrayList=todayArrayListIn;
ExpAdapter.notifyDataSetChanged();
}
}
This is the code for when the Drawer closes:
#Override
public void onDrawerClosed(View drawerView)
{
//Here, the new ArrayLists are created(...)
ListFragmentOfTomorrow tomorrowFragmentToUpdate = (ListFragmentOfTomorrow)pagerAdapter.getRegisteredFragment(0);
tomorrowFragmentToUpdate.RefreshList(updatedTomorrowArrayList);
ListFragmentOfToday todayFragmentToUpdate = (ListFragmentOfToday)pagerAdapter.getRegisteredFragment(1);
todayFragmentToUpdate.RefreshList(updatedTodayArrayList);
ListFragmentOfYesterday yesterdayFragmentToUpdate = (ListFragmentOfYesterday)pagerAdapter.getRegisteredFragment(2);
yesterdayFragmentToUpdate.RefreshList(updatedYesterdayArrayList);
}
Question: How can I get all three pages to show the new updated arraylists as soon as the drawer closes?
As a new developer and a new StackOverflow user, I would also like to get any feedback on my code writing and my question format. Thank you.
Thank you Thomas! That worked. I tried notifyDataSetChanged() and getItemPosition(Object object), but never together.
dhke - I saw the instantiating fragments when I was looking for an answer, but understood the difference to newInstance()...

Where is my static context?

I'm brand new to android programming and I'm trying to use a gridviewpager with fragments. My code is:
public class Main extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.grid);
final Resources res = getResources();
GridViewPager pager = (GridViewPager) findViewById(R.id.gridpager);
pager.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener() {
#Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
// Adjust page margins:
// A little extra horizontal spacing between pages looks a bit
// less crowded on a round display.
final boolean round = insets.isRound();
int rowMargin = res.getDimensionPixelOffset(R.dimen.page_row_margin);
int colMargin = res.getDimensionPixelOffset(round ?
R.dimen.page_column_margin_round : R.dimen.page_column_margin);
pager.setPageMargins(rowMargin, colMargin);
// GridViewPager relies on insets to properly handle
// layout for round displays. They must be explicitly
// applied since this listener has taken them over.
pager.onApplyWindowInsets(insets);
return insets;
}
});
// MyPagerAdapter adapter=new MyPagerAdapter();
pager.setAdapter(new MyPagerAdapter(Activity.getFragmentManager()));
}
public class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int pos) {
switch(pos) {
case 0: return FirstFragment.newInstance("FirstFragment, Instance 1");
case 1: return SecondFragment.newInstance("SecondFragment, Instance 1");
case 2: return SecondFragment.newInstance("SecondFragment, Instance 2");
default: return SecondFragment.newInstance("SecondFragment, Default");
}
}
#Override
public int getCount() {
return 3;
}
}
}
I've collapsed some to be concise. The error is in this line:
pager.setAdapter(new MyPagerAdapter(Activity.getFragmentManager()));
This returns the error:
Non-static method 'getFragmentManager()' cannot be referenced from a static context
But I don't know what is static in my code. I've tried assigning everything I can think of to variables but still can't get this. Thanks for your help.
Activity is the name of a class. By stating Activity.getFragmentManager(), you are attempting to call a method on the class itself rather than a specific instance of the class.
In your case, you don't need the Activity. section at all - just call getFragmentManager() itself:
pager.setAdapter(new MyPagerAdapter(getFragmentManager()));

Android: Access a fragment instance from MainActivity

I've started coding a small app using Android Studio's pre-defined tabbed Layout with Fragments (SectionsPagerAdapter, ViewPager).
I've got a task running in the MainActivity.class main/Ui thread which at one point shows a dialog with the onClick method
#Override
public void onClick(DialogInterface dialog, int which) {
category = eventsToDisplay.get(which);
averageFragment.category = category;
dialog.dismiss();
}
But I can't get the averageFragment.category = category; assignment to work.
In the MainActivity's onCreate method I call averageFragment = (AverageFragment) getSupportFragmentManager().findFragmentByTag(AverageFragment.tag); but this gives me a NullPointerException.
I have already tried the following solutions (most of which are from this website):
getSupportFragmentManager().findFragmentById(R.id.fragment_average)
getSupportFragmentManager().findFragmentByTag(AverageFragment.tag) <-- basically a static variable created upon instantiating the fragment.
mSectionsPageAdapter.getItem(1)
all of which give me either a NPE or IllegalStateException.FragmentNotAttachedToView.
Other relevant code:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
switch (position) {
case 0:
return InspectionFragment.newInstance(position + 1);
case 1:
return AverageFragment.newInstance(position + 1);
case 2:
return RegulationsFragment.newInstance(position + 1);
}
return null;
}
#Override
public int getCount() {
// Show 3 total pages.
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.title_section1).toUpperCase(l);
case 1:
return getString(R.string.title_section2).toUpperCase(l);
case 2:
return getString(R.string.title_section3).toUpperCase(l);
}
return null;
}
}
Any ideas on how to access the fragment from the main activity?
Check the location where you assign the instance of your adapter to the pager. The exception your getting means that the fragment hasn't loaded to the view yet, which is likely given that you're calling your assignment:
averageFragment = (AverageFragment) getSupportFragmentManager().findFragmentByTag(AverageFragment.tag);
from the onCreate() method. Try moving this assignment to the onResume(), which should ensure that your fragment has been loaded into the view and is accessible through the supportFragmentManager. Also make sure this assignment is occurring after you set the pager's adapter within the lifecycle of your activity.

Passing data between two Fragments in a VIewPager (Android) (NullPointerException)

So basically I have 2 Fragments - FragmentConverter and FragmentFavourites, and I have one MainActivity. I'm trying to pass 4 arrays from the first fragment to the second one using an Interface called Communicator. The specific snippets are show below:
public interface Communicator {
public void respond(String[] names, String[] codes, String[] symbols, int[] images);
}
This is a method inside FragmentFavourites:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
String[] checkedNames = new String[counter];
String[] checkedCodes = new String[counter];
String[] checkedSymbols = new String[counter];
int[] checkedImages = new int[counter];
comm = (Communicator) getActivity();
int index = 0;
if (item.getItemId() == R.id.action_save){
for (int i=0;i<checked.size();i++){
if (checked.get(i) == true){
checkedNames[index] = names[i];
checkedCodes[index] = codes[i];
checkedSymbols[index] = symbols[i];
checkedImages[index] = images[i];
index++;
}
}
comm.respond(checkedNames, checkedCodes, checkedSymbols, checkedImages);
}
return super.onOptionsItemSelected(item);
}
This is the implemented interface method inside MainActivity:
#Override
public void respond(String[] names, String[] codes, String[] symbols,
int[] images) {
// TODO Auto-generated method stub
FragmentConverter frag = (FragmentConverter) fragmentPagerAdapter.getItem(1);
frag.changeData(names, codes, symbols, images);
}
And this is a method that collects the data in FragmentConverter:
public void changeData(String[] names, String[] codes, String[] symbols, int[] images){
this.names = names;
this.codes = codes;
this.symbols = symbols;
this.images = images;
Log.d("TEST", symbols.length + names.length + codes.length + images.length + "");
tvOneRate.setText(names[1]);
}
Now the problem is that whenever I try to change a ui component inside FragmentConverter, I get a NullPointerException, though the Log.d statement returns the correct results.
EDIT1: getItem() method of FragmentPagerAdapter:
#Override
public Fragment getItem(int i) {
// TODO Auto-generated method stub
Fragment frag = null;
if (i == 0){
frag = new FragmentFavourites();
}
if (i == 1){
frag = new FragmentConverter();
}
return frag;
}
EDITED:
When you call fragmentPagerAdapter.getItem(1) you are getting a new instance of the fragment so you are referring to a different object. this is why the view is null and you get the NullPointerException. If you need an adapter for only 2 fragments, you can try with something like that:
public class YourPagerAdapter extends android.support.v4.app.FragmentPagerAdapter {
private FragmentFavourites mFragFavourites;
private FragmentConverter mFragConverter;
public YourPagerAdapter() {
// ... your code above
this.mFragFavourites = new FragmentFavourites();
this.mFragConverter = new FragmentConverter();
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return mFragFavourites;
case 1:
return mFragConverter;
default:
return null;
}
}
}
As above carlo.marinangeli has suggested when you call fragmentPagerAdapter.getItem(1) you are getting a new instance of the fragment so you are referring to a different object
So to get same object from you adapter you need to store your object. you can try following method in your adapter -
public Fragment getFragmentAtPosition(int position) {
return registeredFragments.get(position);
}
where registeredFragments is -
private SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
and fill this sparseArray in getItem method like below -
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
fragment = FragmentPost.newInstance(position);
registeredFragments.put(position, fragment);
return fragment;
}
return null;
}
By using fragmentPagerAdapter.getItem(pos) method I was referring to a new object every time the respond() method was called. I fixed it by using findFragmentByTag() method instead:
#Override
public void respond(String[] names, String[] codes, String[] symbols,
int[] images) {
FragmentManager manager = getSupportFragmentManager();
FragmentConverter frag = (FragmentConverter) manager.findFragmentByTag("android:switcher:" + pager.getId() + ":" + 1);
frag.changeData(names, codes, symbols, images);
}
you can get that error because you are assuming that you have got the FragmentConverter and the views associated to it.
Without a logcat it becomes a little bit difficult to help you but basically what I would like to do in a situation like this is to pass everything through the activity without letting know the existence of the other fragment to the fragments.
F1 modifies a state object into the activity
F2 has to register as a
listener to the activity (be aware that the fragment can be attached
and detached in the view pager)
The Activity as soon it receives an updated, looks for all the registered listeners and if there is someone it delivers the updated

Categories