I'm currently creating functionality to change Fragments using bottom navigation. But instead of destroying them and recreating them, I want to simply hide and show the fragments to preserve the member variables.
I've tried replace(), hide() and show() but haven't managed the get it right, I'm getting animation errors which I'm not able to track down.
I also can't find an example of switching fragments within an AppCompatActivity.
public class MainActivity extends AppCompatActivity {
PassengerFragment passengerFragment;
DriverFragment driverFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView bottomNav = findViewById(R.id.bottom_navigation);
bottomNav.setOnNavigationItemSelectedListener(navListener);
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
new PassengerFragment()).commit();
}
// this handles the bottom navigation so when you click an item it changes fragment
private BottomNavigationView.OnNavigationItemSelectedListener navListener =
new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
Fragment selectedFragment = null;
switch (menuItem.getItemId()) {
case R.id.nav_passenger:
selectedFragment = passengerFragment;
break;
case R.id.nav_driver:
if (driverFragment==null) {
selectedFragment = new DriverFragment();
}
else {
selectedFragment = driverFragment;
}
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
selectedFragment).commit(); <---- line 54
return true;
}
};
}
ERROR
Java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
at android.support.v4.app.BackStackRecord.doAddOp(BackStackRecord.java:396)
at android.support.v4.app.BackStackRecord.replace(BackStackRecord.java:444)
at android.support.v4.app.BackStackRecord.replace(BackStackRecord.java:434)
at je.digital.kevin_pickmeup.MainActivity$1.onNavigationItemSelected(MainActivity.java:54)
That is because you are trying do replace with Fragment which are null. You have created passengerFragment and driverFragment as fields or global variables and than you are trying to use them but in some cases especially passengerFragment is null. If you are trying to keep the data in Fragments even they are replaced you can use addToBackStack but you will need to manage not to putting too much to back stack probably by checking getSupportFragmentManager().getBackStackEntryCount() and removing if there are more than 1 using popBackStack() or save the data in Bundle on onDestroyView and restore that data in onCreateView:
just needed better if statements to handle all the cases.
private BottomNavigationView.OnNavigationItemSelectedListener navListener =
new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
Fragment passengerFragment = getSupportFragmentManager().findFragmentByTag("A");
Fragment driverFragment = getSupportFragmentManager().findFragmentByTag("B");
switch (menuItem.getItemId()) {
case R.id.nav_passenger:
if(passengerFragment.isHidden()) {
getSupportFragmentManager().beginTransaction().show(passengerFragment).commit();
getSupportFragmentManager().beginTransaction().hide(driverFragment).commit();
}
break;
case R.id.nav_driver:
if(driverFragment==null) {
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, new DriverFragment(),"B").commit();
getSupportFragmentManager().beginTransaction().hide(passengerFragment).commit();
}
else {
if(driverFragment.isHidden()) {
getSupportFragmentManager().beginTransaction().show(driverFragment).commit();
getSupportFragmentManager().beginTransaction().hide(passengerFragment).commit();
}
}
break;
}
return true;
}
};
Related
I have an interface like so:
public interface IStartCamera {
void startScanning();
}
I have a fragment that implements this interface and in this fragment I have an override for this like so:
#Override
public void startScanning() {
Intent intent = new Intent(getActivity(), QrScanAcitivity.class);
startActivityForResult(intent, SQ_SCANNING_RESULT);
startActivity(new Intent(getActivity().getApplicationContext(), QrScanAcitivity.class));
}
In my activity I create an instance of this fragment when a navigation button is clicked like so:
private IStartCamera iStartCameraInFragment;
public void setStartCameraInFragment(IStartCamera IStartCameraInFragment) {
this.iStartCameraInFragment = IStartCameraInFragment;
}
fragment = new ExitFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
fragment).commit();
navigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
fragment = null;
switch (item.getItemId()){
case R.id.item_manual_scanning:
fragment = new ExitFragment();
setStartCameraInFragment(fragment); // <=== I HAVE THE PROBLEM HERE
break;
case R.id.item_reload:
fragment = new ReloadFragment();
break;
case R.id.item_add_entry_info:
fragment = new AddEntryInfoFragment();
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
fragment).commit();
return true;
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.fabOpenCamera:
iStartCameraInFragment.startScanning();
break;
}
}
when I the setStartCameraInFragment(fragment) when instantiating the fragment, I get a wrong requirement since it is expecting an IStartCamera and not a fragment even though that fragment implements IStartCamera.
First of all, don't instantiate the Fragment by using the constructor. It's a good practice to use a static method, usually named 'newInstance' for instantiating Fragments, because if you'd like to pass any parameter to it later on, you will be able to do it easily.
Regarding your question, you get the error because you are trying to call the 'setStartCameraInFragment' method by using a Fragment object, while the method requires an instance of IStartCamera. What you should do (if you want to stick with this logic) is check if the Fragment is an instance of the IStartCamera interface, and after that you would be able to use it as such. Here's an example:
if (fragment instanceof IStartCamera)
setStartCameraInFragment((IStartCamera)fragment); // --> Here the fragment can be considered as an instance of IStartCamera and this will work
I have an Activity with a BottomNavigationView, which consists of 4 Fragments, the Fragments reload whenever I change tabs, this is my Activity's code
public class MainHomeActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_home);
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation);
bottomNavigationView.setOnNavigationItemSelectedListener(navigationItemSelectedListener);
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new HomeFragment()).commit();
}
private BottomNavigationView.OnNavigationItemSelectedListener navigationItemSelectedListener = new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull #NotNull MenuItem item) {
Fragment selectedFragment = null;
switch (item.getItemId()){
case R.id.nav_home:
selectedFragment = new HomeFragment();
break;
case R.id.nav_list:
selectedFragment = new UsersFragment();
break;
case R.id.nav_profile:
selectedFragment = new ProfileFragment();
break;
case R.id.nav_settings:
selectedFragment = new SettingsFragment();
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, selectedFragment).commit();
return true;
}
};
}
In the Fragments im loading data from parse
I know that the mistake im doing is that I'm creating a new instance of the Fragment whenever I switch tabs, but I do not how to fix it or where to start from
I saw some people saying that a ViewPagerAdapter should be used in this case but i cant manage to find a place where its explained properly.
Any assistance would be very appreciated!
Here's an article which describes your case perfectly and in detail.
Basically, it creates a fragment for each tab in memory, and saves them as a local variable in the activity:
final Fragment fragment1 = new HomeFragment();
final Fragment fragment2 = new DashboardFragment();
final Fragment fragment3 = new NotificationsFragment();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment1;
You add all 3 fragments to the manager, but hide 2 of them, so only 1 will be visible:
fm.beginTransaction().add(R.id.main_container, fragment3, "3").hide(fragment3).commit();
fm.beginTransaction().add(R.id.main_container, fragment2, "2").hide(fragment2).commit();
fm.beginTransaction().add(R.id.main_container,fragment1, "1").commit();
You implement the OnNavigationItemSelectedListener of the BottomNavigationView, check which item was pressed, and then show that fragment while hiding the previous:
case R.id.navigation_dashboard:
fm.beginTransaction().hide(active).show(fragment2).commit();
active = fragment2;
Instead of replacing the fragment, use add/remove and create a mechanism for adding and removing fragment stack, also it is not recommended but for the sake of question you can create a singleton fragment, instead of using a new Keyword everything tab is changed, along with that you can have a look at pageOffSet
companion object{
private lateinit var INSTANCE?: HomeFragment() = null
}
fun getInstance(): HomeFragment(){
if(INSTANCE == null){
INSTANCE = HomeFragment()
}
return INSTANCE
}
As we know the ViewPager recreates the fragments when we switch the pages with our BottomNavigationView. I know how to prevent this, use one of these 2 Options:
As i can see your amount of fragments is small and fixed, add in your onCreate() of your
MainHomeActivity.java:
mViewPager = (ViewPager)findViewById(R.id.pager);
mViewPager.setOffscreenPageLimit(limit); //limit is a fixed integer
Use a FragmentPagerAdapter as part of your ViewPager. (example provided in link)
UPDATE
In #moalzoabi's case the 2nd option worked. Follow along this post.
I am developing a car rental app and right now I have 3 fragments in my app (Home - where all the cars are displayed from a recyclerview , Orders - where user's bookings are shown , Profile - user profile). I can switch beetween these 3 fragments from my bottom navigationbar.
Now I created onclick listeners for every car on home page. When I click on a car it sends me to a new activity "activity_car_detail.xml" which contains specific information about the selected car. My question is: how can I make the navigation bar to appear on this Car Detail activity? Should I use fragment instead of activity or what should I do?
This is the code for the Main Activity which contains the bottom navigation bar:
public class MainActivity extends AppCompatActivity {
public static ProgressBar progressBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView bottomNav = findViewById(R.id.bottom_navigation);
bottomNav.setOnNavigationItemSelectedListener(navListener);
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new HomeFragment()).commit();
}
private BottomNavigationView.OnNavigationItemSelectedListener navListener = new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment selectedFragment = null;
switch (item.getItemId()) {
case R.id.nav_home:
selectedFragment = new HomeFragment();
break;
case R.id.nav_orders:
selectedFragment = new OrdersFragment();
break;
case R.id.nav_profile:
selectedFragment = new ProfileFragment();
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, selectedFragment).commit();
return true;
}
};
}
I suggest using the Navigation component instead of Fragment transactions. Also this will fix your issue and it will remove boilerplate code. Read more about it's documentation here.
I am new to using fragments, I am trying to execute a fragment method from my main activity, the method compares a textview and then deletes cookies and loads a website.
This is what I have:
In MainActivity:
NavigatorFragment navigator = new NavigatorFragment();
navigator.refresh();
In Fragment:
public void refresh(){
if (!textView.getText().toString().equals("Click HERE")){
cookieManager.removeAllCookie();
cookieManager.removeSessionCookie();
myWebView.loadUrl(URL);
}
}
the result:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.CharSequence android.widget.TextView.getText()' on a null object reference
EDIT
Sorry but forget to mention that although the fragment is started the same thing happens, I previously used this code to prevent my fragments from restarting: link So when I press item4 of my BottomNavigationView it opens the fragment, in onCreateView:
textView = root.findViewById(R.id.textView);
when I press it again (without having changed fragment) is when I want the method to be executed
I repeat, when I press item 4 BottomNavigationView for the first time, it opens Fragment 4 and loads its elements, when I press item 4 a second time (without having changed the fragment) that's when I want the method to run
Main Activity:
public class MainActivity extends AppCompatActivity {
/*
Fragment MyFragment;
private NavController navController;
*/
NavigatorFragment navigator = new NavigatorFragment();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.nav_view);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
replace_fragment(new HomeFragment());
}
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.nav_host_fragment, 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.navigation_home:
replace_fragment(new HomeFragment());
if (!getSupportActionBar().isShowing()){
showBar();
}
return true;
case R.id.navigation_schedule:
replace_fragment(new ScheduleFragment());
if (!getSupportActionBar().isShowing()){
showBar();
}
return true;
case R.id.navigation_calculators:
replace_fragment(new CalculatorsFragment());
if (!getSupportActionBar().isShowing()){
showBar();
}
return true;
case R.id.navigation_navigator:
replace_fragment(new NavigatorFragment());
if (!getSupportActionBar().isShowing()){
navigator.refresh();//**************PROBLEM HERE******************************************
}
hideBar();
return true;
}
return false;
}
};
public void hideBar(){
if (Build.VERSION.SDK_INT >= 19) {
// Call some material design APIs here
Objects.requireNonNull(getSupportActionBar()).hide();
}
else {
// Implement this feature without material design
getSupportActionBar().hide();
}
}
public void showBar(){
if (Build.VERSION.SDK_INT >= 19) {
// Call some material design APIs here
Objects.requireNonNull(getSupportActionBar()).show();
}
else {
// Implement this feature without material design
getSupportActionBar().show();
}
}
}
**the refresh method works if run from the fragment code
When instantiating a fragment instance using new NavigatorFragment() that doesn't involve that the fragment life cycle starts away. Meaning that the fragment isn't created, started, resumed, and shown on the screen.. So, accessing fragment's views at this moment will return NullPointerException.
So, what you need to do is to have a placeholder for the fragment in the activity layout, and start fragment transaction by adding or replacing fragments in this placeholder, which makes the fragment passes by its all life cycle callback methods, and to give it a chance to create its view (layout) on the screen within the onCreateView() lifecycle callback, where you can just allowed to access the underlying TextView or any other view in this fragment, and use their methods like in your case textView.getText() subsequently without NullPointerException.
Here you can find how to use a fragment the right way.
In my Application there are four ButtonNavigationView tabs with fragments, i want 2 of them to refresh every time i click on them and the other 2 to not refresh every time i click on them , how can i get that?
Home Activity
final Fragment factorsFragment = new FactorsFragment();
final Fragment newFactorFragment = new NewFactorFragment();
final Fragment productsFragment = new ProductsFragment();
final Fragment checkList = new CheckList();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = newFactorFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
fm.beginTransaction().add(R.id.fragment_container, productsFragment, "4").hide(productsFragment).commit();
fm.beginTransaction().add(R.id.fragment_container, factorsFragment, "3").hide(factorsFragment).commit();
fm.beginTransaction().add(R.id.fragment_container, checkList, "2").hide(checkList).commit();
fm.beginTransaction().add(R.id.fragment_container, newFactorFragment, "1").commit();
SQLiteStudioService.instance().start(this);
}
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
fm.beginTransaction().hide(active).show(factorsFragment).commit();
active = factorsFragment;
return true;
case R.id.navigation_dashboard:
fm.beginTransaction().hide(active).show(newFactorFragment).commit();
active = newFactorFragment;
return true;
case R.id.navigation_notifications:
fm.beginTransaction().hide(active).show(productsFragment).commit();
active = productsFragment;
return true;
case R.id.navigation_checklist:
fm.beginTransaction().hide(active).show(checkList).commit();
active = checkList;
return true;
}
return false;
}
};
i want factorFragment and productFragment to refresh without adding a swiperefreshlayout or custom refresh button
my problem is all of my fragments don't refresh on tab change, because i am just making them show and hide , but i want 2 of them to refresh.
Call all the functions of your factorFragment and productFragment in onResume of the respective fragments. Changes will reflect as you are calling the methods again in onResume.
Fragment has offset. So if you are loading one fragment in activity the next few(depends on what you set or the default offset) fragments also get initialized. That's why it will be wise to recall your functions in onResume.