Tie destinations to menu items, from fragment - java

This guide explains how to use NavigationComponent to make it easier to use the menu in the toolbar. All great when it comes to Activity but, how can I do the same thing with a fragment?
My project has 3 fragments: Login,Map,Settings.
The Login fragment has no toolbar, so the rest all have a custom toolbar.
The Map fragment has in the toolbar the menu that server to reach the Settings fragment.
This is how I added the toolbar in my Map fragment:
private void setupToolbar(View view) {
mNavController = Navigation.findNavController(view);
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(R.id.settingsFragment, R.id.mapsFragment).build();
NavigationUI.setupWithNavController(
mToolbar, mNavController, appBarConfiguration);
}
#Override
public boolean onOptionsItemSelected(#NonNull MenuItem item) {
return NavigationUI.onNavDestinationSelected(item, mNavController)
|| super.onOptionsItemSelected(item);
}
#Override
public void onCreateOptionsMenu(#NonNull Menu menu, #NonNull MenuInflater inflater) {
inflater.inflate(R.menu.main_menu, menu);
}
Obviously i call setHasOptions(true) inside the OnCreateView of the Map fragment.
The setUpToolbar method is called inside the OnViewCreate of the Map fragment.
This code allows me to show the toolbar inside the Map fragment but what it doesn't allow me to do is to reach (Using the navigation component) the settings fragment by clicking on the menu.
And, yes, the menu id is the same as the fragment name.
Solved
The call to the setSupportActionBar method in the onCreateView of the Map fragment was missing
#Override
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentMapsBinding.inflate(inflater, container, false);
((AppCompatActivity) requireActivity()).setSupportActionBar(binding.toolbar);
setHasOptionsMenu(true);
return binding.getRoot();
}

Related

Navigation component with bottom navigation in android [duplicate]

I have an Android app (Java) which uses the Navigation Component to set up a Bottom Navigation. The app consists of a single Activity (Main), everything else is loaded in fragments.
The idea is for the Splash Screen to launch and check if the user is logged in or not. If the user is logged in, then the home screen loads and the bottom navigation (previously hidden) becomes visible.
The bottom navigation consists of 3 tabs.
However, when the user signs in, it can be one of two types of users. If he's a subscriber he will get access to all sections of the app. However if the user is a visitor, he will get access to only two sections. In this case I want to have a different bottom navigation menu to be visible with only 2 tabs.
How can I replace the bottom navigation depending on the user type if the main bottom navigation has already been assigned in the main activity?
This is the code in the MainActivity:
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FragmentManager supportFragmentManager = getSupportFragmentManager();
NavHostFragment navHostFragment = (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(R.id.homeFragment, R.id.libraryFragment, R.id.searchFragment, R.id.myStuffFragment).build();
NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration);
BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);
NavigationUI.setupWithNavController(bottomNav, navController);
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
#Override
public void onDestinationChanged(#NonNull NavController controller, #NonNull NavDestination destination, #Nullable Bundle arguments) {
if (destination.getId() == R.id.splashScreenFragment || destination.getId() == R.id.welcomeFragment || destination.getId() == R.id.signInFragment) {
toolbar.setVisibility(View.GONE);
bottomNav.setVisibility(View.GONE);
} else if (destination.getId() == R.id.audioPlayerFragment) {
bottomNav.setVisibility(View.GONE);
} else {
toolbar.setVisibility(View.VISIBLE);
bottomNav.setVisibility(View.VISIBLE);
}
}
});
This is the code in the SplashScreenFragment (this is the startDestination in the nav_graph):
public class SplashScreenFragment extends Fragment {
private static final String TAG = "SplashScreenFragment";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_splash_screen, container, false);
return v;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
SharedPreferences getSharedData = this.getActivity().getSharedPreferences("AppTitle", Context.MODE_PRIVATE);
Boolean loggedIn = getSharedData.getBoolean("isUserLoggedIn", false);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (!loggedIn) {
Navigation.findNavController(view).navigate(R.id.action_splashScreenFragment_to_welcomeFragment);
} else {
Navigation.findNavController(view).navigate(R.id.action_splashScreenFragment_to_homeFragment);
}
}
}, 2000);
}
You can have the full menu items by default in the BottomNavigationView, so when a subscribed user logged in, then no change in the menu is required.
And if the user is not subscribed, then you can remove the needed items from the menu programmatically using removeItem()
So, in MainActivity add a condition :
if (notSubscribedUser) {
if (bottomNav.getMenu().findItem(R.id.my_item_id) != null)
bottomNavigationView.getMenu().removeItem(R.id.my_item_id);
}
And do the same for all menu item ids that you want to remove

Switching between fragments with button onClick but bottom navigation icon does not change

I have an app which has food menu and cart in the bottom navigation and used fragments for those. There is a button in the cart fragment which will show when the cart is empty. This is a 'Add Food' button because the cart is empty. This button takes the user to the menu fragment. That part of the code works, but my issue is that the icon in the bottom navigation bar is not changing from cart icon to menu icon.
public class CartFragment extends Fragment {
private LinearLayout emptyCartInfo;
private Button addFoodButton;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_cart, container, false);
setHasOptionsMenu(true);
//return inflater.inflate(R.layout.fragment_cart, container, false);
emptyCartInfo = view.findViewById(R.id.cart_empty_layout);
addFoodButton = view.findViewById(R.id.add_food_button);
addFoodButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Fragment menu = new MenuFragment();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, menu);
ft.commit();
}
});
return view;
}
}
The code above works fine on click of the button which takes me to menu fragment, however, the icon shown in the bottom navigation remains selected as cart and I need help as how to change this from within the fragment. When fragment switches from cart to menu, icon in the bottom navigation should also change which in my case does not. Need to do this from within the cart fragment under the onClick method.
In your onClick function, you should add a statement that turns off the focus from one MenuItem by changing the colour:
(In case that menu is already defined)
menu.findItem(R.id.main_page).setIconColor(Color.WHITE);
And the same thing in the second Fragment, while resuming it, change the colour to your "focused" colour.
Also, I'd like to suggest, just because I think it would be a better UX, changing the bottom navigation to a tabbed menu (TabbedLayout).
Then, just remove the button and you'll be able to swipe between Fragments.

Android: Tabs and searchbars

I have two tabs and each tabs has its own searchbar.
I bind the searchbar in onCreateOptionsMenu. However, the searchbars only work if I leave the screen once and return to the screen (meaning it needs one more lifecycle for the searchbars to react). I confirmed that onCreateOptionsMenu is indeed called two times at the time of the creation of the ViewPagerFragment.
I bind them like this:
MenuItem searchItem = menu.findItem(R.id.search);
searchItem.setVisible(true);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
...
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
...
return false;
}
});
I am guessing this bug is related to the tabs. How do implement a working searchbar with tabs (i.e. viewpager2)?
I call this on onCreateOptionsMenu:
public void onCreateOptionsMenu(#NonNull Menu menu, #NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
// Call the above...
}
The ViewPager hosting them looks like this:
private void init(View view) {
ViewPager2 viewPager2 = view.findViewById(R.id.view_pager_fragment_view_pager);
TabLayout tabLayout = view.findViewById(R.id.tab_layout_fragment_view_pager);
viewPager2.setUserInputEnabled(true);
viewPager2.setAdapter(new ViewPagerFragmentAdapter(ViewPagerFragment.this));
viewPager2.setOffscreenPageLimit(5);
new TabLayoutMediator
(tabLayout, viewPager2,
(tab, position) -> tab.setText(titles[position])).attach();
}
OP and I were communicating while we found a solution.
First each fragment was changed to
public class MergedItemsFragment extends Fragment implements SearchView.OnQueryTextListener {
/// ------------------------------
/// SearchView.OnQueryTextListener
#Override
public boolean onQueryTextSubmit(String query) {
// ...
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
// ...
return false;
}
/// --------
/// Fragment
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_merged_items, container, false);
}
#Override
public void onCreateOptionsMenu(#NonNull Menu menu, #NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
final MenuItem searchItem = menu.findItem(R.id.search);
final SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(this);
}
}
SearchView was inflated by using a menu.xml file within the App's Activity.
SearchView.OnQueryTextListener was implemented by each Fragment that needed access to the SearchView
Since onCreateOptionsMenu() is invoked each time a Fragment is created, or subsequently comes into view (e.g. Swipe); the SearchView's OnQueryTextListener is updated to the corresponding Fragment
Lastly, there was a line in OP's main Fragment containing the ViewPager2, which was removed: viewPager2.setOffscreenPageLimit(5); that caused each Fragment provided by the FragmentStateAdapter to instantiate and mutate the SearchView's OnQueryTextListener each time a Fragment was created.
Removing the line made sure that only the Fragment that was in view was bound to the Toolbar's SearchView.
If any more code is desired, i'd be happy to post what I have, and if I come up with a solution using viewPager2.setOffscreenPageLimit(5); i.e. caching, i'll post that as well

Is there a way on the new navigation component on back pressed to restore the state on recyclerview

What I'm using:
MVP
Navigation Component
Single Activity
Fragments
RecyclerView
Realm
Problem:
I have a MainActivity that hosts all the fragments for the application flow.
There's one fragment that has a recyclerview, the more I scroll the more API requests are made. Whenever I press one item to navigate to a different fragment and press back to return to the list, the list only has a small piece of the previous loaded recyclerview, I want everything.
Is it possible to do this with the navigation component?
Can I prevent onCreateView being called whenever I press back?
I've tried to save the View in a static variable, have a onSaveInstance (but it never gets called)
I'm using navigation this way:
#Override
public void onClick(View view) {
Navigation.findNavController(view)
.navigate(R.id.action_tripsFragment_to_tripDetailsFragment, bundle);
}
MainFragment:
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater,
#Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.trips_fragment, container, false);
mPresenter = new TripsFragmentPresenterImpl(this);
ButterKnife.bind(this, v);
navController = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
mLinearLayoutManager = new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false);
mAdapter = new PaginationAdapter(getContext());
mAdapter.setOnItemClickListener(onItemClickListener);
mPresenter.setLoadingTripsProgressBar(true);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addOnScrollListener(new PaginationScrollListener(mLinearLayoutManager) {
#Override
protected void loadMoreItems() {
isLoading = true;
currentPage += 1;
loadNextPage();
}
#Override
public int getTotalPageCount() {
return currentPage;
}
#Override
public boolean isLoading() {
return isLoading;
}
});
loadFirstPage();
return v;
}
What I expect:
Whenever I press back from TripDetails to Trips I want to get my recyclerview as it was, with the previous loaded items at the correct position.
Current results:
Whenever I press back from TripDetails to Trips the recyclerview cuts the items to only the items that are visible, meaning that we'll lose items previous loaded in previous pages.
For future reference I've managed to solve the issue by creating a ModelView to keep the data independent of the lifecycle of the fragment

Viewpager won't work when pressing back from a toolbar

I tried to phrase the title as I can, but Basically inside the HomeActivity I have a custom menu with a bunch for fragments.! One of the them is HomeFragment which contains a Tablelayout of 2 tabs with a viewpager..!
Everything is working correctly.! but inside the menu fragment, when the user clicks the back button on the toolbar is to return to the HomeFragment.
A fragment replacement method will do the trick which I already used it to replace between the fragments when choosing from the menu !?
But in this case, the HomeFragment opens yet the Tablayout isn't responsive! it feels like the the fragment isn't created correctly!?
I tried using a new intent of the same activity which opens HomeFragment by default, and it opens it but with the same problem..!
The Problem
The Code
HomeFragment
// code..
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
InitViews(rootView);
TabLayoutAdapter adapter = new TabLayoutAdapter(getFragmentManager());
pager.setAdapter(adapter);
tabLayout.setupWithViewPager(pager);
return rootView;
}
// code..
Menu Fragment
// code..
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.to_home:
replaceFragment(new HomeFragment());
// tried this and it's the same problem
// startActivity(new Intent(getActivity(), HomeActivity.class).setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void replaceFragment(Fragment fragment) {
android.support.v4.app.FragmentTransaction t = getFragmentManager().beginTransaction();
t.replace(R.id.Container, fragment);
t.commit();
}
// code..
Looking at the code I think you have to use getChildFragmentManager() instead of getFragmentManager() and use it for fragment Transaction.
The definition of getChildFragmentManager() is:
Return a private FragmentManager for placing and managing Fragments inside of this Fragment.
And the definition of getFragmentManager() is:
Return the FragmentManager for interacting with fragments associated with this fragment's activity.
Replace this line of code having getFragmentManager() by getChildFragmentManager()
TabLayoutAdapter adapter = new TabLayoutAdapter(getChildFragmentManager());
Also in the menu Fragment
android.support.v4.app.FragmentTransaction t = getChildFragmentManager().beginTransaction();

Categories