This is more of a Java question (having some trouble understanding how should the inheritance be set). I'm trying to add a side menu to my application (which works ok). The class signature of my menu activity is:
public class MenuActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener
Once the user logged in, the first window he see is the main dashboard. The dashboard activity extends the menu activity in order to have the side menu:
public class DashboardActivity extends MenuActivity
For navigating to other activities, I implemented the onNavigationItemSelected method to select the intent to load (The method is located in the MenuActivity):
#Override
public boolean onOptionsItemSelected(#NonNull MenuItem item) {
if (toggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Intent intent = null;
switch (item.getItemId()) {
case R.id.lay_dashboard:
intent = new Intent(this, DashboardActivity.class);
//intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
break;
case R.id.lay_settings:
intent = new Intent(this, SettingsActivity.class);
//intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
break;
case R.id.lay_contacts:
startActivity(new Intent(this, ContactsActivity.class));
break;
case R.id.lay_about:
startActivity(new Intent(this, AboutActivity.class));
break;
case R.id.lay_logout:
mAuth.signOut();
intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
break;
}
if (intent != null) {
startActivity(intent);
}
drawerLayout.closeDrawer(GravityCompat.START);
return true;
}
As you can see I commented the intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); as part of debugging the issue. I want the navigation process not to start a new screen, rather move to it.
Also the OnCreate method in the MenuActivity looks like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
}
drawerLayout = findViewById(R.id.drawer_layout);
navigationView = findViewById(R.id.lay_nav_view);
toggle = new ActionBarDrawerToggle(this, drawerLayout,
R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawerLayout.addDrawerListener(toggle);
toggle.syncState();
navigationView.setNavigationItemSelectedListener(this);
mAuth = FirebaseAuth.getInstance();
logged_user = mAuth.getCurrentUser();
if (logged_user != null) {
// Get data from firebase in order to set the avatar
// and user name in the menu
}
}
protected void setLayoutView(int layout) {
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater != null) {
View contentView = inflater.inflate(layout, null, false);
drawerLayout.addView(contentView, 0);
}
}
Each one of the activitise extends the MenuActivity and does the following in their onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setLayoutView(R.layout.activity_about); // My method which loads UI
// other code
}
So, as you can see, each one of the activities is extending the MenuActivity. As I understand, this means that everytime I navigate from one activity to another, the OnCreate of MenuActivity is being executed again. I fetch data from the Firebase in that method in order to set the username and the avatar in the top of the menu. So everytime I navigate from one activity to another, it will fetch again and again. How can I make the menu to load only once in a logged mode ? Also, thanks to all of you that have read this topic (I know it's long).
EDIT: To make it more clear, I'm adding the hierarchy of my code:
Maybe the solution for this issue is to make the MenuActivity a singleton class?
All I want to have a menu on each one of the activities and fetching the data only once.
Hello you could use NavigationDrawer with fragments. That will be the most optimal solution and easy, You could also use Navigation Component from Jetpack to change the destinations.
you could use something like this,the following layout uses a DrawerLayout with two child views: a NavHostFragment to contain the main content and a NavigationView for the contents of the navigation drawer.
<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- Layout to contain contents of main body of screen (drawer will slide over this) -->
<fragment
android:name="androidx.navigation.fragment.NavHostFragment"
android:id="#+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />
Next, connect the DrawerLayout to your navigation graph by passing it to AppBarConfiguration, as shown in the following example:
val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
Next, in your main activity class, call setupWithNavController() from your main activity's onCreate() method, as shown below:
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)
...
val navController = findNavController(R.id.nav_host_fragment)
findViewById<NavigationView>(R.id.nav_view)
.setupWithNavController(navController)
}
Thats it , then you can use the navContoller to navigate to any fragment,
and rest of what you need is a navigation graph and actions set on destinations to navigate between fragments this way :
navController.navigate(R.id.actionSettingFragment_to_ContactsFrament)
Hope this is kinda helpful. I have a blog written on this so please check it if it is useful to you.
There's more than one way to resolve your issue. I'm going to provide different approaches towards a solution.
Repository
To reuse the data you fetch from Firebase, it needs to be persisted or cached within your application. In general you'd implement an repository abstraction around the Firebase. This repository could be a singleton and just cache the data in memory directly. You only have to make sure to cleanup the cache when necessary. The MenuActivity mustn't be a singlton.
Intent extras
Instead of storing the data in memory, you could provide it as Intent extras. Whenever the data is present, just skip the loading and reuse the data provided.
Fragments
Instead of a stack of activities, you could use fragments. So you'll have a single activity and just replace the fragments within it. With this approach you'll just define a single drawer layout you'll automatically use with every fragment. This approach enables you to use Navigation components as well.
Related
I built a customizable navigation drawer from scratch(didn't make use of the default drawer provided by Android Studio). In my weather app's navigation bar menu https://i.stack.imgur.com/SIjdx.jpg, whenever I select an option on the menu(say settings), it displays the contents of the option along with the bottom navigation view and my Activity's Toolbar contents which comprises of the nav hamburger icon, the edittext and the search button(the activity hosting my 3 fragments) which spoils the app and makes it look very ugly i.e. https://i.stack.imgur.com/gxj5n.jpg (From that screenshot, the entire content should be empty if implemented well). The case is the same for the other bar menu options. All I want is an empty space to work on, I want the app to only display the navigation bar contents without the rest. Example; https://i.stack.imgur.com/3Jtga.png Please how should I do this?
The view of the Navigation Menu is controlled by this code(on line 185):
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.settings_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Settings()).commit();
break;
case R.id.ads_upgrade_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Upgrade()).commit();
break;
case R.id.privacy_policy_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Privacy_Policy()).commit();
break;
}
drawer.closeDrawer(GravityCompat.START);
return true;
}
"fragment" there represents that I'm currently using my fragment's container view on my activity to display the Nav menu contents which I know is wrong for sure, so what should I use in replace? I lack strong experience as it's my first time building an app and I've tirelessly spent 3 hours on my own trying to figure out the issue which proved abortive.
Here is my Activity code:
public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private DrawerLayout drawer;
// Last update time, click sound, search button, search panel.
TextView timeField;
MediaPlayer player;
ImageView Search;
EditText textfield;
// For scheduling background image change(using constraint layout, start counting from dubai, down to statue of liberty.
ConstraintLayout constraintLayout;
public static int count = 0;
int[] drawable = new int[]{R.drawable.dubai, R.drawable.norway, R.drawable.eiffel_tower, R.drawable.hong_kong, R.drawable.statue_of_liberty,
R.drawable.beijing, R.drawable.chicago, R.drawable.colombia, R.drawable.vienna,R.drawable.tokyo};
Timer _t;
private WeatherDataViewModel viewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
// use home activity layout.
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Allow activity to make use of the toolbar
drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
viewModel = new ViewModelProvider(this).get(WeatherDataViewModel.class);
// Trigger action to open & close navigation drawer
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar
, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
timeField = findViewById(R.id.textView9);
Search = findViewById(R.id.imageView4);
textfield = findViewById(R.id.textfield);
// find the id's of specific variables.
BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
// host 3 fragments along with bottom navigation.
final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
assert navHostFragment != null;
final NavController navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(bottomNavigationView, navController);
// Make hourly & daily tab unusable
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
}
return false;
});
navController.addOnDestinationChangedListener((controller, destination, arguments) -> navController.popBackStack(destination.getId(), false));
// For scheduling background image change
constraintLayout = findViewById(R.id.layout);
constraintLayout.setBackgroundResource(R.drawable.dubai);
_t = new Timer();
_t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
// run on ui thread
runOnUiThread(() -> {
if (count < drawable.length) {
constraintLayout.setBackgroundResource(drawable[count]);
count = (count + 1) % drawable.length;
}
});
}
}, 5000, 5000);
Search.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// make click sound when search button is clicked.
player = MediaPlayer.create(HomeActivity.this, R.raw.click);
player.start();
getWeatherData(textfield.getText().toString().trim());
// make use of some fragment's data
Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
if (currentFragment instanceof FirstFragment) {
FirstFragment firstFragment = (FirstFragment) currentFragment;
firstFragment.getWeatherData(textfield.getText().toString().trim());
} else if (currentFragment instanceof SecondFragment) {
SecondFragment secondFragment = (SecondFragment) currentFragment;
secondFragment.getWeatherData(textfield.getText().toString().trim());
} else if (currentFragment instanceof ThirdFragment) {
ThirdFragment thirdFragment = (ThirdFragment) currentFragment;
thirdFragment.getWeatherData(textfield.getText().toString().trim());
}
}
private void getWeatherData(String name) {
ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);
Call<Example> call = apiInterface.getWeatherData(name);
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(#NonNull Call<Example> call, #NonNull Response<Example> response) {
try {
assert response.body() != null;
timeField.setVisibility(View.VISIBLE);
timeField.setText("First Updated:" + " " + response.body().getDt());
} catch (Exception e) {
timeField.setVisibility(View.GONE);
timeField.setText("First Updated: Unknown");
Log.e("TAG", "No City found");
Toast.makeText(HomeActivity.this, "No City found", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(#NotNull Call<Example> call, #NotNull Throwable t) {
t.printStackTrace();
}
});
}
});
}
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.settings_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Settings()).commit();
break;
case R.id.ads_upgrade_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Upgrade()).commit();
break;
case R.id.privacy_policy_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Privacy_Policy()).commit();
break;
}
drawer.closeDrawer(GravityCompat.START);
return true;
}
#Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
// Open/close drawer animation
}
}
}
In case you require any other code to look into the issue, please let me know. I'm just trying to avoid posting too much
EDIT:
My old bottomtabs nav graph:
<?xml version="1.0" encoding="utf-8"?>
<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/my_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.viz.lightweatherforecast.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" />
<fragment
android:id="#+id/secondFragment"
android:name="com.viz.lightpreciseweatherforecast.SecondFragment"
android:label="fragment_second"
tools:layout="#layout/fragment_second" />
<fragment
android:id="#+id/thirdFragment"
android:name="com.viz.lightpreciseweatherforecast.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third" />
</navigation>
My new nav bar graph:
<?xml version="1.0" encoding="utf-8"?>
<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/bar_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.viz.lightweatherforecast.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" />
<fragment
android:id="#+id/settings_id"
android:name="com.viz.lightweatherforecast.Settings"
android:label="#string/settings"
tools:layout="#layout/settings" />
<fragment
android:id="#+id/ads_upgrade_id"
android:name="com.viz.lightweatherforecast.Upgrade"
android:label="#string/upgrade_to_remove_ads"
tools:layout="#layout/upgrade" />
<fragment
android:id="#+id/privacy_policy_id"
android:name="com.viz.lightweatherforecast.Privacy_Policy"
android:label="#string/privacy_policy"
tools:layout="#layout/privacy_policy"/>
</navigation>
You are using navigation architecture components, so the navController is the one that should control fragment transactions, you are doing that right with BottomNavigationView.
But within the navDrawer you are doing the transaction through the supportFragmentManager which should be done through the navController instead as both handle the navigation differently.
whenever I select an option on the menu(say settings), it displays the contents of the option along with the bottom navigation view
That is because the BottomNavView is a part of the activity, and you need to move it to a fragment; this requires to change the navigation design of your app; to do that change your app navigation like the below:
Main navigation:
<navigation
..... >
<fragment
android:name="......HomeFragment"/>
<fragment
android:name="......SettingFragment"/>
<fragment
android:name="......AdsUpgradeFragment"/>
<fragment
android:name="......PrivacyPolicyFragment"/>
</navigation>
The HomeFragment is the fragment that should hold the BottomNaviagtionView instead of the activity; and when you navigate to the SettingFragment, the navConroller will replace the entire fragment in the navHostFragment, and therefore the BottomNaviagtionView won't be shown.
my Activity's Toolbar contents which comprises of the nav hamburger
icon, the edittext and the search button(the activity hosting my 3
fragments) which spoils the app and makes it look very ugly
Unlike the BottomNaviagtionView, you can't do that with your toolBar that is used as the supportActionBar, because setting supportActionBar more than once in order to change its look; will duplicates it; so you have to accept a single toolbar; but instead you can hide/show the layout that holds the search button & the EditText whenever the destination changes:
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
LinearLayout searchBar = findViewById(R.id.searchbar); // change searchbar according to the layout id that holds the search button and the EditText
if (destination.getId() == R.id.nav_home) {
searchBar.setVisibility(View.VISIBLE);
} else {
searchBar.setVisibility(View.GONE);
}
});
and yes they currently exit the app when clicking back
To exit the app whenever, the bottom back button is pressed in any fragment use OnBackPressedDispatcher() within onCreateView() of those fragment (in your case SettingFragment, PrivacyPolicyFragment, & AdsUpgradeFragment):
And make sure that appBarConfiguration doesn't reference those fragments so, that the UP button can be shown instead of the burger.
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
// Exit the app when back is pressed
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
requireActivity().finishAndRemoveTask();
else requireActivity().finish();
}
});
Also, make sure that your setup the DrawerLayout & its navView in the navController with:
NavigationView navView = findViewById(....);
appBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home) // remove up button from all these fragments >> Keep the up/back button in R.id.settings_id, R.id.settings_id, ads_upgrade_id, privacy_policy_id
.setOpenableLayout(drawer)
.build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
And to make the Home fragment hidden by default in the navDrawer:
navView.getMenu().findItem(R.id.nav_home).setVisible(false); // adjust R.id.nav_home to yours
UPDATE
I currently have a single nav graph. The firstFragment represents the
today, 2nd - hourly and 3rd
I'm using is for my other weather tabs but the one you're suggesting
is for the navbar menu, should I replace yours with mine or just
create a new one for your suggestion?
You should use two navGraphs, the first is for the main navigation which I'd suggested; and the second is for the BottomNavigationView navigation which you already use; that is because we transferred the BottomNavigationView from the activity layout to the main/home fragment layout; and it' recommended the BottomNavigationView should have a separate navGraph;
So, you now need two FragmentContainerView; the first is in the activity layout which reference the navGraph provided in this answer, and the second is in the home fragment layout that references your original navGraph of the BottomNavigationView.
Sample:
I think I understood your problem very well (if I am not wrong). Thing is that what behavior you are getting right now is the normal scenario. You are using the same host activity to host both Navigation Drawer and Bottom Navigation fragments thus when you tried to navigate to another fragment from the same host the presence of the host's direct child view Bottom NavBar is showing. I think you can solve this problem in a few different but pretty simple logical ways.
(Not recommended) Using a different Activity for the Settings page. Just like startActivity(this, <some intent>). But in this way, you will end up creating lots of individual activities.
(Recommended) Using a common Navigation Host activity for navigating to independent pages/fragments. Like: Settings, Contact Us, etc. You can add different fragments into the common_nav_graph and set some actions with or without arguments for navigation. Just add the common_nav_graph as a nested graph inside your current one and set a simple argumented action. The arguments will help you to navigate desired pages/fragments without showing the bottom navigation bar.
Simply hide Bottom navigation when navigating to some fragments from your drawer.
I have an Activity that contains a BottomNavigationView, and this bottomnav helps the activity to display three fragments. These fragments load well, and I use an AsyncTask to do every heavy operation, while in the UI thread, I show a ProgressBar until everything loads.
There is a weird behaviour with my fragment: The first time I load the fragment it takes some time to actually display it, instead of displaying it instantly with a progressbar.
This thing only happens the first time, and only in this fragment.
The fragment code only contains this:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
new LoadData(getView(), getContext()).execute();
}
private class LoadData extends AsyncTask<Void, Void, Void> {
private View v;
private Context context;
public LoadData(View v, Context context) {
items = new ArrayList<>();
this.v = v;
this.context = context;
}
#Override
protected Void doInBackground(Void... voids) {
setItems(context); //Heavy operation
adapter = new DashAdapter(items, context);
return null;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
//shows progressbar
progress = v.findViewById(R.id.DFProgress);
progress.setVisibility(View.VISIBLE);
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
setPager();
//sets viewPager and hides progressbar
progress.setVisibility(View.GONE);
}
}
In the gif below, if you look at the bottomnavigationview at the bottom, you can see that it takes time to display the fragment. But after trying to load the fragment a second time, it loads as expected.
How could I make the fragment to load the right way?
I had the same problem. I have two options.
Use postdelay when you call LoadData or
First add all fragments with manually. You manage navigationItemSelected yourself.
Like this:
val firstFragment: Fragment = FirstFragment()
val secondFragment: Fragment = SecondFragment()
val thirdFragment: Fragment = ThirdFragment()
val navView: BottomNavigationView = findViewById(R.id.nav_view)
var active = firstFragment
fm.beginTransaction().add(R.id.nav_host_fragment, thirdFragment, "3").hide(thirdFragment).commit()
fm.beginTransaction().add(R.id.nav_host_fragment, secondFragment, "2").hide(secondFragment).commit()
fm.beginTransaction().add(R.id.nav_host_fragment, firstFragment, "1").commit()
navView.setOnNavigationItemReselectedListener { }
navView.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_first -> {
fm.beginTransaction().hide(active).show(firstFragment).commit()
active = firstFragment
}
R.id.navigation_second -> {
fm.beginTransaction().hide(active).show(secondFragment).commit()
active = secondFragment
}
R.id.navigation_third -> {
fm.beginTransaction().hide(active).show(thirdFragment).commit()
active = thirdFragment
}
}
true
}
And remove these lines in your nav_host_fragment:
app:defaultNavHost="true"
app:navGraph="#navigation/mobile_navigation"
you can use jetpack navigation for simple bottombar navigation
Simple Bottom Navigation with Jetpack Navigation:
Let’s start by including the Jetpack Navigation library in your apps by adding these lines in app’s build.gradle file:
def nav_version = "2.1.0"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
We start by creating a simple bottom navigation flow first. For that, you need to do first add NavHostFragment in your single activity layout file. Add this in the activity_main.xml file inside the FrameLayout tag.
<fragment
android:id="#+id/fragNavHost"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/bottom_nav_graph" />
You will see an error saying “Cannot resolve symbol #navigation/bottom_nav_graph .”
<?xml version="1.0" encoding="utf-8"?>
<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/bottom_nav_graph.xml"
app:startDestination="#id/homeFragment2">
<fragment
android:id="#+id/homeFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.HomeFragment"
android:label="fragment_home"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/searchFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.SearchFragment"
android:label="fragment_search"
tools:layout="#layout/fragment_search" />
<fragment
android:id="#+id/notificationsFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.NotificationsFragment"
android:label="fragment_notifications"
tools:layout="#layout/fragment_notifications" />
<fragment
android:id="#+id/profileFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.ProfileFragment"
android:label="fragment_profile"
tools:layout="#layout/fragment_profile" />
</navigation>
Its time for add some code in our activity class. Open MainActivity.kt file, and create a method setupViews() in it. Call this in onCreate() of the activity. Add these lines in the setupVeiws() method.
fun setupViews()
{
// Finding the Navigation Controller
var navController = findNavController(R.id.fragNavHost)
// Setting Navigation Controller with the BottomNavigationView
bottomNavView.setupWithNavController(navController)
// Setting Up ActionBar with Navigation Controller
// Pass the IDs of top-level destinations in AppBarConfiguration
var appBarConfiguration = AppBarConfiguration(
topLevelDestinationIds = setOf (
R.id.homeFragment,
R.id.searchFragment,
R.id.notificationsFragment,
R.id.profileFragment
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
}
When I tried #Kasım Özdemir 's answer, Each time the activity is launched, There was an initial ripple effect on the first item in bottom navigation view.(because I am using material bottom navigation view, which has a default ripple effect. And also UI was not visible when I tap for the first time, but I think that was because I was using different method than #Kasım Özdemir to change my active fragment when Item is clicked.
I didn't want to start my activity with first item, but with middle item in navigation view, which is "FragmentTwo" in below case. So the ripple effect was quite irrelevant.
So, I just attached the fragment instead of adding it and then hiding it, and now there is no ripple, Here is how code looks in Kotlin...
val firstFragment: Fragment = FragmentOne()
val middleFragment: Fragment = FragmentTwo()
val thirdFragment: Fragment = FragmentThree()
var fragment: Fragment? = null
var bnv: BottomNavigationView? = null
bnv = findViewById(R.id.bottom_navigation)
bnv!!.selectedItemId = R.id.middle_page
//fragments attached other than the fragment linked with middle item
supportFragmentManager.beginTransaction().attach(firstFragment).commit()
supportFragmentManager.beginTransaction().attach(thirdFragment).commit()
supportFragmentManager.beginTransaction().add(R.id.activity_main_container, middleFragment())
.commit()
bnv!!.setOnItemSelectedListener { item ->
when (item.itemId) {
R.id.first_page -> fragment = firstFragment
R.id.middle_page -> fragment = middleFragment
R.id.third_page -> fragment = thirdFragment
}
if (item.isChecked){
false
}
else {
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out).replace(
R.id.activity_main_container,
fragment!!
).commit()
true
}
}
So, using attach can also work fine instead of adding and then hiding it... for more information you can check this answer
(If you haven't already, Try using Navigation Component for bottom navigation, like this. It is more handy in some case, as it handles default backStack management by itself.)
The Problem
I have a navigation drawer with different fragments. There is a default toolbar every Fragment should use, except of one Fragment which needs a collapsing Toolbar.
My Question
How can I switch between the toolbars for the fragments ?
It seems you want to achieve something like this.
I have made an activity with common toolbar. when switching to the collapsing toolbar fragment I've made the toolbar transparent and fragment's toolbar takes over. The toolbar's color remains the same on switching to other fragments.
This allows you to manage complete collapsing toolbar's layout structure in xml and logic remains in Fragment.
Hope this will help. Refer the gif linked.
Gist for gif
The best solution that I found to easily collapse, lock it(keep it in collapsed mode) and unlock the collapsingToolbar.
private void collapseAppBar() {
// Collapse the AppBarLayout with animation
mAppBarLayout.setExpanded(false, true);
}
private void lockAppBar() {
/* Disable the nestedScrolling to disable expanding the
appBar with dragging the nestedScrollView below it */
ViewCompat.setNestedScrollingEnabled(nestedScrollView, false);
/* But still appBar is expandable with dragging the appBar itself
and below code disables that too
*/
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return false;
}
});
}
private void unLockAppBar() {
ViewCompat.setNestedScrollingEnabled(nestedScrollView, true);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
if (behavior != null) {
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return true;
}
});
}
}
And I use these functions in this way:
Fragment fragment = null;
Class fragmentClass;
switch (menuItem.getItemId()) {
case R.id.fragment1:
unLockAppBar();
fragmentClass = first_Fragment.class;
break;
case R.id.fragment2:
collapseAppBar();
lockAppBar();
fragmentClass = second_Fragment.class;
break;
case R.id.fragment3:
collapseAppBar();
lockAppBar();
fragmentClass = third_Fragment.class;
break;
You can easily get the Toolbar from your Fragment and then modify or change some property of that Toolbar inside the Fragment.
To get the Toolbar from your Activity you might consider using this.
Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
Now you need to make the changes on the Toolbar in the onResume function and then undo the changes each time you return from the Fragment inside onStop function. Otherwise the changes made in the Fragment will be carried on to other fragments as well when switched to other Fragment from the navigation drawer.
But in your case, I would recommend each Fragment should have their Toolbar so that it doesn't conflict with each other and can be modified as you need. And yes, remove the Toolbar from your Activity.
So add the Toolbar in the layout of your Fragment like this.
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimaryDark"/>
Then find it in the Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, container, false);
Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
// Modify your Toolbar here.
// ...
// For example.
// toolbar.setBackground(R.color.red);
// Create home button
AppCompatActivity activity = (AppCompatActivity) getActivity();
activity.setSupportActionBar(toolbar);
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
And Override the onOptionsItemSelected function.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case android.R.id.home:
getActivity().onBackPressed();
}
return super.onOptionsItemSelected(item);
}
I'm using Jetpack's Navigation components with single Activity and different Fragments in my app.
Some Fragments are accessible from bottom navigation (and have Toolbar from Activity). Some others are "special" Fragments and have their own Collapsible Toolbar.
To achieve this, I'm hiding Toolbar from Activity in "special" Fragments with this code in Activity:
// Handle toolbar changes in different Fragments
val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.my_special_fragment_with_collapsible_toolbar -> {
binding.toolbarMain.visibility = View.GONE
}
else -> {
binding.toolbarMain.visibility = View.VISIBLE
}
}
}
The recommend practice is to use toolbars in fragments instead of a common toolbar in activity. That way you can control the looks and behaviour of toolbar in fragment. Refer https://developer.android.com/guide/navigation/navigation-ui#support_app_bar_variations
I'm working with a online video curses about Android.
I have a list of items and when I click on a list item opens a new activity to show the details of the item. The problem is that I get a double text on detail activity which I try to fix it and I figure out that if I remove the code on DetailsActivity#onCreate()
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.details_fragment, new DetailsActivityFragment())
.commit();
}
the detail activity displayed fine.
Here is the photos and the code of DetailsActivity (working with fragment in different class)
Main Activity
Details Activity
But if I comment out the part of code which I mention below on DetailsActivity#onCreate(), the detail activity looks fine:
after comment out the code
DetailsActivity.java:
import android...//all the need package here don't worry about
public class DetailsActivity extends ActionBarActivity {
private Intent shareIntent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
//Create my Share Intent (for share options in action bar)
shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
shareIntent.putExtra(Intent.EXTRA_TEXT, "My whether data");
shareIntent.setType("text/plain");
/* IF I COMMENT THIS if CODE details showed fine. No double text */
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.details_fragment, new DetailsActivityFragment())
.commit();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_details, menu);
//Locate MenuItem with ShareActionProvider OR find the MenuItem that we know has the ShareActionProvider
MenuItem shareItem = menu.findItem(R.id.action_share_item);
// Fetch and Store ShareActionProvider
ShareActionProvider MyShareProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(shareItem);
if(MyShareProvider != null){
MyShareProvider.setShareIntent(shareIntent);
Log.d(DetailsActivity.class.getSimpleName(), "INTO share Provider--" + MyShareProvider.toString());
}
else{
Log.d(DetailsActivity.class.getSimpleName(), "NO share Provider");
}
return true;
}
....other code...
}
and I have implement the fragment in another class
public class DetailsActivityFragment extends Fragment {
public DetailsActivityFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//get the view of fragment's layout
View rootView = inflater.inflate(R.layout.fragment_details, container, false);
//get Extras form intent
Bundle DataFromIntent = getActivity().getIntent().getExtras();
//get specific data as String.
String weatherDataDetails = DataFromIntent.getString("weatherDataDetails");
//get text view from layout (rootview) and set test on it the above data (whetherData)
((TextView) rootView.findViewById(R.id.details_textview)).setText(weatherDataDetails);
return rootView;
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
}
}
My layouts (main + fragment)
activity_details.xml (main)
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/details_fragment"
android:name="com.example.android.sunshine.app.DetailsActivityFragment"
tools:layout="#layout/fragment_details"
android:layout_width="match_parent"
android:layout_height="match_parent" />
and fragment_details.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
tools:context="com.example.android.sunshine.app.DetailsActivityFragment">
<TextView
android:text="---null---"
android:id = "#+id/details_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
You can see that it used a <fragment> tag here
With <fragment> tag, the fragment is attached automatically when the layout is created. I.e. it is almost like the code below is automatically run for you
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.details_fragment, new DetailsActivityFragment())
.commit();
}
So if you run the code by yourself, it would be like creating ANOTHER fragment and put it on the same spot. It should be why you see that strange behaviour.
Edit:
In general, we handle fragments in one of the follow 2 ways
define in <fragment> tag and do NOT use FragmentManager to manage it
in code afterwards.
we declare a container, usually an empty <FrameLayout> and we use FragmentManager like the code above to manage it in code.
P.S. of course there are other cases you use ViewPager etc, but you will know later when you need it.
Reference:
Using <fragment> tag
http://developer.android.com/training/basics/fragments/creating.html
Using container
http://developer.android.com/training/basics/fragments/fragment-ui.html
I need to do something like this. Suppose I have 2 fragments A and B.There is a text which can be clickable in fragment A and when user click this text , he can go to fragment B. This example helped me to do it but I think it does not work for fragment. So please tell me a way to solve this problem.
public class myClaimsFragment extends Fragment {
TextView requestNewClaim;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View newClaimRequest = inflater.inflate(R.layout.activity_my_claims, container, false);
SpannableString ss = new SpannableString("Request");
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View textView) {
Intent intent = new Intent(getActivity(),LoginActivity.class);
startActivity(intent);
}
};
ss.setSpan(clickableSpan , 0,ss.length() , Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
requestNewClaim =(TextView) newClaimRequest.findViewById(R.id.requestHere);
requestNewClaim.setText(ss.toString());
requestNewClaim.setMovementMethod(LinkMovementMethod.getInstance());
return newClaimRequest;
}
}
Layout XML
<LinearLayout
android:id="#+id/view2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/requestHere"
android:clickable="true"
android:textSize="20dp"
android:textStyle="bold"
android:textColor="#000000"
android:layout_marginLeft="20dp"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"/>
</LinearLayout>
If LoginActivity is a fragment class then it would be okay if you use setOnClickListener on textview. But for fragment change you have to change Intent to fragmentTransaction,
Use something like,
textview.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getFragmentManager().beginTransaction().replace(R.id.container, new LoginActivity() ).addToBackStack("").commit();
});
But, if you want to use SpannableString then do like this,
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View textView) {
getFragmentManager().beginTransaction().replace(R.id.container, new LoginActivity() ).addToBackStack("").commit();
}
};
Here, R.id.container is the fragment of your main activity layout in which new view will be replaced.
You can not call Fragment via Intent. You need to replace your current fragment with new one.
you have to replace your fragment A to B, use this code
FragmentManager fm = getActivity.getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Fragment fragment = new FragmentB();
ft.replace(R.id.activity_main_content_fragment,fragment);
ft.commit();
In This code replace R.id.youframelayoutid, then it workable
If its code useful so please mark me my answer. :)
As an usual manner you should put a FrameLayout in your Activity's layout XML file. This FrameLayout acts like a placeholder for your Fragments. In other words, Fragment A can be pasted there, so is for Fragment B.
Okay, suppose you've added a FrameLayout in you activity's layout file. Pasting fragments on it and also replacing fragments should be done by the FragmentManager. Hence, you should grab a reference to a FragmentManger in your activity class. For getting this done ...
If you use Android Support Libraries, you should get a reference to FragmentManger by getSupportFragmentManager()
Otherwise, getFragmentManager()
In Android adding fragments and also replacing them are done in the form of a transaction. Thus you should inform the fragment manager that you would like to do a transaction. This could be done via:
FragmentTransaction transaction = fragmentManger.beginTransaction();
Now, you can apply all what you want on this transaction object. For instance, Adding a fragment could be done like this:
transaction.add(R.id.placeholder, new FragmentA() , "tag-frag-A");
For replacing ...
transaction.replace(R.id.placeholder, new FragmentB(), "tag-frag-B");
After you're done, you commit that transaction by calling
transaction.commit();
Notes:
FragmentManager acts like a container for your added fragments. You can search through your added fragments by their tag.
Device rotation does not remove added fragments in the FragmentManager. Thus in your onCreate method take care you've added any fragments only once.
You can add a Transaction to the back stack. This means that whenever user clicks on the Android back button this fragment will be removed from the state stack and also will be rolled back.