Null object reference when using getChildFragmentManager().beginTransaction() - android

I have 4 fragments in my ViewPgaer using FragmentPagerAdapter. I try to add each fragment after a constant delay to have a jerk free animation while switching bottom bar tabs.
I use this library for the bottom bar which has a shifting animation. All four fragments have multiple api calls and if I don't add these after a delay, the shifting animation is jerky.
The problem is I get an error log (posted below) which is hard to replicate.
Error Log:
Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
at android.support.v4.app.BackStackRecord.doAddOp(BackStackRecord.java:380)
at android.support.v4.app.BackStackRecord.add(BackStackRecord.java:369)
at com.vanitee.services.view.FragmentFake$1.run(FragmentFake.java:46)
at android.os.Handler.handleCallback(Handler.java:761)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6585)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:941)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:831)
ViewPagerAdapterClass:
class MyAccountPagerAdapter extends FragmentPagerAdapter {
public MyAccountPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
if (position == ME_TAB) {
if (Utility.isEmpty(fragmentProfile)) {
fragmentProfile = FragmentFake.create(new FragmentUserProfile(), 50);
}
return fragmentProfile;
} else if (position == BOOKINGS_TAB) {
if (Utility.isEmpty(fragmentBooking)) {
fragmentBooking = FragmentFake.create(new FragmentBookings(), 100);
}
return fragmentBooking;
} else if (position == WALLET_TAB) {
if (Utility.isEmpty(fragmentWallet)) {
fragmentWallet = FragmentFake.create(new FragmentWallet(), 150);
}
return fragmentWallet;
} else if (position == LIKES_TAB) {
if (Utility.isEmpty(fragmentLikes)) {
fragmentLikes = FragmentFake.create(new FragmentLikes(), 200);
}
return fragmentLikes;
}
return null;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case ME_TAB:
return getContext().getString(R.string.me_tab);
case BOOKINGS_TAB:
return getContext().getString(R.string.bookings_tab);
case WALLET_TAB:
return getContext().getString(R.string.wallet_tab);
case LIKES_TAB:
return getContext().getString(R.string.likes_tab);
default:
return null;
}
}
#Override
public int getCount() {
return 4;
}
}
FragmentFake:
public class FragmentFake extends Fragment {
private View root;
Fragment fragment;
public static Fragment create(Fragment frag, int timeMs) {
FragmentFake fragmentFake = new FragmentFake();
Bundle bundle = new Bundle();
fragmentFake.fragment = frag;
bundle.putInt("time", timeMs);
fragmentFake.setArguments(bundle);
return fragmentFake;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
root = inflater.inflate(R.layout.layout_fake_fragment, container, false);
Bundle bundle = getArguments();
replaceFragment(bundle.getInt("time"));
return root;
}
private void replaceFragment(int time) {
root.postDelayed(new Runnable() {
#Override
public void run() {
if(!isAdded()){
return;
}
getChildFragmentManager().beginTransaction().add(R.id.replace_fragment_container, fragment).commit();
}
}, time);
}
}
The Error log reported is on the line
getChildFragmentManager().beginTransaction().add(R.id.replace_fragment_container, fragment).commit();

Create one Abstract Fragment that extends Fragment. like this
public abstract BaseChildFragment extends Fragment{
public void onViewPagerPagechange();
}
Make All ChildFragment extends BaseChildFragment(FragmentUserProfile, FragmentUserLike,FragmentUserWellet,ETC) ,
On your Adapter add below code.
LruCache<Integer,BaseChildFragment> cache = new LruCache<>(4);
public BaseChildFragment getBaseChildFragment(int position){
return cache.get(position);
}
#Override
public Fragment getItem(int position) {
if (position == ME_TAB) {
if (Utility.isEmpty(fragmentProfile)) {
fragmentProfile = FragmentFake.create(new FragmentUserProfile(), 50);
cache.put(position,fragmentProfile );
}
return fragmentProfile;
} else if (position == BOOKINGS_TAB) {
if (Utility.isEmpty(fragmentBooking)) {
fragmentBooking = FragmentFake.create(new FragmentBookings(), 100);
cache.put(position,fragmentBooking );
}
return fragmentBooking;
} else if (position == WALLET_TAB) {
if (Utility.isEmpty(fragmentWallet)) {
fragmentWallet = FragmentFake.create(new FragmentWallet(), 150);
cache.put(position,fragmentWallet );
}
return fragmentWallet;
} else if (position == LIKES_TAB) {
if (Utility.isEmpty(fragmentLikes)) {
fragmentLikes = FragmentFake.create(new FragmentLikes(), 200);
cache.put(position,fragmentLikes );
}
return fragmentLikes;
}
return null;
}
On your Fragment that have View pager AddpageChangeListener like this
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
// optional
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { }
// optional
#Override
public void onPageSelected(int position) {
BaseChildFragment fragment = adapter.getBaseChildFragment(position);
fragmeng.onViewPagerPagechange();
}
// optional
#Override
public void onPageScrollStateChanged(int state) { }
});
This will call your ChildFragment onViewPagerPagechange(); method. where you can load your child fragments.

You cannot do this
public static Fragment create(Fragment frag, int timeMs) {
FragmentFake fragmentFake = new FragmentFake();
Bundle bundle = new Bundle();
//remove this line, it won't work
fragmentFake.fragment = frag;
bundle.putInt("time", timeMs);
fragmentFake.setArguments(bundle);
return fragmentFake;
}
You can remove that assignment line from the create statement and add another method to assign the fragment after an instance is created. Your code should be like this
public static Fragment create(Fragment frag, int timeMs) {
FragmentFake fragmentFake = new FragmentFake();
Bundle bundle = new Bundle();
bundle.putInt("time", timeMs);
fragmentFake.setArguments(bundle);
return fragmentFake;
}
public void setFragment(Fragment fragment){
this.fragment = fragment;
}
//How you assign fragment
FragmentFake fakeFragment = FragmentFake.create(0);
fakeFragment.setFragment(yourFragment);
Then make the fragment transaction

Related

How to switch List fragment and Detail fragment in view pager tab?

Im trying to implement switching of fragments inside first tab of view pager.I tried with approach found here link which uses root fragment in first tab and then switches child fragments. But when in second child fragment screen rotation occurs app switches back to first child fragment and besides that it seems like a hack.
I tried searching for other approaches but found dozens of answers that all seems messy.
Explanation from here seems right but i dont fully understand it.Here is the explanation and code from that link:
This approach is to switch the fragment that displays inside the FragmentPagerAdapter by overriding the getItem(...) method as well as getItemPosition(Object object) which is invoked every time you call viewPager.getAdapter().notifyDataSetChanged(). For example:
public static class MyPagerAdapter extends FragmentStatePagerAdapter {
// Return a different fragment for position based on additional state tracked in a member variable
#Override
public Fragment getItem(int position) {
// For a given position, return two different potential fragments based on a condition
}
// Force a refresh of the page when a different fragment is displayed
#Override
public int getItemPosition(Object object) {
// this method will be called for every fragment in the ViewPager
if (object instanceof SomePermanantCachedFragment) {
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;
}
}
}
Lets say i have ListFragment and DetailFragment that should switch in first tab.
Where will i call viewPager.getAdapter().notifyDataSetChanged() ?
(...based on additional state tracked in a member variable) how do i implement this?
SomePermanantCachedFragment refers to ListFragment in my case?
I also tried searching github for example but with no success...
If your fragments in a PagerAdapter are dynamic, then you should use a Dynamic FragmentPagerAdapter.
public class DynamicFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "DynamicFragmentPagerAdapter";
private final FragmentManager fragmentManager;
public static abstract class FragmentIdentifier implements Parcelable {
private final String fragmentTag;
private final Bundle args;
public FragmentIdentifier(#NonNull String fragmentTag, #Nullable Bundle args) {
this.fragmentTag = fragmentTag;
this.args = args;
}
protected FragmentIdentifier(Parcel in) {
fragmentTag = in.readString();
args = in.readBundle(getClass().getClassLoader());
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(fragmentTag);
dest.writeBundle(args);
}
protected final Fragment newFragment() {
Fragment fragment = createFragment();
Bundle oldArgs = fragment.getArguments();
Bundle newArgs = new Bundle();
if(oldArgs != null) {
newArgs.putAll(oldArgs);
}
if(args != null) {
newArgs.putAll(args);
}
fragment.setArguments(newArgs);
return fragment;
}
protected abstract Fragment createFragment();
}
private ArrayList<FragmentIdentifier> fragmentIdentifiers = new ArrayList<>();
private FragmentTransaction currentTransaction = null;
private Fragment currentPrimaryItem = null;
public DynamicFragmentPagerAdapter(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
}
private int findIndexIfAdded(FragmentIdentifier fragmentIdentifier) {
for (int i = 0, size = fragmentIdentifiers.size(); i < size; i++) {
FragmentIdentifier identifier = fragmentIdentifiers.get(i);
if (identifier.fragmentTag.equals(fragmentIdentifier.fragmentTag)) {
return i;
}
}
return -1;
}
public void addFragment(FragmentIdentifier fragmentIdentifier) {
if (findIndexIfAdded(fragmentIdentifier) < 0) {
fragmentIdentifiers.add(fragmentIdentifier);
notifyDataSetChanged();
}
}
public void removeFragment(FragmentIdentifier fragmentIdentifier) {
int index = findIndexIfAdded(fragmentIdentifier);
if (index >= 0) {
fragmentIdentifiers.remove(index);
notifyDataSetChanged();
}
}
#Override
public int getCount() {
return fragmentIdentifiers.size();
}
#Override
public void startUpdate(#NonNull ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this
+ " requires a view id");
}
}
#SuppressWarnings("ReferenceEquality")
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
if (currentTransaction == null) {
currentTransaction = fragmentManager.beginTransaction();
}
final FragmentIdentifier fragmentIdentifier = fragmentIdentifiers.get(position);
// Do we already have this fragment?
final String name = fragmentIdentifier.fragmentTag;
Fragment fragment = fragmentManager.findFragmentByTag(name);
if (fragment != null) {
currentTransaction.attach(fragment);
} else {
fragment = fragmentIdentifier.newFragment();
currentTransaction.add(container.getId(), fragment, fragmentIdentifier.fragmentTag);
}
if (fragment != currentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
if (currentTransaction == null) {
currentTransaction = fragmentManager.beginTransaction();
}
currentTransaction.detach((Fragment) object);
}
#SuppressWarnings("ReferenceEquality")
#Override
public void setPrimaryItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
Fragment fragment = (Fragment) object;
if (fragment != currentPrimaryItem) {
if (currentPrimaryItem != null) {
currentPrimaryItem.setMenuVisibility(false);
currentPrimaryItem.setUserVisibleHint(false);
}
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
currentPrimaryItem = fragment;
}
}
#Override
public void finishUpdate(#NonNull ViewGroup container) {
if (currentTransaction != null) {
currentTransaction.commitNowAllowingStateLoss();
currentTransaction = null;
}
}
#Override
public boolean isViewFromObject(#NonNull View view, #NonNull Object object) {
return ((Fragment) object).getView() == view;
}
#Override
public Parcelable saveState() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList("fragmentIdentifiers", fragmentIdentifiers);
return bundle;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
Bundle bundle = ((Bundle)state);
bundle.setClassLoader(loader);
fragmentIdentifiers = bundle.getParcelableArrayList("fragmentIdentifiers");
}
}
Once you have that, you can use removeFragment and addFragment to change the fragments inside your ViewPager.
You could technically add a setFragment(int index) method to replace it at index 0 specifically.
If the fragments don't seem to update, the trick to refreshing a ViewPager is always setAdapter(null); setAdapter(adapter);.

Refresh a fragment inside a viewpager which is inside a fragment

I have an activity which consists of three fragments. In one of these fragments I have a viewpager that consists of three other fragments. I call getChildFragmentManager() inside fragment that has viewpager in it. I have searched SO and read almost every answer that are related to this topic for example: this and this and one that seemed most useful was this but could not solve my problem. I think if i can just return POSITION_NONE for getItemPosition(Object object) in FragmentStatePagerAdapter and call notifyDataSetChanged() will solve my problem but every time I do this my app crashes with this error:
java.lang.IllegalStateException: FragmentManager is already executing
transactions!!!
and I mentioned that I am using getChildFragmentManager() inside my fragment.
Anyone thinks of any solution to this problem?
this is the parent fragment:
public class WordDetailFragment extends Fragment {
#BindView(R.id.word_parts_tab_layout)
TabLayout mTabLayout;
#BindView(R.id.word_part_view_pager)
ViewPager mViewPager;
private WordPagerAdapter mPagerAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_word_detail, container, false);
ButterKnife.bind(this, view);
mPagerAdapter = new WordPagerAdapter(getChildFragmentManager(), getActivity());
mViewPager.setAdapter(mPagerAdapter);
mTabLayout.setupWithViewPager(mViewPager);
return view;
}
public void notifyTranslateChanged() {
mViewPager.getAdapter().notifyDataSetChanged();
}
}
this is the code for adapter
public class WordPagerAdapter extends FragmentStatePagerAdapter {
final int PAGE_COUNT = 3;
private String[] tabTitles = new String[]{"Definition", "Example", "Col & fam"};
private Context context;
public WordPagerAdapter(FragmentManager fm, Context context) {
super(fm);
this.context = context;
}
#Override
public int getCount() {
return PAGE_COUNT;
}
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
case 0:
fragment = new WordDefinitionFragment();
break;
case 1:
fragment = new WordExampleFragment();
break;
case 2:
fragment = new WordColFamFragment();
break;
default: fragment = new WordDefinitionFragment();
}
return fragment;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public CharSequence getPageTitle(int position) {
// Generate title based on item position
return tabTitles[position];
}
}
One of the fragments in viewpager
public class WordDefinitionFragment extends Fragment {
#BindView(R.id.show_translate_switch)
Switch mShowTranslateSwitch;
private List<WordInfo> mWordInfoList;
private List<Definition> mDefinitionList;
private boolean mShowTranslation;
private View mView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.fragment_word_definition, container, false);
ButterKnife.bind(this, mView);
mShowTranslation = SharedPrefHelper.getBooleanInfo(getActivity(), getString(R.string.show_translate_key));
mShowTranslateSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (getActivity() != null)
((WordActivity) getActivity()).changeTranslateMode(b);
showTranslation(b);
}
});
return mView;
}
}
and my activity. U have just included the parts that i felt are needed here.
public class WordActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceSate) {
super.onCreate(savedInstanceSate);
setContentView(R.layout.activity_word);
}
public void changeTranslateMode(boolean show) {
SharedPrefHelper.setBooleanInfo(this, getString(R.string.show_translate_ke)y, show);
((WordDetailFragment)mWordDetailFragment).notifyTranslateChanged();
}
}
and my logcat error is
After many searches I could figure out a way which is not very smart but for the time being has solved my problem.
First of all I used the class FixedFragmentStatePagerAdapter from this post. I also had to remove the part in which the fragment bundle is restored because it caches fragment variables and views.
public abstract class FixedFragmentStatePagerAdapter extends PagerAdapter {
private static final String TAG = "PagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();
private ArrayList<String> mSavedFragmentTags = new ArrayList<>();
private ArrayList<Fragment> mFragments = new ArrayList<>();
private Fragment mCurrentPrimaryItem = null;
public FixedFragmentStatePagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
public String getTag(int position) {
return null;
}
#Override
public void startUpdate(ViewGroup container) {
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
String fragmentTag = getTag(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment + " t=" + fragmentTag);
/* if (mSavedState.size() > position) {
String savedTag = mSavedFragmentTags.get(position);
if (TextUtils.equals(fragmentTag, savedTag)) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
}*/
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment, fragmentTag);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView() + " t=" + fragment.getTag());
while (mSavedState.size() <= position) {
mSavedState.add(null);
mSavedFragmentTags.add(null);
}
mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
mSavedFragmentTags.set(position, fragment.getTag());
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
#Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
#Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
#Override
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
state.putStringArrayList("tags", mSavedFragmentTags);
}
for (int i=0; i<mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null) {
if (state == null) {
state = new Bundle();
}
String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
}
}
return state;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle)state;
bundle.setClassLoader(loader);
Parcelable[] fss = bundle.getParcelableArray("states");
mSavedState.clear();
mFragments.clear();
ArrayList<String> tags = bundle.getStringArrayList("tags");
if (tags != null) {
mSavedFragmentTags = tags;
} else {
mSavedFragmentTags.clear();
}
if (fss != null) {
for (int i=0; i<fss.length; i++) {
mSavedState.add((Fragment.SavedState)fss[i]);
}
}
Iterable<String> keys = bundle.keySet();
for (String key: keys) {
if (key.startsWith("f")) {
int index = Integer.parseInt(key.substring(1));
Fragment f = mFragmentManager.getFragment(bundle, key);
if (f != null) {
while (mFragments.size() <= index) {
mFragments.add(null);
}
f.setMenuVisibility(false);
mFragments.set(index, f);
} else {
Log.w(TAG, "Bad fragment at key " + key);
}
}
}
}
}
}
Then I implemented ViewPager.OnPageChangeListener in the fragment that contains the viewpager. And in the void onPageScrollStateChanged(int state) method I updated the view of my fragments in viewpager.
public class WordDetailFragment extends Fragment implements ViewPager.OnPageChangeListener {
#BindView(R.id.word_parts_tab_layout)
TabLayout mTabLayout;
#BindView(R.id.word_part_view_pager)
ViewPager mViewPager;
private WordPagerAdapter mPagerAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_word_detail, container, false);
ButterKnife.bind(this, view);
mPagerAdapter = new WordPagerAdapter(getChildFragmentManager(), getActivity());
mViewPager.setAdapter(mPagerAdapter);
mTabLayout.setupWithViewPager(mViewPager);
mViewPager.addOnPageChangeListener(this);
return view;
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
Fragment mFragment;
#Override
public void onPageSelected(int position) {
mFragment = mPagerAdapter.getItem(position);
}
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
if (mFragment instanceof WordDefinitionFragment) {
((WordDefinitionFragment) mFragment).showTranslation();
//Log.e("StateChanged", mFragment.getClass().getSimpleName());
}
if (mFragment instanceof WordExampleFragment) {
((WordExampleFragment) mFragment).showTranslation();
//Log.e("StateChanged", mFragment.getClass().getSimpleName());
}
if (mFragment instanceof WordColFamFragment) {
((WordColFamFragment) mFragment).showTranslation();
//Log.e("StateChanged", mFragment.getClass().getSimpleName());
}
}
}
}
And in my fragments, I had a callback interface which was implemented in the activity. And in void setUserVisibleHint(boolean isVisibleToUser) method I updated the UI again because neighbor fragments did not get updated.
public class WordExampleFragment extends Fragment {
#BindView(R.id.example_field_name_id)
TextView mExampleFieldNameTV;
#BindView(R.id.word_tv)
TextView mWordTv;
#BindView(R.id.play_sound_iv)
ImageView mPlaySoundIv;
#BindView(R.id.show_translate_switch)
Switch mShowTranslateSwitch;
private TextView mExample1ContentFaTV;
private List<WordInfo> mWordInfoList;
private List<Example> mExampleList;
private int mWordNumber;
//callback interface
private TranslateModeChangeListener mWordPartFragments;
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mWordPartFragments = (TranslateModeChangeListener) context;
} catch (ClassCastException ex) {
throw new ClassCastException("Must implement WordPartFragments interface");
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_word_example, container, false);
ButterKnife.bind(this, view);
mWordInfoList = WordActivity.sWordInfoList;
mWordNumber = WordActivity.sWordNumber;
mExampleList = mWordInfoList.get(mWordNumber).getExamples();
showTranslation();
mShowTranslateSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
//this method is called from activity=> callback interface
mWordPartFragments.changeTranslateMode(b);
showTranslation();
}
});
return view;
}
//this method is called when viewpager shows this fragment and it is visible to user
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
showTranslation();
}
}
//this methoud updates my UI: shows or hides the translation
public void showTranslation() {
if (isAdded()) {
boolean showTranslation = WordActivity.sTranslationShown;
//Log.e("Ex", "added & shown = " + showTranslation);
mShowTranslateSwitch.setChecked(showTranslation);
mExample1ContentFaTV.setVisibility((showTranslation) ? View.VISIBLE : View.INVISIBLE);
}
}
}

add callback listener in each viewpager fragment

I've 2 fragments in viewpager and I want to add callback methods using interface in both. I add the interface listener while initiating fragments in viewpager adapter but only the last fragment's is being called. That's why when I click on the MainActivity's menu option, only the Toast in MyFragB is being called even if MyFragA is showing.
Both Fragments... Only Activity calling MyFragB Toast is showing when wither fragment is opened
public class MyFragA extends Fragment implements FragmentCaller {
private TextView myTxt;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.myfraga, container, false);
}
#Override
public void CallFragment() {
if(getActivity()!=null)
Toast.makeText(getActivity(), "Activity calling MyFragA", Toast.LENGTH_SHORT).show();
}
}
public class MyFragB extends Fragment implements FragmentCaller{
private TextView myTxt;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.myfragb, container, false);
}
#Override
public void CallFragment() {
if(getActivity()!=null)
Toast.makeText(getActivity(), "Activity calling MyFragB", Toast.LENGTH_SHORT).show();
}
}
Main Activity
public class MainActivity extends AppCompatActivity {
TabLayout tabs;
ViewPager viewpager;
FragmentCaller fragListerner = null;
public interface FragmentCaller {
void CallFragment();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tabs = (TabLayout)findViewById(R.id.tabs);
viewpager = (ViewPager)findViewById(R.id.viewpager);
SimpleFragmentPagerAdapter adapter = new SimpleFragmentPagerAdapter(this, getSupportFragmentManager());
viewpager.setAdapter(adapter);
tabs.setupWithViewPager(viewpager);
} // onCreate ends
public class SimpleFragmentPagerAdapter extends FragmentStatePagerAdapter {
private Context mContext;
public SimpleFragmentPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
// This determines the fragment for each tab
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
fragment = new MyFragA();
fragListerner = (FragmentCaller) fragment;
return fragment;
} else if (position == 1){
fragment = new MyFragB();
fragListerner = (FragmentCaller)fragment;
return fragment;
}
return null;
}
// This determines the number of tabs
#Override
public int getCount() {
return 2;
}
// This determines the title for each tab
#Override
public CharSequence getPageTitle(int position) {
// Generate title based on item position
switch (position) {
case 0:
return mContext.getString(R.string.fraga);
case 1:
return mContext.getString(R.string.fragb);
default:
return null;
}
}
}
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.callback, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()){
case R.id.call:
if(fragListerner!=null){
if(fragListerner instanceof MyFragA){
fragListerner.CallFragment();
}else if(fragListerner instanceof MyFragB){
fragListerner.CallFragment();
}
}
break;
}
return true;
}
}
I used rick's answer from here.
public class MainActivity extends AppCompatActivity {
TabLayout tabs;
ViewPager viewpager;
public static int currentFrag=0;
SimpleFragmentPagerAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tabs = (TabLayout)findViewById(R.id.tabs);
viewpager = (ViewPager)findViewById(R.id.viewpager);
adapter = new SimpleFragmentPagerAdapter(this, getSupportFragmentManager());
viewpager.setAdapter(adapter);
tabs.setupWithViewPager(viewpager);
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
System.out.println("sammy_current_position: "+position);
currentFrag = position;
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
} // onCreate ends
public class SimpleFragmentPagerAdapter extends FragmentStatePagerAdapter {
private Context mContext;
public SimpleFragmentPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
#Override
public Fragment getItem(int position) {
if (position == 0) {
return new MyFragA();
} else if (position == 1){
return new MyFragB();
}
return null;
}
#Override
public int getCount() {
return 2;
}
// This determines the title for each tab
#Override
public CharSequence getPageTitle(int position) {
// Generate title based on item position
switch (position) {
case 0:
return mContext.getString(R.string.fraga);
case 1:
return mContext.getString(R.string.fragb);
default:
return null;
}
}
}
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.callback, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()){
case R.id.call:
if(currentFrag == 0) {
MyFragA frag1 = (MyFragA)viewpager.getAdapter().instantiateItem(viewpager, viewpager.getCurrentItem());
frag1.specialCase();
}
break;
}
return true;
}
}
but only the last fragment's is being called
Of Course ! because at pos == 0 you are setting fragListerner = (FragmentCaller) fragment then at again in pos == 1 you re-set fragListerner = (FragmentCaller)fragment;. So this condition, fragListerner instanceof MyFragB will always be true!
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
fragment = new MyFragA();
fragListerner = (FragmentCaller) fragment;
return fragment;
} else if (position == 1){
fragment = new MyFragB();
fragListerner = (FragmentCaller)fragment;
return fragment;
}
return null;
}
So, you are trying to communicate from activity to fragment
okay, if you still want to use interfaces, you can do it like that:
create an instance of your interface for each fragment! and in getItem assign them both,
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
fragment = new MyFragA();
fragAListerner = (FragmentCaller) fragment;
return fragment;
} else if (position == 1){
fragment = new MyFragB();
fragBListerner = (FragmentCaller)fragment;
return fragment;
}
return null;
}
But, there is an easier way to call fragment's methods from it's parent activity, which is like follows, for ex:
Fragment fragment = adapter.getItem(0); // MyFragA is at pos 0
fragment.<specific_function_name>();
Or, by using it's id!
MyFragA fragment = (MyFragA) getFragmentManager().findFragmentById(R.id.MyFragAId);
fragment.<specific_function_name>();
Or, by using it's tag!
MyFragA fragment = (MyFragA) getFragmentManager().findFragmentByTag("MyFragATag");
fragment.<specific_function_name>();

Roman Nurik's WizardPager inside Fragment

I have a FragmentActivity with different Fragments inside which can be switched through a NavigationDrawer.
Roman Nurik's original code was written with a FragmentActivity but I need it inside a Fragment...
This is my Activity (shortened):
public class MainActivity extends FragmentActivity implements
PageFragmentCallbacks, ReviewFragment.Callbacks, ModelCallbacks {
// those are the methods I took from the sample
#Override
public void onPageTreeChanged() {
mCurrentPageSequence = mWizardModel.getCurrentPageSequence();
recalculateCutOffPage();
mStepPagerStrip.setPageCount(mCurrentPageSequence.size() + 1); // + 1 =
// review
// step
mPagerAdapter.notifyDataSetChanged();
updateBottomBar();
}
private static void updateBottomBar() {
int position = mPager.getCurrentItem();
if (position == mCurrentPageSequence.size()) {
mNextButton.setText(R.string.finish);
mNextButton.setBackgroundResource(R.drawable.finish_background);
mNextButton.setTextAppearance(myContext,
R.style.TextAppearanceFinish);
} else {
mNextButton.setText(mEditingAfterReview ? R.string.review
: R.string.next);
mNextButton
.setBackgroundResource(R.drawable.selectable_item_background);
TypedValue v = new TypedValue();
myContext.getTheme().resolveAttribute(
android.R.attr.textAppearanceMedium, v, true);
mNextButton.setTextAppearance(myContext, v.resourceId);
mNextButton.setEnabled(position != mPagerAdapter.getCutOffPage());
}
mPrevButton
.setVisibility(position <= 0 ? View.INVISIBLE : View.VISIBLE);
}
#Override
protected void onDestroy() {
super.onDestroy();
mWizardModel.unregisterListener(this);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBundle("model", mWizardModel.save());
}
#Override
public AbstractWizardModel onGetModel() {
return mWizardModel;
}
#Override
public void onEditScreenAfterReview(String key) {
for (int i = mCurrentPageSequence.size() - 1; i >= 0; i--) {
if (mCurrentPageSequence.get(i).getKey().equals(key)) {
mConsumePageSelectedEvent = true;
mEditingAfterReview = true;
mPager.setCurrentItem(i);
updateBottomBar();
break;
}
}
}
#Override
public void onPageDataChanged(Page page) {
if (page.isRequired()) {
if (recalculateCutOffPage()) {
mPagerAdapter.notifyDataSetChanged();
updateBottomBar();
}
}
}
#Override
public Page onGetPage(String key) {
return mWizardModel.findByKey(key);
}
private static boolean recalculateCutOffPage() {
// Cut off the pager adapter at first required page that isn't completed
int cutOffPage = mCurrentPageSequence.size() + 1;
for (int i = 0; i < mCurrentPageSequence.size(); i++) {
Page page = mCurrentPageSequence.get(i);
if (page.isRequired() && !page.isCompleted()) {
cutOffPage = i;
break;
}
}
if (mPagerAdapter.getCutOffPage() != cutOffPage) {
mPagerAdapter.setCutOffPage(cutOffPage);
return true;
}
return false;
}
}
This is my Fragment code:
public static class MyFragment extends Fragment {
public MyFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main_dummy3,
container, false);
if (savedInstanceState != null) {
mWizardModel.load(savedInstanceState.getBundle("model"));
}
mWizardModel.registerListener(this);
mPagerAdapter = new MyPagerAdapter(fragmentManager1);
mPager = (ViewPager) rootView.findViewById(R.id.pager);
mPager.setAdapter(mPagerAdapter);
mStepPagerStrip = (StepPagerStrip) rootView
.findViewById(R.id.strip);
mStepPagerStrip
.setOnPageSelectedListener(new StepPagerStrip.OnPageSelectedListener() {
#Override
public void onPageStripSelected(int position) {
position = Math.min(mPagerAdapter.getCount() - 1,
position);
if (mPager.getCurrentItem() != position) {
mPager.setCurrentItem(position);
}
}
});
mNextButton = (Button) rootView.findViewById(R.id.next_button);
mPrevButton = (Button) rootView.findViewById(R.id.prev_button);
mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
mStepPagerStrip.setCurrentPage(position);
if (mConsumePageSelectedEvent) {
mConsumePageSelectedEvent = false;
return;
}
mEditingAfterReview = false;
updateBottomBar();
}
});
mNextButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mPager.getCurrentItem() == mCurrentPageSequence.size()) {
DialogFragment dg = new DialogFragment() {
#Override
public Dialog onCreateDialog(
Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(
R.string.submit_confirm_message)
.setPositiveButton(
R.string.submit_confirm_button,
null)
.setNegativeButton(
android.R.string.cancel, null)
.create();
}
};
dg.show(fragmentManager1, "place_order_dialog");
} else {
if (mEditingAfterReview) {
mPager.setCurrentItem(mPagerAdapter.getCount() - 1);
} else {
mPager.setCurrentItem(mPager.getCurrentItem() + 1);
}
}
}
});
mPrevButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mPager.setCurrentItem(mPager.getCurrentItem() - 1);
}
});
onPageTreeChanged();
updateBottomBar();
return rootView;
}
public class MyPagerAdapter extends FragmentStatePagerAdapter {
private int mCutOffPage;
private Fragment mPrimaryItem;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
if (i >= mCurrentPageSequence.size()) {
return new ReviewFragment();
}
return mCurrentPageSequence.get(i).createFragment();
}
#Override
public int getItemPosition(Object object) {
if (object == mPrimaryItem) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
#Override
public void setPrimaryItem(ViewGroup container, int position,
Object object) {
super.setPrimaryItem(container, position, object);
mPrimaryItem = (Fragment) object;
}
#Override
public int getCount() {
return 6;
// return Math.min(mCutOffPage + 1,
// mCurrentPageSequence.size() + 1);
}
public void setCutOffPage(int cutOffPage) {
if (cutOffPage < 0) {
cutOffPage = Integer.MAX_VALUE;
}
mCutOffPage = cutOffPage;
}
public int getCutOffPage() {
return mCutOffPage;
}
}
}
I get the following errors:
The method registerListener(ModelCallbacks) in the type AbstractWizardModel is not applicable for the arguments (MainActivity.MyFragment)
and:
Cannot make a static reference to the non-static method onPageTreeChanged() from the type MainActivity
What can I do to fix those errors?
Try changing:
registerListener(this)
for
registerListener(getActivity())
The other error, as you can read from the message, your fragment is static and you are calling onPageTreeChanged() that is in the MainActivity, which is not static. You could keep a reference to the MainActivity this way:
First in your Fragment declare a variable of type ModelCallbacks:
ModelCallbacks mModelCallback;
Then, override the Fragment's onAttach method, and set the reference:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mModelCallback = (ModelCallbacks)activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement ModelCallbacks");
}
}
Finally, instead of calling directly onPageTreeChanged() from the fragment, change the line for:
mModelCallback.onPageTreeChanged();
That should solve your errors.
This is most likely too late a response. But in case someone else falls victim of a similar issue:
Your fragment doesn't seem to implement the necessary callbacks. That explains the case where it wasn't applicable to the registerListener method.
You should implement these PageFragmentCallbacks, ReviewFragment.Callbacks, ModelCallbacks callbacks. Particularly, registerListener(ModelCallbacks) requires that whatever parameter you supply (your fragment in this case) implements the ModelCallbacks interface

Android ViewPager in ViewPager won't update correctly

I have a ViewPager with 6 screens (mViewPagerMain).
At screen 4 I have another ViewPager inside the other (mViewPagerSub). I disable scrolling for the mViewPagerMain at my CustumViewPager, when mViewPagerSub is used - works great.
Now the problem:
When I scroll through the main pages everything works fine, all things load.
When I scroll back from screen 4 (containing the sub) to screen 2 and back to 4, the first 2 screens inside the sub aren't loaded. If I scroll forth and back in the sub, everything loads correctly.
onCreate:
protected void onCreate(Bundle savedInstanceState) {
mSectionsPagerAdapterMain = new SectionsPagerAdapterMain(getSupportFragmentManager());
mSectionsPagerAdapterSub = new SectionsPagerAdapterSub(getSupportFragmentManager());
mViewPagerMain = (CustomViewPager) findViewById(R.id.pagerMain);
mViewPagerMain.setAdapter(mSectionsPagerAdapter);
}
SectionPagerAdapters: (just like in the google example, same for the sub)
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = new SettingsFragment(0);
break;
case 1:
fragment = new SettingsFragment(1);
break;
case 2:
fragment = new SettingsFragment(2);
break;
// and so on...
default:
fragment = new SettingsFragment(0);
}
return fragment;
}
#Override
public int getCount() {
return 6;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.title_section1).toUpperCase();
case 1:
return getString(R.string.title_section2).toUpperCase();
// and so on ...
}
return null;
}
}
Settingsfragment: (just like in the google example)
public static class SettingsFragment extends Fragment {
int position = 0;
public SettingsFragment(int position) {
this.position = position;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView;
if (position == 0) {
rootView = inflater.inflate(R.layout.main0, container, false);
main0(rootView);
return rootView;
} else if (position == 1) {
rootView = inflater.inflate(R.layout.main1, container, false);
main1(rootView);
return rootView;
} else if (position == 2) {
rootView = inflater.inflate(R.layout.main2, container, false);
main2(rootView);
return rootView;
}
//...and so on...
}
}
relevant page: main4():
public static void main4(View rootView) {
subView = rootView;
new setAdapterTask().execute();
}
and the async task to load the pager:
private static class setAdapterTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... params) {
return null;
}
#Override
protected void onPostExecute(Void result) {
mViewPagerSub = (CustomViewPager)subView.findViewById(R.id.pagerSub);
mViewPagerSub.setAdapter(mSectionsPagerAdapterSub);
mViewPagerSub.refresh();
}
}
CustomViewPager.refresh():
public void refresh() {
getAdapter().notifyDataSetChanged();
}
I tried to use a PageListener like this, to simulate the scrolling, but it had no effect:
private static class PageListener extends SimpleOnPageChangeListener {
public void onPageSelected(int position) {
currentPage = position;
if (mViewPagerSub != null && currentPage == 3) {
mViewPagerSub.setCurrentItem(5);
} else if (mViewPagerSub != null && currentPage == 4) {
mViewPagerSub.setCurrentItem(0);
}
if (mViewPagerSub != null)
mViewPagerSub.refresh();
}
}
Answering my own question:
This did the trick:
private static class PageListener extends SimpleOnPageChangeListener {
public void onPageSelected(int position) {
currentPage = position;
if(currentPage==4 && mViewPagerSub!=null){
new setAdapterTask().execute();
}
}
}

Categories

Resources