Give toolbar to each fragment!

Or how did we change global toolbar to small local toolbars

Michael Spitsin
7 min readDec 24, 2016
pages with different toolbars

In our company’s project we used till this day planty famous android navigation pattern. We had only one activity (actually there were several activities, but they was just servants for main application activity) and almost each page was represented by fragment. They stored in fragment backstack in foregoing activity. I’ve written an article about navigation organization, that we’ve used, so, please, check it out :)

Main point was that in activity we had layout similar to this:

<?xml version="1.0" encoding="utf-8"?><LinearLayout 
xmlns:android
="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/layout_with_toolbar" /> <FrameLayout
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

And after switching from drawer to bottom navigation:

<?xml version="1.0" encoding="utf-8"?><LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/layout_with_toolbar"/> <FrameLayout
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>
<com.aurelhubert.ahbottomnavigation.AHBottomNavigation
android:id="@+id/bottomNavigation"
android:layout_width="match_parent"
android:layout_height="56dp"/>
</LinearLayout>

So we had one main toolbar and bottom navigation. Each fragment had to adjust common navigation views for its needs. Moreover several fragments didn’t use full setup for toolbar. For example, some fragments didn’t point that toolbar must show ‘left-arrow’ navigation icon, because previous fragments in backstack already set up this icon. So every time programmer created new fragment, he/she had to think about bottom navigation and toolbar synchronization. And this was not only one problem.

First, a solution!

Whishes

We decided to remain ‘one activity many fragments’ pattern, because it can live. Main point of refactoring was to get rid of godlike toolbar and bottom navigation. We wanted to give toolbar and bottom navigation to each page, that need them. Furthermore we wanted to minimize changes of current fragment layouts. For example, if some fragments have layout and we need to add just one toolbar on top of it we don’t want to place in each layout that line:

<include layout=”@layout/layout_with_toolbar”/>

Wrapping them with linear layout, we want just to automate layout building process. And of course we want to have posibility to customize them (toolbar and navigation). For example, we have some complex layout with custom toolbar or coordinator layout with app bar and behaviors, so we want to say our future ‘system’ to not automatically assign toolbar for this layout, because we already have it.

And we came to next solution

We decided to create main fragment class that was named NavigationFragment. It manages all navigational stuff. It has many methods, but most important are (they will be used in inheritors):

NavigationBuilder buildNavigation() — used for setting up initial navigation of calling fragment. All stuff will be created according to it. Also it can be used for refining current navigation. When user want to change navigation of fragment he/she can call invalidateNavigation() and pass buildNavigation(). For example:

invalidateNavigation(buildNavigation().toolbarTitle("New title"));

void prepareToolbar(Toolbar toolbar) — used to make additional preparations for created and constructed toolbar. For example, if you want to add tabs, you can override this method and add such functionality.

void prepareBottomNavigation(AHBottomNavigation bottomNavigation) — used to make additional preparations for created and constructed bottomNavigation. By the way I used AHBottomNavigation library to work with bottom navigation in Android.

void invalidateNavigation(NavigationBuilder newNavigation) — called when you need to update navigation at some time of fragment showing. For example, after data is loaded, you want to add new items to the menu or update title.

Let’s talk about building navigation

NavigationBuilder is a main class for helping construct a proper navigation for your fragment page. First of all we need to understand that there are two main mechanisms of building navigation in our app. First is automatic mechanism when we just give our layout and say “Please, wrap it somehow with toolbar and bottom bar if needed”. Second is custom mechanism, when we tell: “Okey, I already have toolbar or bottom navigation in layout. Thank you, but you don’t need to create anything. Instead, just use these ids to find my controls”. And both of these principles have same settings (toolbar title, toolbar icon, menu, bottom bar menu), but also they have separate tuning features. For example, with automatic mechanism you can specify, do you want to show toolbar or not, do you want to show bottombar or not. With custom mechanism you can specify ids of toolbar or bottom bar.

Thus we came to solution of abstract parent NavigationBuilder and two children that can switch between each other. For implementing it we used ‘this-pattern’ to be able to keep reference from child builder, when calling methods from parent builder. Basically it looks like:

public abstract class NavigationBuilder<T extends NavigationBuilder<T>> {
...
public T toolbarTitle(CharSequence toolbarTitle) {
this.toolbarTitle = toolbarTitle;
return getThis();
}
protected abstract T getThis(); ...
}
public final class AutoLayoutNavigationBuilder extends NavigationBuilder<AutoLayoutNavigationBuilder> {
...
public AutoLayoutNavigationBuilder includeToolbar() {
this.includeToolbar = true;
return this;
}
@Override
protected AutoLayoutNavigationBuilder getThis() {
return this;
}
...
}

Thus, we will able to write, for example:

new AutoLayoutNavigationBuilder()
.toolbarTitle(“Cool”)
.includeToolbar()
.build();

Building Layout

We need to determine ways to build layout. For example, we can say “Inflate layout with this id” or “Here is our custom layout. Please, do nothing with it, just give me” or “Here is id of my layout or my custom view. Please, wrap it with toolbar and bottom navigation”. So we came to next solution:

public interface LayoutFactory {
View produceLayout(LayoutInflater inflater, @Nullable ViewGroup container);
}
public final class IdLayoutFactory implements LayoutFactory {
private final int layoutId;
public IdLayoutFactory(int layoutId) {
this.layoutId = layoutId;
}
@Override
public View produceLayout(LayoutInflater inflater, @Nullable ViewGroup container) {
return inflater.inflate(layoutId, container, false);
}
}
public final class DummyLayoutFactory implements LayoutFactory {
private final View view;
public DummyLayoutFactory(View view) {
this
.view = view;
}
@Override
public View produceLayout(LayoutInflater inflater, @Nullable ViewGroup container) {
return view;
}
}
public final class NavigationLayoutFactory implements LayoutFactory {
private final boolean includeToolbar;
private final boolean includeBottomBar;
private final LayoutFactory origin;
public NavigationLayoutFactory(boolean includeToolbar, boolean includeBottomBar, LayoutFactory origin) {
this.includeToolbar = includeToolbar;
this
.includeBottomBar = includeBottomBar;
this.origin = origin;
}
@Override
public View produceLayout(LayoutInflater inflater, @Nullable ViewGroup container) {
LinearLayout parent = new LinearLayout(inflater.getContext());
parent.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
parent.setOrientation(VERTICAL);
View child = origin.produceLayout(inflater, parent);
LinearLayout.LayoutParams childParams = new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
if (includeBottomBar) {
childParams.weight = 1;
}
if (includeToolbar) {
inflater.inflate(R.layout.layout_with_toolbar, parent);
}
parent.addView(child, childParams); if (includeBottomBar) {
int height = (int) dp(parent.getContext(), 56);
AHBottomNavigation bottomNavigation = new AmruBottomNavigation(parent.getContext());
bottomNavigation.setId(R.id.bottomNavigation);
parent.addView(bottomNavigation, new LinearLayout.LayoutParams(MATCH_PARENT, height));
}
return parent;
}
}

Last one class uses Decorator pattern to be able to wrap layout that was build, for example, from xml. You can inject any layout factory you want. And of course I used linear layout just for simplicity, but any can use your own LayoutFactory.

Why we did it?

I think that every programmer choose its own architecture, based on wishes and future wishes of users/product owner/designer. In my case I think each particular toolbar in each page is important because then ui looks consistent. You know that every page has it own small navigation, its own menu, title, bottom navigation possibly. Some pages have paralax photo in toolbar, some pages dont have toolbar, some pages have some custom layout in toolbar. So every page has its own toolbar, but some pages have same toolbar. So only base on those thoughts I decided to give toolbar to each page and make some convenient way to set up it.

For example with current architecture we can set up navigation something like that:

@Override
protected NavigationBuilder buildNavigation() {
return navigation(R.layout.fragment_send_feedback)
.custom()
.toolbarId(R.id.my_toolbar_id)
.toolbarTitleRes(R.string.title_feedback_screen)
.toolbarNavigationIcon(showCloseIcon ? CLOSE : BACK)
.menuRes(R.menu.menu_send_feedback,
item(R.id.sendMenuItem, this::sendFeedback));
}
@Override
protected NavigationBuilder buildNavigation() {
return navigation(R.layout.view_pager)
.toolbarTitleRes(R.string.title_favorites)
.toolbarNavigationIcon(NOTHING)
.includeBottomBar()
.currentBottomBarItem(FAVORITES_PAGE);
}

And of course it is not 100% better than approach with fragment pages that share one toolbar. Maybe in some cases second approach is better. For example with it, as I think, it is better to create translation animations. With first approach it is also possible but maybe it will look like hack rather than clean solution.

We chose “each page has its own toolbat”-approach, because with it you don’t need to think about synchronization between fragment and navigation, you will not have animation- or view- problems with switching between, for example, non-toolbar-fragment and toolbar-with-tabs-fragment and e.t.c

More info about cons and procs you can find here.

Afterwords

My refactoring was a last resort. I explained to product owner that it would be easer to implement than fix many bugs with switching between tabs, when we implemented bottom navigation. But I think this is a one of cases when you need to sit in the beginning of project and discuss with team, with your boss next plans about application. Will be there many different toolbars in each page, will they transform or not, will they be same or different? You always need to think about architecture in perspective of product plans.

By the way after such refactoring there was several bugs. So also you need to always keep in mind, that many changes (expessially such massive as that) can lead to future bugs, that you can miss.

P.S.: Sources

As Bhargav Pandya asked, I’ve added sources of NavigationFragment implementation. Here you can find them. In github you will see app folder and navigation folder. Second one is kind of library that holds all necessary stuff to help you to build fragments with navigation. And first folder is sample application, which shows how to use it.

Thank you all for reading again.

P.P.S.: Links

If you liked article there are other by me about improve working with permissions, saving states and refactoring base fragment.

--

--

Michael Spitsin

Love being creative to solve some problems with an simple and elegant ways