Android Getting Fragment ID for use in Switch statement - android

So, I am writing a navigation part of my app and have dynamically created fragments inside an activity.
I want, when a user hits either the next or previous arrow, for the app to find out what fragment is in the activity and replace it with either the next or previous fragment. In order to do that, I created a switch statement to check on what fragment is being displayed.
I thought that I would be able to do this by using
getSupportFragmentManager().findFragmentById(R.id.activity_public_internet)
but that doesn't seem to work in the switch statement.
So I then tried
getFragmentManager().findFragmentById(R.id.public_internet_intro_fragment).getId()
and that doesn't work either.
Here is the full code. Any help will be fully appreciated. And feel free to tell me if there is a better way to do it.
public void goPrev(View view) {
switch(getSupportFragmentManager().findFragmentById(R.id.activity_public_internet).getId()) {
case R.id.public_internet_intro_fragment:
Intent intent = new Intent(this, LearnActivity.class);
startActivity(intent);
break;
case R.id.public_internet_topic_fragment:
Fragment introFragment = new PublicInternetIntroFragment();
FragmentTransaction exampleTransaction = getFragmentManager().beginTransaction();
exampleTransaction.replace(R.id.activity_public_internet, introFragment);
exampleTransaction.addToBackStack(null);
exampleTransaction.commit();
break;
case R.id.public_internet_example_fragment:
Fragment topicFragment = new PublicInternetTopicFragment();
FragmentTransaction topicTransaction = getFragmentManager().beginTransaction();
topicTransaction.replace(R.id.activity_public_internet, topicFragment);
topicTransaction.addToBackStack(null);
topicTransaction.commit();
break;
}
}
public void goNext(View view) {
switch(getFragmentManager().findFragmentById(R.id.public_internet_intro_fragment).getId()) {
case R.id.public_internet_intro_fragment:
Fragment topicFragment = new PublicInternetTopicFragment();
FragmentTransaction topicTransaction = getFragmentManager().beginTransaction();
topicTransaction.replace(R.id.activity_public_internet, topicFragment);
topicTransaction.addToBackStack(null);
topicTransaction.commit();
break;
case R.id.public_internet_topic_fragment:
Fragment exampleFragment = new PublicInternetExampleFragment();
FragmentTransaction exampleTransaction = getFragmentManager().beginTransaction();
exampleTransaction.replace(R.id.activity_public_internet, exampleFragment);
exampleTransaction.addToBackStack(null);
exampleTransaction.commit();
break;
case R.id.public_internet_example_fragment:
Intent intent = new Intent(this, LearnActivity.class);
startActivity(intent);
break;
}
}
When checking the error log, a Null Pointer Exception is raised after attempting to invoke int android.app.Fragment.getId()
EDIT: Added PublicInternetActivity.java
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
public class PublicInternetActivity extends ActionBarActivity{
private static final String tag_public_internet_intro_fragment = "public_internet_intro_fragment";
private static final String tag_public_internet_topic_fragment = "public_internet_topic_fragment";
private static final String tag_public_internet_example_fragment = "public_internet_example_fragment";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_public_internet);
introFrag();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_learn, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_home) {
goHome();
return true;
}
else {
return super.onOptionsItemSelected(item);
}
}
public void introFrag() {
Fragment introFragment = new PublicInternetIntroFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.activity_public_internet, introFragment);
transaction.addToBackStack(null);
transaction.commit();
}
public void goHome() {
Intent homeIntent = NavUtils.getParentActivityIntent(this);
NavUtils.navigateUpTo(this, homeIntent);
}
public void pushNewFragment( Fragment newFrag, String tag) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.activity_public_internet, newFrag, tag);
transaction.addToBackStack(tag);
transaction.commit();
}
public String getActiveFragmentTag() {
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
return null;
}
String tag = getSupportFragmentManager().getBackStackEntryAt(getSupportFragmentManager().getBackStackEntryCount() - 1).getName();
return tag;
}
public void goPrev(View view) {
switch(getActiveFragmentTag()) {
case tag_public_internet_intro_fragment:
Intent intent = new Intent(this, LearnActivity.class);
startActivity(intent);
break;
case tag_public_internet_topic_fragment:
Fragment introFragment = new PublicInternetIntroFragment();
pushNewFragment(introFragment, tag_public_internet_intro_fragment);
break;
case tag_public_internet_example_fragment:
Fragment topicFragment = new PublicInternetTopicFragment();
pushNewFragment(topicFragment, tag_public_internet_topic_fragment);
break;
}
}
public void goNext(View view) {
switch(getActiveFragmentTag()) {
case tag_public_internet_intro_fragment:
Fragment topicFragment = new PublicInternetTopicFragment();
pushNewFragment(topicFragment, tag_public_internet_topic_fragment);
break;
case tag_public_internet_topic_fragment:
Fragment exampleFragment = new PublicInternetExampleFragment();
pushNewFragment(exampleFragment, tag_public_internet_example_fragment);
break;
case tag_public_internet_example_fragment:
Intent intent = new Intent(this, LearnActivity.class);
startActivity(intent);
break;
}
}
}

Try to do it this way:
public void pushNewFragment( Fragment newFrag, String tag) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.activity_public_internet, newFrag, tag);
transaction.addToBackStack(tag);
transaction.commit();
}
public String getActiveFragmentTag() {
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
return null;
}
String tag = getSupportFragmentManager().getBackStackEntryAt(getSupportFragmentManager().getBackStackEntryCount() - 1).getName();
return tag;
}
public void goPrev(View view) {
switch(getActiveFragmentTag()) {
case tag_public_internet_intro_fragment:
Intent intent = new Intent(this, LearnActivity.class);
startActivity(intent);
break;
case tag_public_internet_topic_fragment:
Fragment introFragment = new PublicInternetIntroFragment();
pushNewFragment(introFragment, tag_public_internet_intro_fragment);
break;
case tag_public_internet_example_fragment:
Fragment topicFragment = new PublicInternetTopicFragment();
pushNewFragment(introFragment, tag_public_internet_topic_fragment);
break;
}
}
EDIT
from the documentation:
Get fragments that exist in the activity, with findFragmentById() (for
fragments that provide a UI in the activity layout) or
findFragmentByTag() (for fragments that do or don't provide a UI).
Normally we use ids for static fragments (embedded in the activity layout and that you don't need to change in runtime). For dynamic Fragments which is your case it's better to use tags.
Best of luck

This looks very complicated, I would suggest something more simple.
Use Fragment.instantiate to create a Fragment and replace the current one. If the order of your Fragments is fixed, keep an Array with the name of the class of each Fragment in it (in the good order).
Then, keep a pointer to know where you are in your Array, and each time you go to the next/previous Fragment, increment your pointer accordingly.
This way,you don't have to implement case by case handling, which seems to be very error-prone. You should consider the cases where
pointer == 0 and pointer == length-1.
This way, you can have any number of Fragments the one after the other. Note that however, it only works for a determined order. If you want something more complicated, you should extend the Fragment class and add logic into your subclass.

Related

How to keep fragments state like Instagram in Android?

I need to implement the UI of my app, like the Instagram one. I need to switch from different fragments, with the usage of the bottom navigation view, but I need to keep state of the fragments, like I left them. How Can I achieve this?
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
item.setChecked(true);
switch (item.getItemId()) {
case R.id.action_formation:
if (homeFragment == null) {
homeFragment = new HomeFragment();
}
displayFragment(homeFragment);
break;
case R.id.action_result:
if (introResultFragment == null) {
introResultFragment = new IntroResultFragment();
}
displayFragment(introResultFragment);
break;
case R.id.action_market:
displayFragment(new MarketFragment());
break;
}
return false;
}
public void displayFragment(final Fragment fragment) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fragmentTransaction.replace(R.id.container, fragment, fragment.getClass().toString());
fragmentTransaction.commit();
}
It's been a long time but I want to offer my open source library in github which implements the same UX of Youtube and Instagram:
https://github.com/ZachBublil/ZNavigator
You got to add this dependency to your gradle:
compile 'com.zach.znavigator:znavigator:1.0.0'
The only thing you have to do is to pass the list of fragments you want to be in the BottomNavigationView:
public class SampleActivity extends NavigationActivity {
private BottomNavigationView navigationView;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
navigationView = (BottomNavigationView) findViewById(R.id.navigationView);
LinkedHashMap<Integer, Fragment> rootFragments = new LinkedHashMap<>();
rootFragments.put(R.id.tab1, new FirstTab());
rootFragments.put(R.id.tab2,new SecondTab());
rootFragments.put(R.id.tab3, new ThirdTab());
init(rootFragments, R.id.container);
navigationView.setOnNavigationItemSelectedListener(this);
navigationView.setOnNavigationItemReselectedListener(this);
}
#Override
public void tabChanged(int id) {
navigationView.getMenu().findItem(id).setChecked(true);
}
}
If you want to open a new fragment as inner screen in one of the tabs you can do it by using ZNavigation class in the tab fragment:
ZNavigation.openChildFragment(getFragmentManager(),new ChildFragment());
just remember the active fragment, and use userVisiableHint to get active status in each fragment.
private Fragment currentFragment; // need to be init
private void switch2Fragment(Fragment target){
getFragmentManager().executePendingTransactions();
if(target.isAdded){
getFragmentManager().beginTransaction().hide(currentFragment).show(target).commit();
} else {
getFragmentManager().beginTransaction().hide(currentFragment).add(R.id.xxx, target).commit();
}
currentFragment.setUserVisibleHint(false);
currentFragment = target;
target.setUserVisibleHint(true);
}
private boolean isFragmentActive(Fragment target){
return target.getUserVisibleHint();
}

How to implement proper back navigation for the Android BottomNavigation

I spent my whole day now looking for an answer and didn't found any solution that suited this question.
I am looking for a way to create a BottomNavigation usage similar to the one of the Instagram or PlayKiosk app. Fragments should be added to the back stack only once. When pressing the back button I'd expect the app to jump back to the last Fragment that was visited and the button of the BottomNavigation to fit that Fragment.
Currently I use the following code:
//BottomNavigationListener
private BottomNavigationView.OnNavigationItemSelectedListener buttonNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
FragmentManager fragmentManager = getSupportFragmentManager();
switch (item.getItemId()) {
case R.id.navigation_game:
currentFragment = GameFragment.newInstance();
fragmentManager.beginTransaction()
.addToBackStack(TAG_FRAGMENT_GAME)
.replace(R.id.content_frame, currentFragment, TAG_FRAGMENT_GAME)
.commit();
break;
case R.id.navigation_tournament:
currentFragment = TournamentFragment.newInstance();
fragmentManager.beginTransaction()
.addToBackStack(TAG_FRAGMENT_TOURNAMENT)
.replace(R.id.content_frame, currentFragment, TAG_FRAGMENT_TOURNAMENT)
.commit();
break;
case R.id.navigation_history:
break;
}
return true;
}
};
But this leads to the problem that I could press the button of my BottomNavigation a couple of times and for each of this clicks a new Fragment would be instantiated. Also the BottomNavigation buttons are not set according to the Fragments.
I found this answer but it didn't work out Prevent The Same Fragment From Stacking More Than Once ( addToBackStack)
You can use following structure to have the same working logic of Instagram.
First create a custom limited unique queue class. It only holds last N items, where N is the limit number (or maximum item count) passed to its constructor. Moreover, this type of queue keeps only one instance of a class. If an instance of class A is already in the queue and another instance of class A is to be added to the queue, the former instance is removed first and then the new object is inserted.
public class LimitedUniqueQueue<E> extends LinkedList<E> {
private int limit;
public LimitedUniqueQueue(int limit) {
this.limit = limit;
}
#Override
public boolean add(E o) {
// For uniqueness
for (int i = 0; i < super.size(); i++) {
E item = super.get(i);
if (item.getClass() == o.getClass()) {
super.remove(i);
break;
}
}
boolean added = super.add(o);
// For size limit
while (added && size() > limit) {
super.remove();
}
return added;
}
}
Then update your Activity as follows:
public class MainActivity extends AppCompatActivity {
private BottomNavigationView navigation;
private LimitedUniqueQueue<Fragment> queue;
...
private BottomNavigationView.OnNavigationItemSelectedListener onNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
Fragment gameFragment;
Fragment tournamentFragment;
Fragment profileFragment;
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
switch (item.getItemId()) {
case R.id.navigation_game:
if (gameFragment == null) {
gameFragment = new GameFragment();
}
transaction.replace(R.id.content, gameFragment).commit();
queue.add(gameFragment);
return true;
case R.id.navigation_tournament:
if (tournamentFragment == null) {
tournamentFragment = new TournamentFragment();
}
transaction.replace(R.id.content, tournamentFragment).commit();
queue.add(tournamentFragment);
return true;
case R.id.navigation_profile:
if (profileFragment == null) {
profileFragment = new ProfileFragment();
}
transaction.replace(R.id.content, profileFragment).commit();
queue.add(profileFragment);
return true;
}
return false;
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
navigation = (BottomNavigationView) findViewById(R.id.navigation);
queue = new LimitedUniqueQueue<>(navigation.getMenu().size());
navigation.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener);
navigation.setSelectedItemId(R.id.navigation_game);
...
}
#Override
public void onBackPressed() {
if (queue.size() > 1) {
queue.removeLast();
Fragment previousFragment = queue.getLast();
if (previousFragment instanceof GameFragment) {
navigation.setSelectedItemId(R.id.navigation_game);
} else if (previousFragment instanceof TournamentFragment) {
navigation.setSelectedItemId(R.id.navigation_tournament);
} else if (previousFragment instanceof ProfileFragment) {
navigation.setSelectedItemId(R.id.navigation_profile);
}
} else {
super.onBackPressed();
}
}
...
}

Fragment re-created on bottom navigation view item selected

Following is my code for bottom navigation view item selected
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
switch (item.getItemId()) {
case R.id.action_one:
// Switch to page one
fragment = FragmentA.newInstance();
break;
case R.id.action_two:
// Switch to page two
fragment = FragmentB.newInstance();
break;
case R.id.action_three:
// Switch to page three
fragment = FragmentC.newInstance();
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.container,fragment,"TAG").commit();
return true;
}
});
Now my problem is every time fragment is re-created and don't want fragment to be recreated every time I also tried adding addToBackStack(null) but it this case on back button press keeps popping fragments from stack which I don't want.
Is there any way to display fragments on bottom navigation bar selected without re-creating fragment
With support library v26 you can do this
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
Fragment curFrag = mFragmentManager.getPrimaryNavigationFragment();
if (curFrag != null) {
fragmentTransaction.detach(curFrag);
}
Fragment fragment = mFragmentManager.findFragmentByTag(tag);
if (fragment == null) {
fragment = new YourFragment();
fragmentTransaction.add(container.getId(), fragment, tag);
} else {
fragmentTransaction.attach(fragment);
}
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.setReorderingAllowed(true);
fragmentTransaction.commitNowAllowingStateLoss();
I faced the same problem and finally i i found the solution, you can try this code. it's work for me.
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.FrameLayout;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
public BottomNavigationView bv;
public home_fragment home;
public account_fragment afrag;
public other_fragment other;
public FrameLayout fr;
android.support.v4.app.Fragment current;
//public FragmentTransaction frt;
public static int temp=0;
final Fragment fragment11 = new account_fragment();
final Fragment fragment22 = new home_fragment();
final Fragment fragment33 = new other_fragment();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment11;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bv=(BottomNavigationView) findViewById(R.id.navigationView);
fm.beginTransaction().add(R.id.main_frame, fragment33, "3").hide(fragment33).commit();
fm.beginTransaction().add(R.id.main_frame, fragment22, "2").hide(fragment22).commit();
fm.beginTransaction().add(R.id.main_frame,fragment11, "1").commit();
bv.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.account:
fm.beginTransaction().hide(active).show(fragment11).commit();
active = fragment11;
return true;
case R.id.home1:
fm.beginTransaction().hide(active).show(fragment22).commit();
active = fragment22;
return true;
case R.id.other:
fm.beginTransaction().hide(active).show(fragment33).commit();
active = fragment33;
return true;
}
return false;
}
});
bv.setOnNavigationItemReselectedListener(new BottomNavigationView.OnNavigationItemReselectedListener() {
#Override
public void onNavigationItemReselected(#NonNull MenuItem item) {
Toast.makeText(MainActivity.this, "Reselected", Toast.LENGTH_SHORT).show();
}
});
}
}
This seemed to work well for me. Instead of attaching and detaching, i use show or hide to maintain fragment state.
public void changeFragment(Fragment fragment, String tagFragmentName) {
FragmentManager mFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
Fragment currentFragment = mFragmentManager.getPrimaryNavigationFragment();
if (currentFragment != null) {
fragmentTransaction.hide(currentFragment);
}
Fragment fragmentTemp = mFragmentManager.findFragmentByTag(tagFragmentName);
if (fragmentTemp == null) {
fragmentTemp = fragment;
fragmentTransaction.add(R.id.frame_layout, fragmentTemp, tagFragmentName);
} else {
fragmentTransaction.show(fragmentTemp);
}
fragmentTransaction.setPrimaryNavigationFragment(fragmentTemp);
fragmentTransaction.setReorderingAllowed(true);
fragmentTransaction.commitNowAllowingStateLoss();
}
And this is how i use it
private void initViews() {
BottomNavigationView bottomNavigationView = findViewById(R.id.navigation);
bottomNavigationView.setOnNavigationItemSelectedListener
(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment selectedFragment = null;
switch (item.getItemId()) {
case R.id.explore:
changeFragment(new ExploreFragment(), ExploreFragment.class
.getSimpleName());
toggleViews(true, "");
break;
case R.id.favorite:
changeFragment(new FavoriteFragment(), FavoriteFragment.class
.getSimpleName());
toggleViews(false, "Favorites");
break;
case R.id.venue:
changeFragment(new VenueFragment(), VenueFragment.class.getSimpleName());
toggleViews(false, "Venues");
break;
case R.id.profile:
changeFragment(new ProfileFragment(), ProfileFragment.class
.getSimpleName());
toggleViews(false, "Profile");
break;
}
return true;
}
});
//Manually displaying the first fragment - one time only
changeFragment(new ExploreFragment(), ExploreFragment.class
.getSimpleName());
}
This solution stops a fragment from being re-created when the currently selected nav button is clicked again. However, a new fragment will be created every time the user clicks a different nav button.
Just add this line to avoid re-created Fragment from BottomNavigationView
bottomNavigation.setOnNavigationItemReselectedListener(new BottomNavigationView.OnNavigationItemReselectedListener() {
#Override
public void onNavigationItemReselected(#NonNull MenuItem item) {
// do nothing here
}
});
Or with a lambda:
bottomNavigation.setOnNavigationItemReselectedListener(item -> { });
Be careful when using replace. Even if providing a fragment that already exists in memory, replace will restart the fragment's lifecycle. To avoid a restart, the transaction object's methods includes add, show, and hide, which can be used to show the correct fragment without restarting it.
private fun switchFragment(selectedTabIndex: Int) {
val previousTabIndex = this.currentTabIndex
this.currentTabIndex = selectedTabIndex
val transaction = supportFragmentManager.beginTransaction()
val tag = fragments[this.currentTabIndex].tag
// if the fragment has not yet been added to the container, add it first
if (supportFragmentManager.findFragmentByTag(tag) == null) {
transaction.add(R.id.container, fragments[this.currentTabIndex], tag)
}
transaction.hide(fragments[previousTabIndex])
transaction.show(fragments[this.currentTabIndex])
transaction.commit()
}
Try this :
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.container);
switch (item.getItemId()) {
case R.id.action_one:
// Switch to page one
if (!(currentFragment instanceof FragmentA)) {
fragment = FragmentA.newInstance();
}
break;
case R.id.action_two:
// Switch to page two
if (!(currentFragment instanceof FragmentB)) {
fragment = FragmentB.newInstance();
}
break;
case R.id.action_three:
// Switch to page three
if (!(currentFragment instanceof FragmentC)) {
fragment = FragmentC.newInstance();
}
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment, "TAG").commit();
return true;
}
});
That will get the current fragment in your container and if you click again on this fragment that will not re add the fragment.
setOnNavigationItemReselectedListener would be a better solution for that
Use setOnNavigationItemReselectedListener like this:
private BottomNavigationView.OnNavigationItemReselectedListener onNavigationItemReselectedListener
= new BottomNavigationView.OnNavigationItemReselectedListener() {
#Override
public void onNavigationItemReselected(#NonNull MenuItem item) {
Toast.makeText(MainActivity.this, "Reselected", Toast.LENGTH_SHORT).show();
}
};
and call it using:
navigation.setOnNavigationItemReselectedListener(onNavigationItemReselectedListener);
The best way i found to do it.
private void replace_fragment(Fragment fragment) {
String tag = fragment.getClass().getSimpleName();
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
Fragment curFrag = getSupportFragmentManager().getPrimaryNavigationFragment();
Fragment cacheFrag = getSupportFragmentManager().findFragmentByTag(tag);
if (curFrag != null)
tr.hide(curFrag);
if (cacheFrag == null) {
tr.add(R.id.main_frame, fragment, tag);
} else {
tr.show(cacheFrag);
fragment = cacheFrag;
}
tr.setPrimaryNavigationFragment(fragment);
tr.commit();
}
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.nav_posts:
replace_fragment(new PostsFragment());
return true;
case R.id.nav_stores:
replace_fragment(new StoresFragment());
return true;
case R.id.nav_chats:
replace_fragment(new DiscussionsFragment());
return true;
case R.id.nav_account:
replace_fragment(new ProfileFragment());
return true;
}
return false;
}
};
I've improved #Viven's answers and written it with Kotlin. My version instantiates fragment only for the first time, hides/shows. I'm new at Kotlin so tell me if I can improve anything.
We need to hold a id to tag map:
private val fragmentTags = hashMapOf(
R.id.action_home to "home_fragment",
R.id.action_profile to "profile_fragment"
)
The listener code:
bottomNavigation.run {
setOnNavigationItemSelectedListener { menuItem ->
supportFragmentManager.beginTransaction()
.let { transaction ->
// hide current fragment
supportFragmentManager.primaryNavigationFragment?.let {
// if selected fragment's tag is same, do nothing.
if (it.tag == fragmentTags[menuItem.itemId]) {
return#setOnNavigationItemSelectedListener true
}
transaction.hide(it)
}
var fragment = supportFragmentManager.findFragmentByTag(fragmentTags[menuItem.itemId])
if (fragment == null) {
// instantiate fragment for the first time
fragment = when(menuItem.itemId) {
R.id.action_home -> HomeFragment()
R.id.action_profile -> ProfileFragment()
else -> null
}?.also {
// and add it
transaction.add(R.id.frame, it, fragmentTags[menuItem.itemId])
}
} else {
// if it's found show it
transaction.show(fragment)
}
transaction
.setPrimaryNavigationFragment(fragment)
.setReorderingAllowed(true)
}.commitNowAllowingStateLoss()
return#setOnNavigationItemSelectedListener true
}
//bottomNavigation things
itemIconTintList = null
selectedItemId = R.id.action_home
}
I solved this problem by adding a ViewPager to which I delegated all my navigation fragments. Its adapter (FragmentPagerAdapter) doesn't recreate the fragments instances when the user navigates through the BotoomNavigationView.
To achieve this, you have to complete 5 easy steps:
add a ViewPager to your layout;
implement its adapter:
class YourNavigationViewPagerAdapter(fm: FragmentManager,
private val param1: Int,
private val param2: Int)
: FragmentPagerAdapter(fm) {
override fun getItem(p0: Int) = when(p0) {
0 -> NavigationFragment1.newInstance(param1, param2)
1 -> NavigationFragment2.newInstance(param1, param2)
2 -> NavigationFragment3.newInstance(param1, param2)
else -> null
}
override fun getCount() = 3
}
don't forget to set the new adapter:
yourViewPager.adapter = YourNavigationViewPagerAdapter(supportFragmentManager, param1, param2)
set a OnNavigationItemSelectedListener to your BottomNavigationView like the following:
yourBottomNavigationView.setOnNavigationItemSelectedListener {
when(it.itemId) {
R.id.yourFirstFragmentMenuItem -> {
yourViewPager.currentItem = 0
true
}
R.id.yourSecondFragmentMenuItem -> {
yourViewPager.currentItem = 1
true
}
R.id.yourThirdFragmentMenuItem -> {
yourViewPager.currentItem = 2
true
}
else -> false
}
}
set a OnPageChangeListener to your ViewPager like the following:
yourViewPager.addOnPageChangeListener(object :
ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(p0: Int) {
}
override fun onPageScrolled(p0: Int, p1: Float, p2: Int) {
}
override fun onPageSelected(p0: Int) {
yourBottomNavigationView.menu.getItem(p0).isChecked = true
}
})
enjoy :)
I wrote an Extension function in Kotlin for the same.
fun FragmentManager.switch(containerId: Int, newFrag: Fragment, tag: String) {
var current = findFragmentByTag(tag)
beginTransaction()
.apply {
//Hide the current fragment
primaryNavigationFragment?.let { hide(it) }
//Check if current fragment exists in fragmentManager
if (current == null) {
current = newFrag
add(containerId, current!!, tag)
} else {
show(current!!)
}
}
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.setPrimaryNavigationFragment(current)
.setReorderingAllowed(true)
.commitNowAllowingStateLoss()
}
There are several test cases involved in proper navigation , I am pasting my code with all test cases checked.
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
switch (item.getItemId()) {
case R.id.dashboard:
Fragment fragment;
fragment = fragmentManager.findFragmentByTag(DashboardFragment.TAG);
if (fragment == null) {
fragment = new DashboardFragment();
fragmentTransaction.add(R.id.frame, fragment, DashboardFragment.TAG);
} else {
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
}
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
case R.id.expenses:
fragment = fragmentManager.findFragmentByTag(ExpenseFragment.TAG);
if (fragment == null) {
fragment = new ExpenseFragment();
fragmentTransaction.add(R.id.frame, fragment, ExpenseFragment.TAG);
} else {
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
}
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
case R.id.vehicle_parts:
Bundle bundle = new Bundle();
bundle.putInt("odometer", vehicle.getOdometer());
bundle.putString("vehicle_id", vehicle.get_id());
fragment = fragmentManager.findFragmentByTag(PartsFragment.TAG);
if (fragment == null) {
fragment = new PartsFragment();
fragment.setArguments(bundle);
fragmentTransaction.add(R.id.frame, fragment, PartsFragment.TAG);
} else {
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
}
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
case R.id.blog:
fragment = fragmentManager.findFragmentByTag(BlogFragment.TAG);
if (fragment == null) {
fragment = new BlogFragment();
fragmentTransaction.add(R.id.frame, fragment, BlogFragment.TAG);
} else {
fragmentTransaction.detach(fragmentManager.getPrimaryNavigationFragment());
fragmentTransaction.attach(fragment);
}
fragmentTransaction.setPrimaryNavigationFragment(fragment);
fragmentTransaction.commitNow();
return true;
}
return false;
Use Cicerone library for handling navigation easily.
https://github.com/terrakok/Cicerone
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.save: {
router.replaceScreen("your fragment1");
menuItem.setChecked(true);
break;
}
case R.id.search_lessons: {
router.replaceScreen("your fragment2");
menuItem.setChecked(true);
break;
}
case R.id.profile_student: {
router.replaceScreen("your fragment3");
menuItem.setChecked(true);
break;
}
}
return false;
}
How To stop recreating Fragment when it is Already Visible Using BottomNavigationView
Step 1--
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
String valuestring;
**if (item.getItemId() == lastSelectedItemId) { // added this
Log.e( "both id is same","LULL" );
return true;
}**
switch (item.getItemId()) {
case R.id.navigation_category:
SetActioBarText( getString( R.string.label_actionbar_categories ) );
fragment = new CategoryFragment();
valuestring = "category";
break;
case R.id.navigation_favourite:
SetActioBarText( getString( R.string.label_actionbar_favourites ) );
fragment = new FavouriteFragment();
valuestring = "favourites";
break;
default:
throw new IllegalStateException( "Unexpected value: " + menuItem.getItemId() );
}
return loadFragment( fragment, valuestring ,menuItem);
}
Now Step 2---
private boolean loadFragment(Fragment fragment, String argument,MenuItem item) {
if (fragment != null) {
transaction = fragmentManager.beginTransaction();
transaction.addToBackStack( argument );
transaction.setTransition( FragmentTransaction.TRANSIT_FRAGMENT_FADE );
transaction.replace( R.id.frame_container, fragment, "demofragment").commitAllowingStateLoss();
lastSelectedItemId= item.getItemId();
return true;
}
return false;
}
no need to do this answers. actually what you really need is to save view in specific fragmet.
private View view;
if(view == null){
view = inflater.inflate(R.layout.call_fragment_edited, container, false);
}
so any time when you create new fragment you see current state

One Fragment inside ViewPager doesn't show up

I have a Tabbed Activity hosting a viewpager (auto-generated with eclipse wizard). I slightly modified just the auto generated FragmentPagerAdapter in order to instantiate a specific fragment on each tab. Initially i had 4 tab, 3 fragments for the first tabs and still the auto generated placeholder in the last one, and they worked. When i replaced the last placeholder with an instance of one of my Fragment i faced the following problem: the fragment in one of the last two tabs stays blank, as follow:
_____1st TAB___|____2nd TAB_|___3rd TAB_____|____4th TAB____|
[MasterFragment][RequestMap][MasterFragment][MasterFragment]
This stays blank_____________________|
Moreover, if i make a "random" sequence of actions (change tabs, click on the buttons to perform some actions,...) the fragment that doesn't show up swaps, and becomes the last one as follow:
_____1st TAB___|____2nd TAB_|___3rd TAB_____|____4th TAB____|
[MasterFragment][RequestMap][MasterFragment][MasterFragment]
This stays blank__________________________________|
Here's the code for my FragmentPagerAdapter:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case REQUEST_TAB:
masterFragment = new MasterFragment(MasterFragment.ALL_REQUEST);
return masterFragment;
case MAP_TAB:
requestMap = new RequestMap();
return requestMap;
case OWNER_TAB:
masterFragmentOwner = new MasterFragment(MasterFragment.OWNER_REQUEST);
System.out.println("I should have created the Owner MasterFragment");
return masterFragmentOwner;
case JOINED_TAB:
masterFragmentJoined = new MasterFragment(MasterFragment.JOINED_REQUEST);
return masterFragmentJoined;
}
return null;
}
#Override
public int getCount() {
return 4;
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case REQUEST_TAB:
return getString(R.string.request_tab_title).toUpperCase(l);
case MAP_TAB:
return getString(R.string.map_tab_title).toUpperCase(l);
case OWNER_TAB:
return getString(R.string.owner_tab_title).toUpperCase(l);
case JOINED_TAB:
return getString(R.string.joined_tab_title).toUpperCase(l);
default:
return "";
}
}
}
I omit the code regarding the setup of the viewPager because is the same auto generated from the eclipse wizard. While here's the code for the MasterFragment class:
package it.polimi.frontend.fragment;
import it.polimi.appengine.entity.manager.model.Request;
import it.polimi.appengine.entity.manager.model.User;
import it.polimi.frontend.activity.R;
import it.polimi.frontend.util.QueryManager;
import it.polimi.frontend.util.QueryManager.OnRequestLoadedListener;
import java.util.ArrayList;
import java.util.List;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class MasterFragment extends Fragment implements OnRequestLoadedListener, RequestList.OnRequestSelectedListener, RequestDetail.OnUserClickedListener{
private boolean twoPane;
private static View view;
public final static int ALL_REQUEST=0;
public final static int OWNER_REQUEST=1;
public final static int JOINED_REQUEST=2;
private int mode;
public MasterFragment(){
this.mode=0;
}
public MasterFragment(int mode){
this.mode=mode;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
QueryManager.getInstance().addListener(this);
if (view != null) {
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null)
parent.removeView(view);
}
try {
view = inflater.inflate(R.layout.fragment_master,container, false);
List<Request> requests = new ArrayList<Request>();
RequestList requestListFragment = null;
switch (mode) {
case OWNER_REQUEST:
requests = QueryManager.getInstance().getCurrentUser().getRequests();
System.out.println("Dovrei aver recuperato le richieste dell'owner");
break;
case JOINED_REQUEST:
String mail=null;
if (QueryManager.getInstance().getCurrentUser()!=null){
User current =QueryManager.getInstance().getCurrentUser();
mail = current.getPwAccount() != null ?
current.getPwAccount()
: current.getGmailAccount() != null ?
current.getGmailAccount()
: current.getFbAccount() != null ?
current.getFbAccount()
: null;
requests = QueryManager.getInstance().getUserPartecipation(mail);
}
else
requests = new ArrayList<Request>();
break;
default: //ALL_REQUEST case and all the other possibilities
requests = QueryManager.getInstance().getRequests();
break;
}
requestListFragment = new RequestList(requests, mode);
if (view.findViewById(R.id.detail_container) != null) {//TABLET CASE
twoPane = true;
getChildFragmentManager().beginTransaction()
.replace(R.id.request_list_container,requestListFragment,RequestList.ID)
.commit();
} else { //PHONE CASE:
getChildFragmentManager().beginTransaction()
.replace(R.id.container,requestListFragment,RequestList.ID)
.commit();
}
} catch (InflateException e) {
// is already there, just return view as it is
e.printStackTrace();
}
return view;
}
#Override
public void onRequestSelected(int position, Request request) {
if (twoPane) {
DetailContainerFragment detailContFrag = new DetailContainerFragment(request,mode);
getChildFragmentManager().beginTransaction()
.replace(R.id.detail_container, detailContFrag, DetailContainerFragment.ID).commit();
} else {
switch (mode) {//This empty switch if for future changes
case OWNER_REQUEST:
break;
case JOINED_REQUEST:
break;
default://ALL_REQUEST
break;
}
RequestDetail fragment = new RequestDetail(request,mode);
Fragment reqList=getChildFragmentManager().findFragmentByTag(RequestList.ID);
getChildFragmentManager().beginTransaction()
.hide(reqList)
.addToBackStack(RequestDetail.ID)
.add(R.id.container,fragment,RequestDetail.ID)
.commit();
}
}
#Override
public void onUserClicked(User user,String requestId) {
if (!twoPane) {
FeedbackDetail fragment = new FeedbackDetail(user,this.mode,requestId);
Fragment reqDetail=getChildFragmentManager().findFragmentByTag(RequestDetail.ID);
getChildFragmentManager().beginTransaction()
.hide(reqDetail)
.addToBackStack(FeedbackDetail.ID)
.add(R.id.container,fragment,FeedbackDetail.ID)
.commit();
} else {
/*DetailContainerFragment should take care of it*/
}
}
#Override
public void onRequestLoaded(List<Request> requests) {
System.out.println("Ho caricato: "+requests.size());
RequestList requestListFragment = (RequestList)getChildFragmentManager().findFragmentByTag(RequestList.ID);
switch (mode) {//Also this switch is for future changes, but the requests list is anyway fetched and assigned to RequestList above
case OWNER_REQUEST:
//TODO
break;
case JOINED_REQUEST:
//TODO
break;
default: //ALL_REQUEST
if (requestListFragment!=null)
requestListFragment.setRequestAdapter(requests);
break;
}
}
}
If i'm missing something important please, let me know.
Thank you all in advance
EDIT:
I forgot to say that the System.out that i placed show up in the console, so the "blank" fragment should have been created, and it should have passed through his onCreateView().
I found the bug. The problem was
private static View view;
because of the static properties. Removing "static" it worked again. Actually i can't explain even now how it could have worked for a while on the first and last tabs and not in the third, because the static attributes should be shared among all instances of the class. Because of the internals of the ViewPager the problem seemed to show up only on adjacent identical MasterFragment, if they where interleaved with any other fragment it worked.

Get previous fragment back

Here is my code snippet:
I want to create a new fragment initially, but if the fragment is already created the i want to show the previous fragment;
But every time a new fragment is initialise.
#Override
protected void onCreate(Bundle savedInstanceState) {
.
.
showFragment(FRAGMENT_ADD_DETAILS);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if(currentFragment==FRAGMENT_ADD_DETAILS){
finish();
}else{
showFragment(FRAGMENT_ADD_DETAILS);
}
break;
case R.id.menu_action_next:
showFragment(FRAGMENT_INVITE_FRIENDS);
break;
}
return super.onOptionsItemSelected(item);
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
return super.onPrepareOptionsMenu(menu);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
.
.
return true;
}
private void showFragment(int fragmentType) {
Log.d(TAG,"showFragment : "+fragmentType);
FragmentManager fragmentManager = getFragmentManager();
Fragment prevFragment = fragmentManager.findFragmentByTag(String.valueOf(fragmentType));
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (prevFragment == null) { //create a fresh instance of fragment
Log.d(TAG,"Initialize new fragment");
Fragment newFragment;
if (fragmentType == FRAGMENT_ADD_DETAILS) {
newFragment = new NewDetailsFragment();
} else {
newFragment = new NewInviteFragment();
}
fragmentTransaction.replace(R.id.ant_container_layout, newFragment);
fragmentTransaction.addToBackStack(String.valueOf(fragmentType));
} else {//just reuse the previous fragment
Log.d(TAG,"Re-Using previous fragment");
fragmentTransaction.replace(R.id.ant_container_layout, prevFragment);
}
fragmentTransaction.commit();
}
Please Help!
You never add any tags to your fragments, so you cannot find them by using findFragmentByTag().
When you replace a fragment, instead of
fragmentTransaction.replace(R.id.ant_container_layout, newFragment);
use the overloaded method that accepts a tag:
fragmentTransaction.replace(R.id.ant_container_layout, newFragment, String.valueOf(fragmentType));
You try to restore your fragments in onCreate() of your activity but it seems that you haven't retained your fragments. They are thus destroyed together with the activity and when the activity is created again, new fragments are created.

Categories

Resources