2

Just one of the many issue with fragments. In Activity.onCreate() method I'm initializing a ViewPager with two Fragments in it.

    viewPager = (ViewPager) findViewById(R.id.viewpager);
    viewPager.setOffscreenPageLimit(1);
    adapter = new ViewPagerAdapter(getSupportFragmentManager());
    adapter.addFragment(new AutoFragment(), getString(R.string.song_header_automatic));
    adapter.addFragment(new ManualFragment(), getString(R.string.song_header_manual));
    adapter.notifyDataSetChanged();
    viewPager.setAdapter(adapter);

    TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
    tabLayout.setupWithViewPager(viewPager);

After that I need to perform an action on a View in the second Fragment of the ViewPager.

((Fragment) adapter.getItem(1)).getView().findViewById(...)

But it gives NPE since the user never browse to the second fragment hence it was not attached yet. Accessing an element of the first Fragment in the same way gives no problems instead.

Shouldn't setOffscreenPageLimit(1) method tell Android to render also the second Fragment?

I also tried not calling super method of destroyItem inside the adapter, but no luck since the problem is that the fragment is not even initialized.

public class ViewPagerAdapter extends FragmentStatePagerAdapter {
    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();

    public ViewPagerAdapter(FragmentManager manager) {
        super(manager);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }

    public void addFragment(Fragment fragment, String title) {
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mFragmentTitleList.get(position);
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {}
}

How can I force it so also non visible fragment is rendered?

NOTE: this problem happens only sometimes to app users and I'm not able to reproduce it. Only once I had this problem, so I think it's all about performing the faulty operation after the correct lifecycle event.

fillobotto
  • 3,698
  • 5
  • 34
  • 58

1 Answers1

0

As documentation of FragmentStatePagerAdapter:

This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.

You might try using FragmentPagerAdapter instead.

Edit:

Since it happens on few devices, I'll guess it is a low memory device.

You might try to reload the second fragment, passing args to it.

At your adapter, implement a method that replaces the fragment:

public void replaceFragment(int index, Fragment fragment, String title) {
    mFragmentList.set(index, fragment);
    mFragmentTitleList.add(index, title);
}

When you want to reload the second fragment with a new data, just do:

Bundle fragmentArgs = new Bundle();
fragmentArgs.putSerializable("obj", obj); //data you want to pass
fragmentArgs.putString("str", str); //data you want to pass
ManualFragment manualFragment = new ManualFragment();
manualFragment.setArguments(fragmentArgs);

adapter.replaceFragment(1, manualFragment, getString(R.string.song_header_manual));
adapter.notifyDataSetChanged();

Then at onCreateView of ManualFragment, get the arguments:

Object obj = (Object) getArguments().getSerializable("obj");
String str = getArguments().getString("str");

And do whatever you have to do.

Edit 2:

Since the OP wants another approach, here it goes...

Do something like this answer: https://stackoverflow.com/a/24343211/2174489

This way you won't access view from another fragment outside the original scope. I don't like the idea of getting like ((Fragment) adapter.getItem(1)).getView().findViewById(...). I'd rather do ((Fragment) adapter.getItem(1)).setTextViewText(...).

Then, at setTextViewText you can test if your view is null or not. If it is null, save the string in a global variable:

public void setTextViewText(String value){
    if (mTextView == null)
        myGlobalValue = value;
    else
        mTextView.setText(value);
}

Then at the onCreateView, you could set that value:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    if (myGlobalValue != null)
        mTextView.setText(myGlobalValue);
    ...
}

Let me know if that solves the problem.

Community
  • 1
  • 1
Eduardo Herzer
  • 2,033
  • 21
  • 24
  • Interesting. I'll give an update to the beta version and see if the problem shows up again. – fillobotto Feb 16 '17 at 11:09
  • Unfortunatly the problem persists. It sounds more like the fragment is intantiated but it's not attached to the activity – fillobotto Feb 17 '17 at 09:35
  • @fillobotto I edited the answer. Maybe you could try this approach – Eduardo Herzer Feb 17 '17 at 12:35
  • But that's an unpractical way since the fragment initialization is complex and I shouldn't do it every time I need to change a value. This sounds more like a workaround, but it's not a _solution_ – fillobotto Feb 21 '17 at 19:27
  • @fillobotto I still think that is the best way, but you could try another approach. Check edit 2 – Eduardo Herzer Feb 21 '17 at 20:16
  • This is a clever method, also doesn't break fragment logic. I pushed a beta version with this so let's see the reports. – fillobotto Feb 22 '17 at 23:44