I have two fragments, call the fragment A and fragment B
both fragments take up the half screen when shown (this is ok)
the activity is loaded with Fragment A in onCreate being attached (this is ok)
now at some press of a button in fragment A i want to replace fragment A on screen with fragment B (this is done)
however, when i do a backpress, i want to replace fragment B with the original fragment A
here is my code in onCreate to show fragment A
Log.d(TAG, "replacing fragment");
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(resId, fragment);
ft.commit();
//no call to backstack intentionally, so that fragment A can't be detached with back press
this is my code on button click to show fragment B
Log.d(TAG, "replacing fragment");
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(enterAnim, exitAnim);
ft.replace(resId, fragment);
ft.addToBackStack(null);
ft.commit();
now when in my onBackPressed method i wrote
getSupportFragmentManager().popBackStack();
this is what happens : fragment B gets removed, fragment A does not display behind it. meaning i get a blank white screen with nothing on it, in the place where fragment A was supposed to be.
why does this happen ?
Related
I am making a javafx media player using vlcj.
There is my DMediaPlayer.java class that creates the vlcj media player and returns an ImageView which displays the video.
and here is my PlayerView.java
this class displays the video.
public class PlayerView extends BorderPane {
private MenuBar menuBar;
private VideoControlPanel controlPanel;
private Drawer drawer;
private IconsProvider icons;
private MediaPlayerInterface mediaPlayer;
private ImageView mediaView;
private StackPane playerHolder;
private ImageView view;
public PlayerView() {
view = new ImageView();
icons = new IconsProvider();
menuBar = new MenuBar(icons);
setTop(menuBar);
playerHolder = new StackPane();
playerHolder.setAlignment(Pos.TOP_LEFT);
setCenter(playerHolder);
mediaPlayer = new DMediaPlayer();
mediaView = mediaPlayer.getMediaView();
playerHolder.getChildren().add(mediaView);
try {
mediaPlayer.load("/home/doruk/Downloads/Video/movie.mkv");
} catch (FileNotFoundException e) {
System.out.println(e.toString());
}
mediaPlayer.play();
playerHolder.getChildren().add(drawer);
}
}
The problem is the video plays as expected, but after few seconds, the ImageView only displays the single image, its like the video is paused. but the audio keeps on playing.
I thought the issue was due to ImageView being garbage collected as if I initially when the mediaplayer was called as follow:
DMediaPlayer player = new DMediaPlayer()
player.play()
inside the PlayerView class, there would be following errors:
JNA: callback object has been garbage collected
JNA: callback object has been garbage collected
JNA: callback object has been garbage collected
malloc(): unaligned tcache chunk detected
and so I created the the ImageView in the same PlayerView class and passed the reference to the DMediaPlayer so that it can play the video in it, but that does not work either.
I've got three Simple Fragments: FragmentA, FragmentB, FragmentC.
FragmentA -> FragmentB:
FragmentB fragment2 = new FragmentB ();
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.addToBackStack(null);
fragmentTransaction.replace(R.id.mainLayout, fragment2);
fragmentTransaction.commit();
FragmentB -> FragmentC:
FragmentC fC= new FragmentC ();
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.mainLayout, fC);
fragmentTransaction.addToBackStack("FragmentA");
fragmentTransaction.commit();
FragmentC -> FragmentA:
getActivity().getSupportFragmentManager().popBackStack("FragmentA",0);
The Issue is that I always return to FragmentB altough my aim is to return to FragmentA. What am I missing?
FragmentManager fmManager = getFragmentManager();
if (fmManager.getBackStackEntryCount() > 0) { fmManager.popBackStack(fmManager.getBackStackEntryAt(fmManager.getBackStackEntryCount()-2).getId(), fmManager.POP_BACK_STACK_INCLUSIVE); }
what this code does, it checks if there are stacked fragments. If yes, it gets the fragment at the first stake, which is fragmentA, and returns to this fragment. Whats important is that u are aware at which position the fragment is positioned!!
With Fragment:setRetainInstance(true); the fragment is not re-instantiated on a phones orientation change.
And of course i want my fragments to be kept alive while switching from one fragment to another.
But the Android Studio 4 provides a wizard-template with only
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow)
.setDrawerLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
From hours of debugging and searching the net if think it would need to inherent from the class FragmentNavigator so i can overwrite FragmentNavigator:naviagte where a new fragment gets created via final Fragment frag = instantiateFragment(.. and then is added with ft.replace(mContainerId, frag);
So i could find my old fragment and use ftNew.show and ftOld.hide instead.
Of course this is a stupid idea, because this navigate method is full of other internal stuff.
And i have no idea where that FrameNavigator is created.
I can retrieve it in the MainActivity:OnCreate with
NavigatorProvider navProvider = navController.getNavigatorProvider ();
Navigator<NavDestination> navigator = navProvider.getNavigator("fragment");
But at that time i could only replace it with my derived version. And there is no replaceNavigtor method but only a addNavigator method, which is called where ?
And anyways this all will be far to complicated and therefore error prone.
Why is there no simple option to keep my fragments alive :-(
In older Wizard-Templates there was the possibility of
#Override
public void onNavigationDrawerItemSelected(int position) {
Fragment fragment;
switch (position) {
case 1:
fragment = fragment1;
break;
case 2:
fragment = fragment2;
break;
case 3:
fragment = fragment3;
break;
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if(mCurrentFragment == null) {
ft.add(R.id.container, fragment).commit();
mCurrentFragment = fragment;
} else if(fragment.isAdded()) {
ft.hide(mCurrentFragment).show(fragment).commit();
} else {
ft.hide(mCurrentFragment).add(R.id.container, fragment).commit();
}
mCurrentFragment = fragment;
}
but i have no idea how to do this with the Android 4.0 template where my MainActivity is only derived as:
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration mAppBarConfiguration;
Ideas welcome :'(
Hi there & sorry for my late answer! I had a similar problem with navigation drawers and navigation component. I tried around a little and found a working solution, which might be helpful for others too.
The key is the usage of a custom FragmentFactory in the FragmentManager of the MainActivity. See the code for this below:
public class StaticFragmentFactory extends FragmentFactory {
private myNavHostFragment1 tripNavHostFragment;
private myNavHostFragment2 settingsNavHostFragment;
#NonNull
#Override
public Fragment instantiate(#NonNull ClassLoader classLoader, #NonNull String className) {
if (MyNavHostFragment1.class.getName().equals(className)) {
if (this.myNavHostFragment1 == null) {
this.myNavHostFragment1 = new MyNavHostFragment1();
}
return this.myNavHostFragment1 ;
} else if (MyNavHostFragment2.class.getName().equals(className)) {
if (this.myNavHostFragment2 == null) {
this.myNavHostFragment2 = new MyNavHostFragment2();
}
return this.myNavHostFragment2;
}
return super.instantiate(classLoader, className);
}
}
The FragmentFactory survives the navigation between different fragments using the NavigationComponent of AndroidX. To keep the fragments alive, the FragmentFactory stores an instance of the fragments which should survive and returns this instance if this is not null. You can find a similar pattern when using a singleton pattern in classes.
You have to register the FragmentFactory in the corresponding activity by calling
this.getSupportFragmentManager().setFragmentFactory(new StaticFragmentFactory())
Please note also that I'm using nesten fragments here, so one toplevel fragment (called NavHostFragmen here) contains multiple child fragments. All fragments are using the same FragmentFactory of their parent fragments. The custom FragmentFactory above returns the result of the super class method, when the fragment to be instantiated is not known to keep alive.
I have a custom DialogFragment as an inner class in my Activity. The custom DialogFragment contains 2 Buttons.
The first Button opens the Camera and the second one opens the Gallery.
Normally this DialogFragment is shown after an Image is pressed.
Until here everything is fine.
Now I want add a new functionality. When the Activityis opened the first time, I want to open the Camera automatically, that means I want to "press" the first Button of my DialogFragment.
In my Activity onCreate method I just show the DialogFragment and perform a click on the first Button. The problem is that the DialogFragment is not dismissed.
Here is my code:
public static class MyDialogFragment extends DialogFragment {
Button openCameraButton;
Button openGalleryButton;
boolean openCameraAutomatically;
public static MyDialogFragment newInstance(boolean openCameraAutomatically) {
MyDialogFragment f = new MyDialogFragment();
Bundle args = new Bundle();
args.putBoolean("open_camera", openCameraAutomatically);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
openCameraAutomatically = getArguments().getBoolean("open_camera");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.my_dialog, container, false);
getDialog().setTitle("title");
openCameraButton = (Button) rootView.findViewById(R.id.open_camera_button);
openCameraButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// launch Camera Intent...
getDialog().dismiss();
}
});
openGalleryButton = (Button) rootView.findViewById(R.id.open_gallery_button);
openGalleryButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Launch Gallery Picker Intent
getDialog().dismiss();
}
});
if(openCameraAutomatically) {
openCameraButton.performClick();
}
return rootView;
}
}
And here is how I call it:
FragmentManager fm = getSupportFragmentManager();
MyDialogFragment myDialogFragment = MyDialogFragment.newInstance();
myDialogFragment.show(fm, "");
The line getDialog().dismiss(); does not dismiss the DialogFragment, after the Camera callback (onActivityResult) the DialogFragment is still visible. If I press the Button manually (without using the method performClick) everything works fine.
Any ideas?
Thanks.
Try just dismiss() to dismiss the dialog and the fragment instead of getDialog().dismiss() which just dismisses the dialog and but not the fragment.
https://developer.android.com/reference/android/app/DialogFragment.html#dismiss()
void dismiss ()
Dismiss the fragment and its dialog. If the fragment was added to the back stack, all back stack state up to and including this entry will be popped. Otherwise, a new transaction will be committed to remove the fragment.
Update:
Here is another thought. You are trying to dismiss the dialog before the view for the DialogFragment is fully ready. This isn't a problem once the buttons are available for you to push, i.e., the layout is complete.
Try moving the automatic dismissal later in life cycle. I think that will work for you.
It's common for a Fragment to be added to a layout when a UI element, such as a button is tapped. If the user taps the button multiple times very quickly, it can happen that the Fragment is added multiple times, causing various issues.
How can this be prevented?
I created a helper method that ensures that the fragment is only added if it doesn't yet exist:
public static void addFragmentOnlyOnce(FragmentManager fragmentManager, Fragment fragment, String tag) {
// Make sure the current transaction finishes first
fragmentManager.executePendingTransactions();
// If there is no fragment yet with this tag...
if (fragmentManager.findFragmentByTag(tag) == null) {
// Add it
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(fragment, tag);
transaction.commit();
}
}
Simple call as such from an Activity or another Fragment:
addFragmentOnlyOnce(getFragmentManager(), myFragment, "myTag");
This works with both the android.app.* and the android.support.app.* packages.
Here my solution, I have try to click show dialog fragment by tap button multiple time and quickly.
FragmentManager fm = getSupportFragmentManager();
Fragment oldFragment = fm.findFragmentByTag("wait_modal");
if(oldFragment != null && oldFragment.isAdded())
return;
if(oldFragment == null && !please_wait_modal.isAdded() && !please_wait_modal.isVisible()){
fm.executePendingTransactions();
please_wait_modal.show(fm,"wait_modal");
}