How hide/show icon on toolbar when opening some fragments - android

I have few fragments and one activity. The app contains toolbar which is always visible. There is icon on my toolbar. And i need to hide this icon when user opens 2,4,5 fragments and show this icon when users open 1 and 3 fragment. I don't need all code for this logic, I need advice how can I implement it and where add some logic for this behavior

The following is true assuming you are using jetpack's navigation and single activity:
Add destination change listener to your main nav controller inside your activity (addOnDestinationChangedListener, the interface is NavController.OnDestinationChangedListener). Inside the listener, you could check for destination.id in onDestinationChanged implementation. Actually, you could create two sets like this
private val twoFourFiveDestinations =
setOf(R.id.two, R.id.four, R.id.five)
private val oneThreeDestinations =
setOf(R.id.one, R.id.three)
just to make check like this if(twoFourFiveDestinations.contains(destination.id) ... and manage your icon visibility accordingly, it will make life easier.
Alternative solution would be to hand over icon management to fragments. You could define some interface for comm with activity, and manage toolbar icon when respective fragment is up and running. But you'd need to do that in every fragment of your question.

Step 1 : create an interface FragmentListener with one method :
public interface MyFragmentListener {
void onChangeToolbarImage(boolean show);
}
Step 2 : implement in YourActivity :
ImageView toolarImage= findViewById(R.id.toolbarimage)()///
#Override
public void onChangeToolbarImage(boolean show) {
if()
{ //check your imageView is Visible or not
toolbarImage.setVisibility(show); //change your ImageView's visibility
}
}
Step 3 : in each fragment you need get instance from interface :
private MyFragmentListener fragmentListener;
Step 4 : override onAtach in your Fragment :
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
if (context instanceof MyFragmentListener)
fragmentListener = (MyFragmentListener) context;
fragmentListener.onChangeToolbarTitleImage(true or false);
}

Create a companion object or static variable if you are using java.
class Util {companion object { lateinit var toolbarImg: ImageView }}
Inside your Main Activity onCreate initialize your toolbar and imageview
toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
Util.toolbarImg = toolbar.findViewById(R.id.cartImg)
XML
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay">
<ImageView
android:id="#+id/cartImg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:visibility="visible"
/>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
Now all you need to do is control the visibility of this ImageView.
On Fragment transaction
To Hide
if(Util.toolbarImg.visibility == View.VISIBLE){
Util.toolbarImg.visibility = View.GONE }

Related

How to change bottom app bar navigation icon programmatically

I am making an Android application which has one activity and many fragments. The activity contains a bottom app bar and that bottom bar has a navigation icon in it. Like this:
<com.google.android.material.bottomappbar.BottomAppBar
android:id="#+id/bottom_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:backgroundTint="#color/colorbottomappbar"
app:fabAlignmentMode="center"
app:navigationIcon="#drawable/ic_menu_green_24dp">
</com.google.android.material.bottomappbar.BottomAppBar>
This navigation menu icon will be shown in every fragment. However, in some fragments I want to change that navigation icon in the bottom app bar to back button/icon. How can I achieve this? Also, currently I handle the navigation icon click in the main activity. How can I handle the click in the case of the back icon? How will it know what the current fragment is and how can I determine which fragment the back icon leads to?
If you look at the documentation, you'll see that BottomAppBar extends from Toolbar and it has an inherited method called setNavigationIcon(int res).
You can implement an interface that your main Activity implements like so:
interface FramentChangedListener {
void onFragmentChanged(int type);
}
Your activity would do something like this:
public class MainActivity extends Activity implements FragmentChangedListener {
// This will keep track of what is currently shown
private int current = 0;
#Override
public void onFragmentChanged(int type) {
if (type == FirstFragment.SOME_TYPE) {
// Update the current fragment value, we're associating each fragment
// with an int value.
current = type;
bottomAppBar.setNavigationIcon(R.drawable.your_back_icon);
}
}
...
}
In your fragment you would do something like this:
public class FirstFragment extends Fragment {
private FragmentChangedListener listener;
public static final int SOME_TYPE = 1;
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceOf FragmentChangedListener) {
// context in this case is your activity, which implements FragmentChangedListener
listener = (FragmentChangedListener) context;
// You can call the listener now
listener.onFragmentChanged(SOME_TYPE);
}
}
}
In your Activity, add a listener to the BottomAppBar via setNavigationOnClickListener and whenever you receive the navigation icon event, you can check against the current value that we defined.

How to intercept MainActivity's BottomNavigationView menu clicks from fragment

My app works like a Wizard. I have an Activity that serves as a centralization for all my fragments. There is a BottomNavigationView with a "Next" button that appears in each fragment to drive the wizard.
When I want to go next I call "action_next" from BottomNavMenu and it navigate to the next fragment.
But I need to perform some action's when the user presses the Next Button in the context of that fragment (like store the data inputted). Further, I need to cancel the navigation if there is any problem with data inputted by the user.
At first try i did this in my fragment:
val controller = NavHostFragment.findNavController(this)
controller.addOnNavigatedListener { controller, navDestination: NavDestination ->
when (navDestination.id) {
R.id.destination_setup_tournment -> {
proceedNavigation(controller, navDestination)
}
}
}
private fun proceedNavigation(controller: NavController, navDestination: NavDestination) {
val teams = teamAdapter.getItems()
if (validateSubmit(teams)){
presenter.saveTeams(teams)
}else{
//how to cancel navigation and stay on this fragment?
}
}
But it does not look good or even correct for me and I don't know how to cancel the navigation if something is wrong.
Here is the App's related files:
MainActivity:
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorPrimary"
android:theme="#style/ToolbarTheme" />
<fragment
android:id="#+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/navigation"
app:defaultNavHost="true" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:menu="#menu/bottom_nav"
style="#style/Widget.MaterialComponents.BottomNavigationView"
>
</com.google.android.material.bottomnavigation.BottomNavigationView>
bottom_nav.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/action_next" android:title="#string/tornment_mode" android:icon="#drawable/ic_navigate_next_black_24dp" />
</menu>
There's no way to 'cancel' a navigation event - you need to put your business logic on your button/listener itself and only call navigate() when you actually want to navigate.
The Creating event callbacks to the activity documentation goes over one approach for connecting your Activity and Fragment together by using onAttach(Context) to get a reference to your Activity and set a property, callback, etc.
For example, you might consider doing something like:
// In your Activity
var onNextClicked: () -> Unit = {}
// In your Activity's onCreate()
button.setOnClickListener = View.OnClickListener {
onNextClicked.invoke()
}
// In your Fragment
override fun onAttach(context: Context) {
(context as YourActivity).onNextClicked = {
val teams = teamAdapter.getItems()
if (validateSubmit(teams)){
presenter.saveTeams(teams)
findNavController().navigate(R.id.action_next)
}
}
}
Each Fragment would set what they want the next button to do, allowing you full control over what the button does.
If you're dead set on the BottomNavigationView instead of just using an actual Button, you'd want to set your own OnNavigationItemSelectedListener and have that listener call the onNextClicked lambda if you're using this example.

Navigation Component: Toolbar issue in conditional navigation

According to the navigation priciples the first destination in your app should be the screen your users would normally see when they launch the app after signup/login or any other conditional navigation, I called that start destination 'homeFragment'.
Following this principle and after reading the post on conditional navigation by Maria Neumayer I am facing some issues with the Toolbar and the back navigation when going through the conditional navigation flows.
I am architecting the app using one single activity with a ConstraintLayout, a Toolbar and the NavHostFragment:
<android.support.constraint.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activities.NavigationTestActivity">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/nav_graph"
app:defaultNavHost="true"
/>
</android.support.constraint.ConstraintLayout>
The main graph looks like this with the home destination as start destination, connected with an action to a detail fragment (this action is triggered from a button) and a conditional navigation implemented using a nested graph:
Main Graph
I called this nested graph welcomeGraph and it includes the screens for login or signup, you can see it here:
Welcome Nested Graph
In the homeFragment onResume I check if the login/signup has been completed (determined by a dummy boolean stored in sharedPrefs) and if not I launch the welcome nested graph for signup/login.
In the login destination I have a 'Completed' button which sets the dummy boolean in sharedPrefs as true and triggers an action popToWelcomeGraph (inclusive) which should close the whole nested graph and take me back to the homeFragment (this works).
PROBLEM - Toolbar issue in nested graph:
As the Welcome graph is lauched inmediatelly after user lands in the app, the toolbar should not display a back/up arrow in the first destination of that nested graph, instead it should feel as if it was the first screen on the app, and tapping back should quit the app.
QUESTION: Is it possible to alter the Toolbar here to simulate the first screen in the nested graph is the first screen in the app until the login/signup is completed? Would this be a bad practice?
I have recently solved this issue by creating an AppBarConfiguration and setting up the toolbar with that configuration. The AppBarConfiguration takes in the top level destination ids as parameter. With that being said, you can do something like
private lateinit var appBarConfiguration: AppBarConfiguration
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appBarConfiguration = AppBarConfiguration(setOf(R.id.homeFragment,R.id.welcomeFragment))
findViewById<Toolbar>(R.id.toolbar).setupWithNavController(navController, appBarConfiguration)
}
This way the back button won`t be shown on top level fragments.
You have to implement communicator interface like below
interface ActivityCommunicator {
void alterToolbar();
}
and implement it to your activity class like below
class HomeActivity extends AppCompatActivity implements ActivityCommunicator {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Activity code
}
#Override
public void alterToolbar() {
ActionBar actionBar = getSupportActionBar();
// False to hide back button and true to show it
actionBar.setDisplayHomeAsUpEnabled(false);
}
}
and from your fragment you can call it like below
public class MainFragment extends Fragment {
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
ActivityCommunicator activityCommunicator = (ActivityCommunicator) getActivity();
activityCommunicator.alterToolbar();
// Fragment code
return super.onCreateView(inflater, container, savedInstanceState);
}
}
You can change alterToolbar() implementation as per your need

Android full-screen dialog callback issue

I am having trouble wrapping my head around something but let me first describe my setup:
I have an activity that references 3 fragments, each one of them get shown at the correct time. This is how the ChildrenSpecificationFragment looks:
If the user clicks the floating action button the following DialogFragment opens:
I found the following information in the new material design guidelines: https://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs
Avoid dialogs that:
Open additional dialogs from within a dialog.
Contain scrolling content, particularly alerts. Instead, consider alternate containers or layouts that are optimized for reading or interacting with significant amounts of content.
Exceptions include:Full-screen dialogs may open additional dialogs, such as pickers, because their design accommodates additional layers of material without significantly increasing the app’s perceived z-depth or visual noise.
This is where my problems begin. The 'add child' dialog has scrollable content (in landscape mode) and when the user clicks 'Birth date' a date picker opens.
I am trying to find a way to implement a full screen dialog (as in the guidelines) that has a callback to the ChildrenSpecificationFragment, so that I can add the child to the RecyclerView .
I hope that my questing is clear and would greatly appreciate any input that would lead me to the solution. Thanks in Advance!
TL;DR - DialogFragment is insufficient for anything other than completely full-screen. Use an Activity instead.
It is possible to make a DialogFragment full-screen (with the ActionBar shown), but it comes with lots of irritations.
A DialogFragment is, as the name suggests, a Dialog and a Fragment rolled into one: it can be treated as both a Dialog, using show() and dismiss(), or as a Fragment, using it with a FragmentManager.
As the official documentation suggests, making a dialog completely full-screen (overlaying everything) is achieved by attaching the dialog to the root view android.R.id.content:
public void showDialog() {
FragmentManager fragmentManager = getSupportFragmentManager();
CustomDialogFragment newFragment = new CustomDialogFragment();
if (mIsLargeLayout) {
// The device is using a large layout, so show the fragment as a dialog
newFragment.show(fragmentManager, "dialog");
} else {
// The device is smaller, so show the fragment fullscreen
FragmentTransaction transaction = fragmentManager.beginTransaction();
// For a little polish, specify a transition animation
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
// To make it fullscreen, use the 'content' root view as the container
// for the fragment, which is always the root view for the activity
transaction.add(android.R.id.content, newFragment)
.addToBackStack(null).commit();
}
}
To get the dialog to appear below the ActionBar, a FrameLayout is required which is used instead of the root layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.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_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.CoordinatorLayout
android:id="#+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Use ThemeOverlay to make the toolbar and tablayout text
white -->
<android.support.design.widget.AppBarLayout
android:id="#+id/abl_top"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:fitsSystemWindows="true"
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
<FrameLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header"
app:menu="#menu/nav_view"/>
</android.support.v4.widget.DrawerLayout>
Now comes the pain.
Depending on how the app's main navigation is setup, different hoops will need to be jumped through in order to get everything working perfectly.
The above example has a NavigationView. Since the home button android.R.id.home is handled in the main view, some logic is needed there to check if our dialog is shown so that the home button, which is now an X, will close the dialog. Returning false here allow the event to be handled in the dialog.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
FragmentManager fm = getSupportFragmentManager();
Fragment f = fm.findFragmentById(R.id.content);
if (f instanceof MyDialogFragment) {
return false;
}
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
}
return super.onOptionsItemSelected(item);
}
Also, the back button needs similar logic to determine whether the NavigationView needs closing or the ActionBar content resetting.
#Override
public void onBackPressed() {
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.closeDrawer(GravityCompat.START);
} else {
FragmentManager fm = getSupportFragmentManager();
Fragment f = fm.findFragmentById(R.id.content);
if (f instanceof MyDialogFragment) {
final ActionBar ab = getSupportActionBar();
ab.setHomeAsUpIndicator(R.drawable.ic_menu);
ab.setTitle(R.string.app_name);
}
super.onBackPressed();
}
}
In the DialogFragment itself, the logic for closing the dialog (and abusing the ActionBar) needs to be implemented.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (mActionBar != null) {
mActionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
mActionBar.setTitle(R.string.app_name);
}
getActivity().getSupportFragmentManager().popBackStack();
case R.id.action_save:
if (mOnAcceptListener != null) {
mOnAcceptListener.onAccept();
}
if (mActionBar != null) {
mActionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
mActionBar.setTitle(R.string.app_name);
}
getActivity().getSupportFragmentManager().popBackStack();
return true;
}
return super.onOptionsItemSelected(item);
}
This alls feels really kludgy. Of course, if you're using a TabLayout, forget everything I've just said.
With a TabLayout you can just handle everything in the DialogFragment, but if you're using a ViewPager, it'll be impossible to get the dialog to cover the tabs but not the action bar. See Show DialogFragment over TabLayout.
That question (by me) has an answer that suggests the same as #Jdruwe, which is to forget the hopelessness of the DialogFragment and use an Activity instead.
A solution described on my blog using startActivityForResult(...): http://jeroendruwe.be/full-screen-dialogs-in-android/
I don't see code from your post. So I am guessing your code structure as a start.
First build your dialog with a listener and process setPositiveButton() and the onClick event.
Code suggestion:
public class ChildrenSpecificationFragment extends Fragment {
...
public void passData(Object obj) {
}
class SubChildFragment extends Fragment {
AlertDialog.Builder builder = new AlertDialog.Builder(thisContext);
...
// Add the buttons...
builder.setPositiveButton("Save", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
...
passData(Object obj); // pass data to the outer fragment class
Notes:
SubChildFragment, for example, is an inner class derived from Fragment. It can call the public method passData() in the outer class ChildrenSpecificationFragment for passing any data you need.
I am using an inner class because I think this is what you meant in your diagram by
Add child full-screen fragment
This coding technique is easier than starting a new Activity and Intent.
For showing fullscreen dialogs, there is a good Google webpage I think # Dialog - Fullscreen. Search text for "Showing a Dialog Fullscreen or as an Embedded Fragment".
add this line to oncreate in your custom dialog fragment.
setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
On the other hand, you can use content resolvers to store your children datas.
It has observer pattern. So each CursorAdapter attached to that content it refreshes itself without calling notifySetDataChanged();.
I think you are using RecyclerView.Adapter. You can use this class.
Another advice for implementing adding child feature is using startActivityForResult(activity);.
You can send back datas by using getIntent().getExtras().put(key,value); You can search for custom start activity for result.
Good luck

Actionbar, Toolbar, activity inheritance

My activities extend from a generic base activity, where I declare and initialize public variables like context of type Context, activity of type Activity and mActionBar of type ActionBar.
So this avoids redundant initialization code in all my app's activities.
But with the advent of Toolbar, I am a little confused on how to do this. Toolbar is not like ActionBar and replaces it, but also extends it.
The ActionBar is a view object that is always available for retrieval, by ActionBar activity, and it sits above the views that are created. This is not declared in layout XML anywhere.
But Toolbar is declared only in layout XML, so I have to include it in each and every layout I create, or else I will not be able to access the Toolbar object.
I typically use setContentView(R.layout.mylayout) in the onCreate method of each individual activity. And then I have to initialize my Toolbar object after that using findViewById. Therefore I can't put this code in my BaseActivity's onCreate function because the setContentView wouldn't have been initialized yet.
Even if I created Toolbar programmatically with it's constructor, and attempted to add the view to the top of the hierarchy, I would still have to do this on a layout by layout, and activity by activity basis, because some layouts are RelativeLayout's as the root object, and others are different. So these will still have separate code considerations.
The reason I am curious about a good way for my activities to inherit Toolbar, is because it is a complete nightmare for Google to suddenly require Android 4.0-4.4 devices to use the v7 compatibility pack, replace the actionbar completely with the Toolbar object, use v4 compatibility pack fragments instead of native ones, all to use the latest design paradigms.
this is my implementation. Hope it helps someone.
public abstract class BaseActivity extends AppCompatActivity {
Toolbar toolbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
protected boolean useToolbar() {
return true;
}
#Override
public void setContentView(int layoutResID) {
View view = getLayoutInflater().inflate(layoutResID, null);
configureToolbar(view);
super.setContentView(view);
}
private void configureToolbar(View view) {
toolbar = (Toolbar) view.findViewById(R.id.toolbar);
if (toolbar != null) {
if (useToolbar()) {
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} else {
toolbar.setVisibility(View.GONE);
}
}
}
}
From here on you just extend BaseActivity. If you don't want a toolbar you will have to override the useToolbar().
Don't forget to add in activity.xml at the top
<include layout="#layout/toolbar" />
toolbar.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
</merge>

Categories

Resources