ViewPager2 not destroying fragments - android

I have a DialogFragment with a ViewPager2:
public class LightboxFragment extends DialogFragment {
private ViewPager2 viewPager;
private MyViewPagerAdapter myViewPagerAdapter;
public static LightboxFragment newInstance() {
LightboxFragment fragment = new LightboxFragment();
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_lightbox, container, false);
viewPager = v.findViewById(R.id.viewPager);
FrameLayout closeButtonContainer = v.findViewById(R.id.close_button_container);
closeButtonContainer.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
}
});
myViewPagerAdapter = new MyViewPagerAdapter(getActivity());
viewPager.setAdapter(myViewPagerAdapter);
return v;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public class MyViewPagerAdapter extends FragmentStateAdapter {
public MyViewPagerAdapter(FragmentActivity fa) {
super(fa);
}
#NonNull
#Override
public Fragment createFragment(int position) {
LightboxVideoFragment fragment = new LightboxVideoFragment();
return fragment;
}
#Override
public int getItemCount() {
return items.size();
}
}
}
And my LightboxVideoFragment looks something like this:
public class LightboxVideoFragment extends Fragment {
private SimpleExoPlayer exoPlayer;
private PlayerView playerView;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.slideshow_video, container, false);
}
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
playerView = view.findViewById(R.id.player_view);
MediaItem mediaItem = MediaItem.fromUri(media.getUrls().getMp4());
exoPlayer = new SimpleExoPlayer.Builder(getContext()).build();
exoPlayer.setRepeatMode(Player.REPEAT_MODE_ALL);
playerView.setPlayer(exoPlayer);
exoPlayer.setMediaItem(mediaItem);
exoPlayer.prepare();
exoPlayer.play();
}
#Override
public void onResume() {
super.onResume();
if (exoPlayer != null) {
exoPlayer.play();
}
}
#Override
public void onPause() {
super.onPause();
if (exoPlayer != null) {
exoPlayer.pause();
}
}
#Override
public void onDetach() {
super.onDetach();
if (exoPlayer != null) {
exoPlayer.release();
}
}
}
My problem is this: When the DialogFragment is dismissed, the audio from the fragment's video is still playing. Why is this? I've tried adding onDestroy in my fragment and setting breakpoints there, but the breakpoint is never called when the dialog is dismissed, nor is onPause or onDetach.
Does this also mean that the ExoPlayer instance is also still alive when the dialog is dismissed?
How can I stop the video's audio when the dialog is dismissed?

I don't use viewpager2 inside another Fragment but based on the documentation and the source code of viewpager2 I think you are creating the adapter incorrectly when doing it from a Fragment.
In the class LightboxFragment try changing line
myViewPagerAdapter = new MyViewPagerAdapter(getActivity());
to
myViewPagerAdapter = new MyViewPagerAdapter(this);
This is because based on the documentation https://developer.android.com/reference/androidx/viewpager2/adapter/FragmentStateAdapter#FragmentStateAdapter(androidx.fragment.app.Fragment)
Fragment: if the ViewPager2 lives directly in a Fragment subclass.
You are using it as if the Viewpager2 is living directly in the host activity.
This means you are putting LightboxVideoFragment in the same FragmentManager as LightboxFragment and the Lifecycle changes to LightboxFragment don't affect what are basically siblings to it (not children as they should be)
If you look at the source code of viewpager2 for the constructors as well
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java#102
The correct constructor ties the Fragments in the viewpager2 adaptor to a host Fragment via getChildFragmentManager() and the Fragment's lifecycle and not to the Activity's lifecycle.
(I've not experimented or tested this just knowledge from the docs and reading the source code).

Related

how to make two fragments communicate each other inside viewpager in android

Suppose, I have two fragments , FragmentA and FragmentB inside viewpager .When i click the button in fragmentA then it should be able to add the textview in another fragmentB.so, how is it possible ....please help me out.
class Myadpter extends FragmentPagerAdapter {
Fragment fragment =null;
public Myadpter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
if(position==0){
fragment = new Post();
}
if(position==1){
fragment = new ActiveChat();
}
if(position==2){
fragment = new LastUsers();
}
if(position==3){
fragment = new Noname();
}
return fragment;
}
#Override
public int getCount() {
return 4;
}
}
Implement a interface to communicate between two fragments, the class where the view pager is will be a middle man
As already stated by the other user, implementing an interface is the way to go. This link Communicating with Other Fragments will explain in more detail how to achieve what you are attempting to do. Hope this solves your problem.
Do as follows:
Fragment A
public class FragmentA extends Fragment implements View.OnClickListener {
OnButtonPressed mCallback;
Button yourButton;
TextView textViewFragA;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.your_layout, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
yourButton = findViewById(R.id.yourBtn);
textViewFragA = findViewById(R.id.textViewFragA);
yourButton.setOnClickListener(this);
}
#Override
public void onClick(View view) {
switch(view.getId()){
case R.id.yourBtn:
mCallback.onButtonPressed(textViewFragA);
break;
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnButtonPressed) getActivity();
} catch (ClassCastException e) {
throw new ClassCastException(getActivity().toString()
+ " must implement OnButtonPressed");
}
}
#Override
public void onDetach() {
mCallback = null; // Avoid memory leaking
super.onDetach();
}
/**
* Interface called whenever the user has clicked on the Button
* #param textView The TextView to add in FragmentB
*/
public interface OnButtonPressed{
void onButtonPressed(TextView textView);
}
}
FragmentB
public class FragmentB extends Fragment{
TextView textViewFragB;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.your_layout, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
textViewFragB= findViewById(R.id.textViewFragB);
}
public TextView getTextViewFragB(){
return textViewFragB;
}
Activity
public class TabControllerActivity extends AppCompatActivity implements FragmentA.OnButtonPressed{
MyAdapter adapter;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_activity_layout);
// Your Stuff
}
// Everytime the user clicks on the Button in FragmentA, this interface method gets triggered
#Override
public void onButtonPressed(TextView textViewFragA) {
FragmentB fragmentB = (FragmentB) adapter.getItem(1)/* Be careful here and get the right fragment,
otherwise the App will crash*/
// Since you got the TextView and not only the text inside of it,
// you can do whatever you want. Here for example we set the text like the textViewFragA.
//In a few words you turn the textViewFragB to the other one
fragmentB.getTextViewFragB().setText(textViewFragA.getText().toString());
}
}
Hope it will help

FragmentStatePagerAdapter items don't load

In my main activity I have multiple Fragments that you can navigate through a bottom navigation (Replace a FrameLayout container in main activity XML)
One of those Fragments contains a ViewPager that should show multiple Fragments (MyFragment) using a FragmentStatePagerAdapter (MyAdapter).
First time I open that Fragment it works just fine but when I navigate to a different one and then come back the first two Fragments in the Pager are grey and unpopulated. Anyone got an idea what the problem might be?
The Adapter is set up like this:
This is called in onFragmentViewCreated
private void setUpHeaderPager() {
featuredAdapter = new MyAdapter(getActivity().getSupportFragmentManager());
myPager.setAdapter(myAdapter);
pageIndicatorView.setViewPager(myPager);
pageIndicatorView.setAnimationType(AnimationType.WORM);
compositeSubscription.add(apiService.getMyPOJOs()
.subscribeOn(networkScheduler)
.observeOn(uiScheduler)
.subscribe(new Action1<List<MyPOJO>>() {
#Override
public void call(List<MyPOJO> myPOJOs) {
myAdapter.setItems(myPOJOs);
pageIndicatorView.setCount(myPOJOs.size());
myPager.setCurrentItem(0);
pageIndicatorView.setSelection(0);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
}));
}
And this is what the Adapter looks like:
private class MyAdapter extends FragmentStatePagerAdapter {
private List<SomePOJO> items = new ArrayList<>(0);
public void setItems(#Nonnull List<Collection> items) {
this.items = items;
notifyDataSetChanged();
}
FeaturedReleasesAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public Fragment getItem(int position) {
return MyFragment.newInstance(items.get(position));
}
#Override
public int getCount() {
return items.size();
}
}
Just in case the relevant code of MyFragment:
public class FeaturedReleaseFragment extends BaseFragment {
private MyPOJO someObject = null;
// Bind some views
public static MyFragment newInstance(MyPOJO someObject) {
MyFragment fragment = new MyFragment();
fragment.someObject = someObject;
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_my_fragment, container, false);
ButterKnife.bind(this, rootView);
populate();
return rootView;
}
private void populate() {
if (MyPOJO != null) {
// set some text and load images
}
}
onFragmentViewCreated is called just fine, the adapters getItem is not...
It may be how you're navigating between the bottom navigation fragments and subsequently whats happening with their lifecycle.
If you are using .replace on the FragmentTransaction, try using .show and .hide instead.
Something like the following might do it:
public void navigateToFragment( Fragment frag ) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.hide(currentFragment);
if ( !fragment.isAdded() ) {
transaction.add(R.id.container,fragment)
} else if ( !fragment.isHidden() ) {
transaction.show(fragment);
}
transation.commit();
}

How to update an XML TextView from other fragment

I have two fragments and I navigate by swiping between them. I want to update second fragment TextView from first fragment. Is it possibleto do that? Here's what I try to do but this doesn't worked to me.
public void updateOtherFragment(){
LayoutInflater inflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View vi = inflater.inflate(R.layout.fragment_log, null); //my second fragment xml file.
TextView tv = (TextView)vi.findViewById(R.id.textLog);
tv.setText("Updated from first fragment " + info.getName());
}
The default Google way for communication between fragments is to do that through the activity that hosts them.
The FirstFragment defines a callbacks interface that the activity must implement. When the activity gets a callback it can send the information through to the SecondFragment. Just read the example code below to make this more clear:
FirstFragment.java:
This fragment has a button, which when clicked sends a callback to its activity.
public class FirstFragment extends Fragment implements View.OnClickListener {
public FirstFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View result = inflater.inflate(R.layout.fragment_first, container, false);
result.findViewById(R.id.updateButton).setOnClickListener(this);
return result;
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.updateButton:
((Callbacks) getActivity()).onUpdateLogtext("This is an update from FirstFragment");
break;
default:
throw new UnsupportedOperationException();
}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks))
throw new UnsupportedOperationException("Hosting activity must implement Callbacks interface");
}
public interface Callbacks {
void onUpdateLogtext(String text);
}
}
MainActivity.java:
This activity implements the FirstFragment.Callbacks interface in order to receive callbacks from FirstFragment. When it receives an onUpdateLogtext it just passes the data on to SecondFragment.
public class MainActivity extends AppCompatActivity implements FirstFragment.Callbacks {
private SecondFragment secondFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
secondFragment = (SecondFragment) getFragmentManager().findFragmentById(R.id.secondFragment);
}
#Override
public void onUpdateLogtext(String text) {
secondFragment.updateLogtext(text);
}
}
SecondFragment.java:
This just provides a public method that sets the textview with new data. And this method is used by MainActivity when it gets a onUpdateLogtext callback from FirstFragment.
public class SecondFragment extends Fragment {
private TextView tv;
public SecondFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View result = inflater.inflate(R.layout.fragment_second, container, false);
tv = (TextView) result.findViewById(R.id.textlog);
return result;
}
public void updateLogtext(String text) {
tv.setText(text);
}
}

Refreshing a fragment from a DialogFragment

I've been going around in circles trying to do something that seems pretty basic. I have a DialogFragment that accepts a users input, then, on submission, refreshes a ListView in a Fragment that is part of a ViewPager.
I have everything working except the Fragment with the ListView does not refresh itself. It's a little confusing though, because it does refresh the data, but I have to swipe a couple views, then back again to see the updated data.
After doing some research, I'm supposed to use getItemPosition and notifyDataSetChanged on the ViewPager and it should work. The problem is that calling notifyDataSetChanged results in a Recursive entry to executePendingTransactions exception being thrown:
Main Activity
public class Main extends SherlockFragmentActivity implements MyListFragment.OnRefreshAdapterListener, DialogConfirmation.OnRefreshKeywordsListener //Updated Code
{
private static List<Fragment> fragments;
#Override
public void onCreate(final Bundle icicle)
{
setContentView(R.layout.main);
}
#Override
public void onResume()
{
mViewPager = (ViewPager)findViewById(R.id.viewpager);
fragments = new ArrayList<Fragment>();
fragments.add(new MyListFragment()); //fragment with the ListView
fragments.add(MyDetailFragment.newInstance(0));
fragments.add(MyDetailFragment.newInstance(1));
fragments.add(MyDetailFragment.newInstance(2));
mMyFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mMyFragmentPagerAdapter);
}
private static class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
return fragments.get(index);
}
#Override
public int getCount() {
return 4;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
#Override
public void onRefreshAdapterListener() {
this.mMyFragmentPagerAdapter.notifyDataSetChanged();
}
//Updated Code
#Override
public void onRefreshTextListener() {
MyListFragment tf = (MyListFragment)getSupportFragmentManager().findFragmentById(R.id.fragmentText);
if (tf == null)
tf = (MyListFragment)this.fragments.get(0);
tf.RefreshText();
}
}
ListFragment
public class MyListFragment extends SherlockListFragment
{
OnRefreshAdapterListener mRefreshAdapter;
#Override
public void onActivityCreated(Bundle savedState) {
adapter = new CustomAdapter();
/*code to add items to adapter */
this.setListAdapter(adapter);
adapter.notifyDataSetChanged();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (getArguments() != null && getArguments().getString("text").length() > 0)
{
SaveText(getArguments().getString("text"));
this.mRefreshAdapter.onRefreshAdapterListener(); //this line causes a "java.lang.IllegalStateException: Recursive entry to executePendingTransactions" exception
}
return inflater.inflate(R.layout.listing, container, false);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mRefreshAdapter = (OnRefreshAdapterListener)activity;
}
public interface OnRefreshAdapterListener {
public void onRefreshAdapterListener();
}
#Override
public void onDialogTextAdd(final String text) {
}
}
DialogFragment
public class DialogTextAdd extends DialogFragment implements OnEditorActionListener {
private EditText mText;
OnRefreshTextListener mTextKeywords; //Updated Code
public interface DialogTextAddListener {
void onDialogTextAdd(final String inputText);
}
public DialogTextAdd() {
// Empty constructor required for DialogFragment
}
//Updated Code
#Override
public void onAttach(Activity act) {
super.onAttach(act);
mTextKeywords = (OnRefreshTextListener)act;
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.dialog_edit, container);
mText = (EditText)view.findViewById(R.id.text_add);
getDialog().setTitle("Add Text");
// Show soft keyboard automatically
mText.requestFocus();
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
mText.setOnEditorActionListener(this);
return view;
}
#Override
public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) {
if (EditorInfo.IME_ACTION_DONE == actionId) {
MyListFragment mf = new MyListFragment();
Bundle args = new Bundle();
args.putString("text", mText.getText().toString());
mf.setArguments(args);
//this seems to be intefering with the notifyDataSetChanged in the listing fragment
getActivity().getSupportFragmentManager().beginTransaction().add(mf, "my_fragment").commit();
mTextKeywords.onRefreshTextListener(); //Updated Code
this.dismiss();
return true;
}
return false;
}
}
I have everything working except the Fragment with the ListView does
not refresh itself.
There is no point on creating and adding to the FragmentActivity a new instance of MyListFragment. From your code it appears that you store the fragments that you use in a list so you have references to them(also, just out of curiosity, did you setup the fragments in portrait, did a rotation of the phone and retried to use the DialogFragment?). Having references to those fragment means you could always get them from the list and use them to call a refresh/update method.

Android ViewPager, switching from fragment to another fragment

I have a viewpager with 2 pages, on each fragment i put a button to switching fragment,
but if i change orientation switching doesn't work.
For switching fragment I using my OnChangePageButtonClick interface
Why is this happening?
ViewPager Activity:
public class ViewPagerMusic extends FragmentActivity implements OnChangePageButtonClick {
private ViewPager vp;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view_pager_music);
ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter(super.getSupportFragmentManager(), fragments);
vp = (ViewPager)findViewById(R.id.viewpager);
vp.setAdapter(viewPagerAdapter);
}
private class ViewPagerAdapter extends FragmentStatePagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch(position) {
case 0:
MainPage mainPage = new MainPage();
mainPage.setOnChengeButtonListener(ViewPagerMusic.this);
return mainPage;
case 1:
PlaylistPage playlistPage = new PlaylistPage();
playlistPage.setOnChengeButtonListener(ViewPagerMusic.this);
return playlistPage;
}
return null;
}
#Override
public int getCount() {
return 2;
}
}
#Override
public void selectPage(int page) {
vp.setCurrentItem(page);
}
}
my Frgamnets:
public class MainPage extends Fragment {
public MainPage() {
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.music_main_page, container, false);
ImageButton btnPlaylist = (ImageButton)v.findViewById(R.id.btnGoPlaylist2);
btnPlaylist.setOnClickListener(onButtonListener);
return v;
}
private OnClickListener onButtonListener = new OnClickListener() {
#Override
public void onClick(View view) {
onChangePageButtonClick.selectPage(1);
}
};
public void setOnBackButtonListener(OnChangePageButtonClick onChangePageButtonClick) {
this.onChangePageButtonClick = onChangePageButtonClick;
}
private OnChangePageButtonClick onChangePageButtonClick;
}
Playlist fragment is similar to MainPage fragment.
Your fragments are storing a reference to the ViewPagerMusic activity. However, after rotation, the activity is destroyed and recreated, so the fragments now contain a reference to the old activity.
Instead of storing a reference to the activity, you can call the activity's method from the fragment like this:
((ViewPagerMusic) getActivity()).selectPage(1);
just create this method in your view pager
#Override
public void selectPage(int page) {
vp.setCurrentItem(page);
}
and then call this in your onclick method
((ViewPagerMusic) getActivity()).selectPage(1);

Categories

Resources