Fragment Replacement Techniques - java

I have three fragments which are bound to Tabs as you swipe from TabA to TabB to TabC and it respectively loads FragA, FragB and FragC using ViewPager(Hope you understand this part). This swipe Tabs works just fine but I have a few fixed buttons attached to the bottom of the layout and when each button is clicked i want to load a new fragment to replace any of the Fragment A, B or C. The replacement works fine but when i return to the replaced/previous Fragment all the UI components on that layout completely disappears and it does not indicate if it has been paused, stopped or destroyed. Heres the Code:
Replacing previous Fragment Code:
if(actionBar.isShowing())
{
actionBar.hide();
}
FragmentManager fms = getSupportFragmentManager();
FragmentTransaction fts = fms.beginTransaction();
search s = new search();
if(getCurrentDisplayFragment() == Type1)
{
setcurrentItem(viewPager.getCurrentItem());
Fragment frgs = mAdapter.getItem(viewPager.getCurrentItem());
viewPager.removeViewAt(viewPager.getCurrentItem());
frgs.setUserVisibleHint(false);
Log.i("if say", "Not Responding");
}
fts.replace(R.id.hoster, s);
fts.addToBackStack("search");
fts.commit();
setCurrentDisplayFragment(s,Type2);
The setCurrentDisplayFragment(s,Type2); i built that code to help indicate which fragment is being replaced and what measures to take. S is a Fragment and Type2 is a string
Thus when the back button is pressed i want to return back to any of the Fragments A, B or C that was previously replaced.
Here is the code:
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
//super.onBackPressed();
Log.i("event in back","I have been presssed ");
if(getCurrentDisplayFragment() == Type2)
{
actionBar.show();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
fm.popBackStack();
ft.remove(getCurrentDisplayFragment(Type2));
ft.commit();
viewPager.setCurrentItem(getcurrentItem());
actionBar.setSelectedNavigationItem(getcurrentItem());
Fragment frags = mAdapter.getItem(viewPager.getCurrentItem());
frags.setUserVisibleHint(true);
setCurrentDisplayFragment(frags, Type1);
}
else
{
}
}
Though some variable names and method declaration are not shown but you should get the picture of what am doing because all declarations have been done. its just The Replacement and retaining of their UI states thats giving me a problem.

Why are you complicating this so much? If you want to replace any of the fragments in the view pager, then just replace the view pager. It would be a lot easier if you would have a fragment holding the view pager with the tabs and when the bottom buttons are pressed, you just replace the fragment holding the view pager with the new fragment. In this way you won't need to do anything when the back button will be pressed.

Related

how to use fragments backstack with shared element transition

shared element transition replace the fragment, so I can't add it to the backstack and call popbackstack when the back arrow button is pressed.
I have a main activity with inside a mainfragment, the main fragment handle a table layout so each tab is a fragment with inside a recycler view, when a recycler view element is clicked the shared element transition start to a new fragment that shows element details.
This is the adapter, where the element is clicked:
holder.image.setTransitionName("transition" + holder.getAdapterPosition());
if (fragment instanceof tab1_anime) {
((tab1_anime) fragment).openShowElementFragment(holder.getAdapterPosition(), v.findViewById(R.id.main_image));
}
This is the openShowElementFragment inside my tab fragment:
public void openShowElementFragment(int position, View view) {
AddElement element = anime_list.get(position);
ShowElementFragment showElementFragment = new ShowElementFragment();
Bundle bundle = new Bundle();
bundle.putString("transitionName", "transition" + position);
bundle.putSerializable("element", element);
bundle.putInt("position", position);
bundle.putInt("from", 0);
showElementFragment.setArguments(bundle);
((MainActivity) context).showFragmentWithTransition(this, showElementFragment, "showElementFragment", view, "transition" + position);
}
this is the openshowelementfragment function called in the previous code block:
public void showFragmentWithTransition(Fragment current, Fragment newFragment, String tag, View sharedView, String sharedElementName) {
FragmentManager fragmentManager = getSupportFragmentManager();
// check if the fragment is in back stack
boolean fragmentPopped = fragmentManager.popBackStackImmediate(tag, 0);
if (fragmentPopped) {
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
current.setSharedElementReturnTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
current.setExitTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setSharedElementEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
}
fragmentManager.beginTransaction()
.replace(R.id.fragmentHolder, newFragment)
.addToBackStack(null)
.addSharedElement(sharedView, sharedElementName)
.commit();
}
}
and this is the backarrow button:
back_arrow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getSupportFragmentManager().popBackStack();
}
});
If I try to add the new fragment instead of replace the old one, then there is not animations at all.
If I try to replace the old fragment with the new one and using anyway the addtobackstack(null) then the shared element transition works from start to end BUT the fragment at the end is without data, empty:
I tried also:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragmentHolder, new MainFragment())
.addToBackStack(null)
.commit();
but in this way the shared element transition doesn't work on exit:
I think your problem is related to exit transition as you get solution of empty list, now for exit transition effect check below code.
public void showFragmentWithTransition(Fragment current, Fragment newFragment, String tag, View sharedView, String sharedElementName) {
FragmentManager fragmentManager = getSupportFragmentManager();
// check if the fragment is in back stack
boolean fragmentPopped = fragmentManager.popBackStackImmediate(tag, 0);
if (fragmentPopped) {
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
current.setSharedElementReturnTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
current.setExitTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setSharedElementEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setExitTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setSharedElementReturnTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
}
getSupportFragmentManager().beginTransaction()
.addSharedElement(sharedElement, transitionName)
.replace(R.id.container, newFragment)
.addToBackStack(null)
.commit();
}
}
Here I add two new lines for exit transitions.
newFragment.setExitTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setSharedElementReturnTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
If its not work then follow below link, I think it will help you.
https://medium.com/#bherbst/fragment-transitions-with-shared-elements-7c7d71d31cbb
Your so called sharedElementName is actually a transitionName.
a) The reference example doesn't directly manipulate the backstack:
Android Fragment Transitions: RecyclerView to ViewPager & Blog.
b) Manual transactions might require another order of method calls:
getSupportFragmentManager().beginTransaction()
.addSharedElement(sharedElement, transitionName)
.replace(R.id.container, newFragment)
.addToBackStack(null)
.commit();
Also see this example, how alternate transitions could be be set, compared to:
TransitionInflater.from(this).inflateTransition(R.transition.default_transition)
Whatever R.transition.default_transition may be.
It may look strange when using the same transition for both ways, instead of enter/exit transitions, alike NavAction would feature. FragmentNavigator.Extras could be used to apply enter/exit transitions, when using navigation component; this also can be combined with ActionBar. Option a) is probably less complicated. b) something alike this Kotlin example might make more sense.
It's pointless to build clunky navigation FX when enter/exit transitions would be supported out-of-the-box. Maybe consider loading with Glide, when loading images.
Assume the following build.gradle; there's no need to reinvent the wheel.
dependencies {
androidTestImplementation "androidx.navigation:navigation-testing:2.5.3"
implementation 'androidx.navigation:navigation-runtime:2.5.3'
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
implementation 'com.github.bumptech.glide:glide:4.14.2'
}
Then Navigation with FragmentNavigator.Extras might rather be the current method:
Bundle navArgs = new Bundle();
navArgs.putInt("position", position);
FragmentNavigator.Extras extras = new FragmentNavigator.Extras.Builder()
.addSharedElement(sharedElement, transitionName)
.build();
Navigation.findNavController(view).navigate(
R.id.details, // ID of Nav destination
navArgs, // Bundle of args
null, // NavOptions
extras);
One can also define transitionName in XML <action/> nodes.

How to keep entered data in fragments when navigation forward and backward through fragments

I have three fragments: FragmentA, FragmentB, FragmentC. All fragments have one TextInputLayout with editText inside and button to go to next fragment. When FragmentA gets called I enter text in editText and go to next fragment (FragmentB) and do the same thing and do the same thing for last fragment (FragmentC). If I go back to FragmentB and type something different in editText and go to FragmentC text that was three is gone. I know that I have to pass bundle to fragment that will be displayed when swipe back on phone usingonDetach() method. I know if I want to pass bundle to fragment that I will call with pressing next button I have to setArguments(bundle).
public void switchFragments(Fragment fragment, Fragment currentFragment, boolean addToBackStack, String tag) {
FragmentTransaction transaction = currentFragment.getActivity().getSupportFragmentManager().beginTransaction();
transaction.add(R.id.fragment_container_sign_up, fragment, tag);
transaction.hide(currentFragment);
if(addToBackStack) {
transaction.addToBackStack(tag);
}
transaction.commit();
}
I am using transaction.add() and transaction.hide() because I am losing text entered in editTexts when I am coming back from previous fragment if I use transaction.replace()
To store and keep values between Fragments, look at SharedPreferences. Use this to pass value between fragments.
holder.YOURBUTTONCLICKID.setOnClickListener {
val sharedPrefs = context
.getSharedPreferences("PREFS", Context.MODE_PRIVATE)
sharedPrefs.edit().putString("postId", EDITTEXTID).apply()
(context as FragmentActivity)
.supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, FRAGMENTA/B/C )
.addToBackStack(null).commit()
}
Replace uppercase with relevant Ids.
And get the value on the other Fragment like this
val preference = context?.getSharedPreferences("PREFS", Context.MODE_PRIVATE)
if (preference != null) {
getTextValue = preference.getString("postId", "none").toString()
}
Remember that your putString key and getString has to be the same.

Can not add Fragment

I've been stuck on this for a while now and will appreciate any guidance on this. Also not sure if my title is accurate. Inside my adapter I create a new fragment:
Adapter code:
cardAdoptDetailsFrag nextFrag = new cardAdoptDetailsFrag();
android.support.v4.app.Fragment callingFrag = ((FragmentActivity)context).getSupportFragmentManager().findFragmentByTag("TagFeedFragment");
FragmentTransaction ft = ((FragmentActivity)context).getSupportFragmentManager().beginTransaction();
ft.hide(callingFrag);
ft.add(R.id.fram, nextFrag,"cardAdoptDetailsFrag");
ft.addToBackStack("TagFeedFragment");
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
Then inside my Main Activity where I manage all the fragments, I'm trying to check if the "cardAdoptDetailsFrag" isAdded. For some reason I cannot do it as per the below.
Get Fragment by tag. The below is where it fails with null object reference.
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
Log.d(TAG, "Check if added: "+frag.isAdded());
Now I know I can just add it inside a method and do a null check and return a boolean, but I know I'm doing something wrong here. Because with my other fragments the isAdded and remove works, but they get initiated inside the Main Activity where "cardAdoptDetailsFrag" gets initiated inside the adapter.
Example of Main Activity:
public class MainActivity extends AppCompatActivity implements adoptFeedFragment.OnFragmentInteractionListener,
lostAndFoundFragment.OnFragmentInteractionListener,
servicesFragment.OnFragmentInteractionListener,
userMenuFragment.OnFragmentInteractionListener,
showUserAdoptPostsFrag.OnFragmentInteractionListener{
adoptFeedFragment adoptFeedFragment;
lostAndFoundFragment lostAndFoundFragment;
servicesFragment servicesFragment;
userMenuFragment userMenuFragment;
....
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adoptFeedFragment = new adoptFeedFragment();
lostAndFoundFragment = new lostAndFoundFragment();
servicesFragment = new servicesFragment();
userMenuFragment = new userMenuFragment();
....
//Here I can do for example:
adoptFeedFragment.isAdded(); //Will simply return a boolean.
//Or I can do a remove:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.remove(adoptFeedFragment);
//But I cannot do the below:
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
Log.d(TAG, "frag.isAdded(): "+frag.isAdded());
*****Edited Post. Ok lets say the frag is added. Why can I not remove it using the below.
public boolean isAdoptDetailsFragAdded() {
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
if(frag == null){
return false;
} else {
return true;
}
}
public Fragment getAdoptDetailsFrag() {
return getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
}
//I'm unable to remove the fragment using the below:
Log.d(TAG, "showFeedFragment: "+isAdoptDetailsFragAdded()); <--returns true
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if(isAdoptDetailsFragAdded()) {
ft.remove(getAdoptDetailsFrag());
}
ft.commit();
//Now I check again wether it is still added. And still returns true even though I just removed it.
Log.d(TAG, "showFeedFragment: "+isAdoptDetailsFragAdded()); <-- Still returns true.
}
This is my fragment adding or replacing method which i am using in 13 projects.
You can use it and get rid of managing fragments.
/**
* replace or add fragment to the container
*
* #param fragment pass android.support.v4.app.Fragment
* #param bundle pass your extra bundle if any
* #param popBackStack if true it will clear back stack
* #param findInStack if true it will load old fragment if found
*/
public void replaceFragment(Fragment fragment, #Nullable Bundle bundle, boolean popBackStack, boolean findInStack) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
String tag = fragment.getClass().getName();
Fragment parentFragment;
if (findInStack && fm.findFragmentByTag(tag) != null) {
parentFragment = fm.findFragmentByTag(tag);
} else {
parentFragment = fragment;
}
// if user passes the #bundle in not null, then can be added to the fragment
if (bundle != null)
parentFragment.setArguments(bundle);
else parentFragment.setArguments(null);
// this is for the very first fragment not to be added into the back stack.
if (popBackStack) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
ft.addToBackStack(parentFragment.getClass().getName() + "");
}
ft.replace(R.id.main_frame_container, parentFragment, tag);
ft.commit();
fm.executePendingTransactions();
}
Here R.id.main_frame_container is FrameLayout in activity layout on which fragment is placed.
Here findFragmentByTag() is returning null when you do getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag").
findFragmentByTag() will return null in two cases.
If fragment cardAdoptDetailsFrag is not yet attached to view (you did not call add or replace for this fragment)
You added that fragment, but you are using different TAG, while searching that fragment.
Make sure if non of the case in your problem.
Now the solution is, you can modify your logic, like
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
if (frag != null && frag.isAdded()) {
// Do you task. Fragment is added
} else {
// Fragment is not added yet
}
I did not went through the long post but I read only this part.
Now I check again wether it is still added. And still returns true even though I just removed it.
And the reason behind that is FragmentTransaction.commit is not synchronous.
Read the docs for commit method:
*Schedules a commit of this transaction. The commit does
* not happen immediately; it will be scheduled as work on the main thread
* to be done the next time that thread is ready.*
If you need that to be synchronous, use commitNow instead.

Create a fragment inside an activity

In my my main activity layout(first layout) I have dynamically set up text views. Now, when these text views are clicked, I want to display another layout(second layout) again having text views(dynamically set). When these text views are clicked I want the text of this text view of this second layout to be set as the text of the text view of the first layout which was clicked.
For eg.
first layout -> [click] [click] ->be the two text views in the layout. Suppose I click the first, then I want to display
[english] [maths] [science]. And suppose I click on english then I want to set the screen to display the first layout again but replacing the text of the view clicked
first layout -> [english] [click].
Now The Problem
I have set up the first layout as main_layout. Now I need to know how can a display the second layout in my fragment_layout and then return back to the main_activity. I want to save the instance of the main_layout as it goes from main to fragment and back to main.
Function called on clicking first layout's text view
View.OnClickListener timetable_click_listen = new View.OnClickListener() {
#Override
public void onClick(View timetable_viewtext) {
setContentView(R.layout.fragment_time_table);
int no_subjects;
/*opens the database and finds out the number of subjects user has entered
Opening database and getting the number of subjects*/
SQLiteDatabase db = openOrCreateDatabase("DATABASE",MODE_PRIVATE,null);
if(db==null)
{
Log.d("Error in TimeTable","There was a error in Opening Database");
}
else{
Log.d("Inside TimeTable Onclick","Opening Database");
}
Cursor c= db.rawQuery("SELECT Num_subjects FROM DETAILS;",null);
c.moveToFirst();
no_subjects = c.getInt(c.getColumnIndex("Num_subjects"));
c.close();
/*Getting the names of the subjects so as to put that name
* as the button's text
*/
String[] name = new String[no_subjects];
c = db.rawQuery("SELECT * FROM SUBJECTS;",null);
c.moveToFirst();
for(int i=0;i<no_subjects;i++)
{
name[i] = c.getString(c.getColumnIndex("Name"));
c.moveToNext();
}
c.close();
db.close();
LinearLayout linearlayout = (LinearLayout) findViewById(R.id.timetable_fragment);
TextView ed;
List<TextView> allEds = new ArrayList<TextView>();
for (int i = 0; i < no_subjects; i++) {
ed = new TextView(TimeTable.this);
allEds.add(ed);
ed.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
ed.setId(1000+i);
ed.setText(name[i]);
tempId = timetable_viewtext.getId();
ed.setClickable(true);
ed.setOnClickListener(subname_click_listen);
((LinearLayout)linearlayout).addView(ed);
}
}
};
Now I am not understanding what to do in the function of the second layout's view.onclick()
Thanks in advance
You should probably use a FragmentManager and a FragmentTransaction. To do this you will need to extend the Fragment class. For example, you might want to make a Fragment to display each layout. These fragments will have their own layout file so you can customize how they look. You will need to provide a container to hold the fragments. I used a FrameLayout called fragment_container. To initialize the fragment, use the FragmentTransaction.add() method and use the FragmentTransaction.replace() method to switch between them.
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
MyFragment fragment = new MyFragment();
ft.add(R.id.fragment_container, fragment);
ft.commit();
Check out the API for more info: FragmentTransaction

Checking current opened fragment position while using DrawerLayout

I am using android DrawerLayout from Support Library to show slide menu.I am using a single activity and 5-6 fragments to show them upon selection in DrawerLayout menu.But I have a small problem which is "How do I check which fragment is currently visible so if user selected the menu item which corresponds to already opened fragment. Currently it creating the Fragment again and displaying it which is not good.The function that triggers when clicked on menu item is:
private void selectItem(int position) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
// Locate Position
switch (position) {
case 0:
ft.replace(R.id.content_frame, fragment1);
break;
case 1:
ft.replace(R.id.content_frame, fragment2);
break;
case 2:
ft.replace(R.id.content_frame, fragment3);
break;
}
ft.commit();
mDrawerList.setItemChecked(position, true);
// Close drawer
mDrawerLayout.closeDrawer(mDrawerList);
}
How do I check if the requested fragment is already opened so not to create it again?Is their any method to check this through FragmentManager?
I would add to #Plato's answer.
Check get currently displayed fragment question if you haven't already.
The answer says that when you add the fragment in your transaction, you can use a tag to represent a particular fragment. Something like:
ft.replace(android.R.id.content, fragment, "MY_FRAGMENT");
Later if you want to check if that fragment is visible, you can do something like:
RequestedFragment fragment = (RequestedFragment)getFragmentManager().findFragmentByTag("MY_FRAGMENT"); //"My_FRAGMENT" is its tag
if (fragment.isVisible()) {
// add your code here
}
Hope this helps.
the simplest approach would be to store last used position and compare next time. Just make your selectItem method like:
int lastPosition = -1;
private void selectItem(int position) {
if( position != lastPosition ) {
[ old method body here ]
lastPostion = position;
}
}
Assign different tags to each one of your fragments and call findFragmentByTag() of the FragmentManager to check if each one is already present.
You can also use the findFragmentById() of the FragmentManager for the same purpose and use the ids of your fragments.
If you are absolutely sure that each of fragment1, fragment2, fragment3 cannot be visible at the same time and if you reuse the same fragments (i.e. do not create a new fragment each time you change position) then the method isVisible() from the class Fragment should tell you if that fragment is currently visible.
Your switch should look like:
switch (position) {
case 0:
if(!fragment1.isVisible()) {
ft.replace(R.id.content_frame, fragment1);
}
break;
case 1:
if(!fragment2.isVisible()) {
ft.replace(R.id.content_frame, fragment2);
}
break;
case 2:
if(!fragment3.isVisible()) {
ft.replace(R.id.content_frame, fragment3);
}
break;
}

Categories

Resources