I have a NavigationViewer activity which has 3 fragments. I want that every time the user selects an item from NavigationViewersliding menu items the app will transact a new fragment object of the selected kind.
for Example I have a NavigationViewer menu item Called "MyFragment"
So I am trying this Code on that item:
MyFragment myFragment = new MyFragment();
fragmentTransaction.replace(R.id.RR, myFragment , "nav_MyFragment ").commit();
But this causes a problem that if user selected "MyFragment" from menu while it is active [seen to user] it will create a new object.
and I want to create that new object only if transacting from some fragment to another.
Any Suggestions?
Edit: retrieving the fragment by tag and then checking if isVisble() or if isAdded() gives null exception
You can keep a Fragment variable and assign your active fragment there, and then check the getClassName() and if it's the clicked one is the same as the one you are currently displaying, you don't load a new one.
I'm going to use the code from this app I have in github.
Then in your Activity declare a Fragment variable like this:
Fragment active;
In your onOptionsItemSelected() method, you have to do the checking for the item pressed.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.nav_item1) {
if (active.getClass().getSimpleName().equals("FragmentA")) {
// then this fragment is already being shown, therefore do nothing
} else {
FragmentA fragtoadd = new FragmentA();
getFragmentManager().beginTransaction()
.remove(fragtoadd)
.commit();
active = fragtoadd; // the magic is here, everytime you add a new fragment, keep the reference to it to know what fragment is being shown
}
return true;
} else if (id == R.id.nav_item2) {
// do the same as above but for FragmentB, and so on
return true;
}
// ... check all your items
return super.onOptionsItemSelected(item);
}
If you have your code in Github so that I can have a look it would be better.
Hope it helps!
U have to add fragment instance into backstack
getSupportedFragmentManager.addToBackStack(fragment.gettag)
I assume you can detect the actual item clicks, so what I do is this:
private static final String TAG_FRAGMENT_SOME_NAME = "something";
mFragmentManager = getSupportFragmentManager(); // or getFragmentManager() if not using support library
mFragmentTransaction = mFragmentManager.beginTransaction();
Fragment myFragment = mFragmentManager.findFragmentByTag(TAG_FRAGMENT_SOME_NAME);
if (myFragment == null) {
myFragment = MyFragment.newInstance();
}
mFragmentTransaction.replace(R.id.RR, myFragment, TAG_FRAGMENT_SOME_NAME).commit();
Related
I am using NavigationDrawer in my application and each menu item in drawer is a fragment.Whenever user chooses a menu item I replace the current fragment in the main container with the requested one but it recreates the fragment every-time, so i updated my code to reuse the existing fragments instead of creating them again and again as content of fragments remain same. My updated code to show fragment is :
public void showTabFragment() {
TabFragment Tf = (TabFragment) mFragmentManager.findFragmentByTag(Constants.TAB_FRAGMENT);
mFragmentTransaction = mFragmentManager.beginTransaction();
if (Tf != null) {
mFragmentTransaction.replace(R.id.containerView, Tf, Constants.TAB_FRAGMENT);
} else {
mFragmentTransaction.replace(R.id.containerView, new TabFragment(), Constants.TAB_FRAGMENT);
}
mFragmentTransaction.commit();
}
In above code I am trying to get fragments by Tag but it always returns null and executes the else case(new fragment).Could someone please guide me what am I doing wrong in my code?
I guess the code you've shown is for one of your menu fragment? If that's the case, what is probably happening is every time you open a menu item, the container is replaced with the new fragment(say, Fragment B) with its new tag(say, TAG 'B'). So, when you try to open the previous fragment(say, Fragment A) using it's tag(TAG 'A'), it won't be there, because that's what you replaced.
One possible solution is to hold references to the fragment as they are created, in, say a hashmap, and reuse them instead.
private HashMap<String, Fragment> menuFragments = new HashMap<>();
public void showMenu(String fragmentID)
{
MenuFragment fragment = menuFragments.get(fragmentID);
if(fragment == null)
{
fragment = new MenuFragment(); //Create the respective menu fragment based on the ID.
menuFragments.put(fragmentID, fragment);
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.containerView, fragment, fragmentID);
transaction.commit();
}
How to prevent opening a fragment from navigation drawer if that fragment is already opened, when the fragment is opened and click in the NavigationDrawer item the fragment reCreates again, So how to check if the fragment is already opened?
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (id == R.id.fragmentA) {
{ FragmentA fragment = new FragmentA();
transaction.add(R.id.main_screen, fragment1, "MainFrag").addToBackStack(null);
}
} else if (id == R.id.fragmentB) {
FragmentB fragment = new FragmentB();
transaction.add(R.id.main_screen, fragment).addToBackStack(null);
} else if (id == R.id.fragmentC) {
FragmentC fragment = new FragmentC();
transaction.replace(R.id.main_screen, fragment).addToBackStack(null);
}
I solved it in this way:
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (id == R.id.n_1 && !(f instanceof MainFragment)) {
MainFragment fragment = new MainFragment();
android.support.v4.app.FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
} }
for example I am in the fragment B, And when I click in fragment B
again from the NavigationDrawer it recreates, so how to check if the
fragment is already shown in the screen or not?
If you have reference to Fragment B from the NavigationDrawer, you can call mFragmentB.isAdded() to know whether it's already shown or not.
While adding fragment use tags to replace it.
transaction.replace(R.id.main_screen, fragment, tag);
Now, when menu items are clicked use fragment manager to get the visible fragment using tag :
public boolean isFragmentCurrentlyVisible(String fragmentTag){
Fragment fragmentToTest;
fragmentToTest = fragmentManager.findFragmentByTag(fragmentTag);
if(fragmentToTest!=null && fragmentToTest.isVisible())
return true;
return false;
}
Depending on value returned by above method you can add the fragment if not already added.
I have also struggled with this for a while, I am using navigation components and Kotlin, so finally I got it sorted out. It might not be the best solution, but it's simple and works, so I wanted to share it with you:
when (menuItem.itemId) {
R.id.nav_board -> {
if(findNavController(R.id.nav_host_fragment).currentDestination?.label != "listFragment")
findNavController(R.id.nav_host_fragment)
.navigate(R.id.action_global_listFragment)
}
The label is the destination label given in the navigation.xml
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/navigation"
app:startDestination="#id/listFragment">
<fragment
android:id="#+id/listFragment"
android:name="at.example.app.ui.ListFragment"
android:label="listFragment"
tools:layout="#layout/fragment_list">
...
So this basically just checks if the current destination (where we're at) is the one that should not be re-opened and only navigates if the current destination is a different one.
I have a navigation drawer and clicking on items shows/hides/creates full screen fragments.
For the most part, this code works great. But sometimes, maybe 1% of the time, I will get crazy full screen fragment overlapping when opening the app while it has already been running.
Is the problem with my code..? Or maybe something else in Android where it does not recognize I have the fragments with the tags already created?
Here is the relevant code for how I show/hide/create fragments:
#SuppressWarnings("StatementWithEmptyBody")
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Get to drawer layout so we can interact with it
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
// Get the fragment manager to remove/add fragments
FragmentManager fragmentManager = getSupportFragmentManager();
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_profile) {
// Hide visible fragment
fragmentManager.beginTransaction().hide(getVisibleFragment()).commit();
// Check if the fragment exists first.
if(fragmentManager.findFragmentByTag("profileFragment") != null) {
// If the fragment exists, show it (no reason to recreate it).
fragmentManager.beginTransaction()
.show(fragmentManager.findFragmentByTag("profileFragment"))
.commit();
} else {
// If the fragment does not exist, add it to fragment manager with a tag to identify it.
// Create new fragment instance with required argument(s).
ProfileFragment fragment = ProfileFragment.newInstance();
fragmentManager.beginTransaction()
.add(R.id.content_frame, fragment, "profileFragment")
.commit();
}
// Set the title
mToolbarTitleTextView.setText(R.string.title_activity_profile);
} else if (id == R.id.nav_feed) {
// Hide visible fragment
fragmentManager.beginTransaction().hide(getVisibleFragment()).commit();
// Check if the fragment exists first.
if(fragmentManager.findFragmentByTag("feedFragment") != null) {
// If the fragment exists, show it (no reason to recreate it).
fragmentManager.beginTransaction()
.show(fragmentManager.findFragmentByTag("feedFragment"))
.commit();
} else {
// If the fragment does not exist, add it to fragment manager with a tag to identify it.
fragmentManager.beginTransaction()
.add(R.id.content_frame, new feedFragment(), "feedFragment")
.commit();
}
// Set the title
mToolbarTitleTextView.setText(R.string.title_activity_feed);
} else if (id == R.id.nav_notifications) {
// Hide visible fragment
fragmentManager.beginTransaction().hide(getVisibleFragment()).commit();
// Hide the post button
mPostButton.setVisibility(View.GONE);
// Check if the fragment exists first.
if(fragmentManager.findFragmentByTag("notificationsFragment") != null) {
// If the fragment exists, show it (no reason to recreate it).
fragmentManager.beginTransaction()
.show(fragmentManager.findFragmentByTag("notificationsFragment"))
.commit();
} else {
// If the fragment does not exist, add it to fragment manager with a tag to identify it.
fragmentManager.beginTransaction()
.add(R.id.content_frame, new NotificationsFragment(), "notificationsFragment")
.commit();
}
// Set the title
mToolbarTitleTextView.setText(R.string.title_activity_notifications);
}
mDrawerLayout.closeDrawer(GravityCompat.START);
return true;
}
// Useful method to hide the currently visible fragment
public Fragment getVisibleFragment(){
FragmentManager fragmentManager = MainActivity.this.getSupportFragmentManager();
List<Fragment> fragments = fragmentManager.getFragments();
if(fragments != null){
for(Fragment fragment : fragments){
if(fragment != null && fragment.isVisible())
return fragment;
}
}
return null;
}
EDIT: It is really hard to reproduce this error which makes it hard to debug. It seems to randomly happen.
Why hide and keep all the fragments with fragmentManager.beginTransaction().add(); you can avoid this error by keeping only one fragment in memory and avoiding the hassle of hiding fragments by using fragmentManager.beginTransaction().replace() and using the fragment lifecycle methods to store the fragment state if necessary.
Here is how I solved the problem. In my MainActivity I did this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
setContentView(R.layout.activity_main);
}
Basically what was happening is if I had 1+ fragments on the screen, if the android system ran low on resources while the app was in the background and shut it down, when restored, MainActivity.onCreate() would be called and it would re-instantiate all the fragments with the call
super.onCreate(savedInstanceState);
So I just made it null and this prevents from all those fragments to be recreated.
The reason they are overlapping is because they were all getting shown at once.
Definitely not the correct way to do it, but it solves my problem right now =P
In my android application, a fragment will be added to the activity by a certain action (for example, the action bar menu).
This is the code I add the fragment:
case R.id.action_add_box:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.place, BoxEditFragment.newInstance(null, null));
ft.addToBackStack(null);
ft.commit();
break;
Now once the user hit the action menu with id action_add_box two times, then he have to hit the back two times to close the fragment which is not expected.
Is it possible to avoid this?
For example, once user hit the action menu, do nothing if the fragment have been already visible to the user?
And one more question, there are some EditTexts in the fragment, once user complete, I will submit the data and close the fragment, however user may need to open the fragment again, and I want to keep the value of the EditText as last entered by user. Now I save the values when the fragment are detached and reset the value when created using the savedInstanceState.
Also create a new instance of the fragment for each action command is a waste of memory, I wonder if I can use only one fragment instance, then I may not need to save/reset the values manually?
you can use singleton parttern to keep one instance of fragment for eg:
public class MyFragment extends Fragment{
public static MyFragment oneInstance = null ;
private MyFragment(){
super();
}
public static MyFragment getInstance(){
if (oneInstance == null ){
synchronized (MyFragment.class){
if ( oneInstance == null ){
oneInstance = new MyFragment();
}
}
return oneInstance ;
}
}
the above code is also thread safe
MyFragment frag= (MyFragment )getFragmentManager().findFragmentById(R.id.your_fragment_layout);
if(frag == null){
// fragment is not visible
}else{
// fragment is visible
}
Android Studio 0.5.8
Hello,
I have one activity (MainActivity) that will host 2 fragments (ListAddressBookFragment, AddAddressBookFragment) (only one at a time). The initial fragment will be the ListAddressBookFragment and will be inflated when MainActivity onCreate gets called.
/* MainActivity.java */
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Add and display fragment */
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.flFragmentContainer);
/* Create new fragment if this hasn't already been done */
if(fragment == null) {
fragment = new ListAddressBookFragment();
fragmentManager.beginTransaction()
.add(R.id.flFragmentContainer, fragment)
.commit();
}
}
In the ListAddressBookFragment I have a option menu to add a new addressbook item. So this will call call MainActivity. So I want to replace ListAddressBookFragment with AddAddressBookFragment. However, because the code above is hardcoded I am wondering is there anyway to do this on the fly?
/* ListAddressBookFragment.java */
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.new_addressbook) {
Intent intent = new Intent(getActivity(), MainActivity.class);
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
Many thanks for any suggestions,
I would recommend you to read this article, Communicating with fragments
Basically, you need to call
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
Also, consider using interface within fragment that activity implements.
Why don't you pass data to the MainActivity to indicate the mode you want the activity to be in?
intent.putExtra("mode", "addressbook");
In the MainActivity, you do the below.
String mode = (String)getIntent().getStringExtra("mode");
if ("addressbook".equals(mode)) {
// Address book fragment
} else {
// ListAddressBookFragment
}
Good Luck