How to Combine Bottom Tabs, Top Tabs and a Hamburger Menu in a React Native Application

How to Combine Bottom Tabs, Top Tabs and a Hamburger Menu in a React Native Application

Mixing different navigation components can be tricky to implement as well as suggested against.  But there may be scenarios where you need to mix a few different components to encapsulate the experience you are looking to convey.   This guide will walk you through creating a simple application that contains bottom tab navigation, top tabs, and a hamburger menu to give you the basics of mixing navigation components.

First let’s initialize the project:

react-native init navigationExample

cd navigationExample

Once initialized we will need to install some packages. The first package we will install is react-navigation.  This will give you the hamburger menu and the bottom tabs and as the name might suggest allow you to navigate between different screens.  Run the following commands:

npm install –save react-navigation

npm install –save react-navigation-gesture-handler

react-native link

Android requires one extra step. Make sure your MainActivity.java looks like this:

package com.navigationexample;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "navigationExample";
    }

  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {
      @Override
      protected ReactRootView createRootView() {
       return new RNGestureHandlerEnabledRootView(MainActivity.this);
      }
    };
  }
}

 

Now that we have that we will install Native Base.  Native Base is a library that allows use to different ui components and allows for simple styling.  Running the following commands with install Native Base:

npm install native-base –save

react-native link native-base

We will also install an icon package for access to some icons for the hamburger menu and bottom tabs.

npm install react-native-vector-icons

react-native link react-native-vector-icons

Awesome, now that we have the packages we need lets get started on the actual code.  First let’s create a `src` folder to hold our screen files. Then let’s create a couple basic screens.

src/DefaultScreen.js

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import Icon from 'react-native-vector-icons/SimpleLineIcons';

const instructions = Platform.select({
 ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
 android:
   'Double tap R on your keyboard to reload,\n' +
   'Shake or press menu button for dev menu',
});

export default class DefaultScreen extends Component {
 render() {
   return (
     <View style={styles.container}>
       <Text style={styles.welcome}>Welcome to React Native!</Text>
       <Text style={styles.instructions}>To get started, edit App.js</Text>
       <Text style={styles.instructions}>{instructions}</Text>
     </View>
   );
 }
}

const styles = StyleSheet.create({
 container: {
   flex: 1,
   justifyContent: 'center',
   alignItems: 'center',
   backgroundColor: '#F5FCFF',
 },
 welcome: {
   fontSize: 20,
   textAlign: 'center',
   margin: 10,
 },
 instructions: {
   textAlign: 'center',
   color: '#333333',
   marginBottom: 5,
 },
});

 

This file is the basic screen that react sets up for you when you start a project.

src/BlueScreen.js

import React, {Component} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import Icon from 'react-native-vector-icons/SimpleLineIcons';


export default class BlueScreen extends Component {
    render() {
 	 return (
        <View style={styles.container}>
     	 <Text style={styles.title}>Blue Screen</Text>
        </View>
 	 );
    }
 }
  const styles = StyleSheet.create({
    container: {
 	 flex: 1,
 	 justifyContent: 'center',
 	 alignItems: 'center',
 	 backgroundColor: 'blue',
    },
    title: {
 	 fontSize: 20,
 	 textAlign: 'center',
 	 margin: 10,
    }
 });

Now that we have two screens we can add them to a Drawer Navigator.

src/HamburgerNavigation.js

import {
 createAppContainer,
 createDrawerNavigator,
} from 'react-navigation';
import BlueScreen from './BlueScreen';
import DefaultScreen from './DefaultScreen';

const HamburgerNavigation = createDrawerNavigator(
    {
        BlueScreen: BlueScreen,
        DefaultScreen: {
            screen: DefaultScreen,
        }
    },
    {
        initialRouteName: ‘DefaultScreen’,
        
        
    }
 );


export default createAppContainer(HamburgerNavigation);

You will then need to make a couple changes to you App.js to display the new screens.

App.js

import React, {Component} from 'react';
import HamburgerNav from './src/HamburgerNav'


export default class App extends Component {
 render() {
   return (
     <HamburgerNav/>
   );
 }
}

So now if you build your application with `react-native run-ios` or `react-native run-android` you should see the same default screen as you would if you started a react project from scratch.  The difference is if you swipe from left to right, you will see the drawer menu open. You can then open the BlueScreen. For now the drawer menu is only accessible from the swipe gesture we will allow the user to access it from the nav bar later.  The drawer navigator handles all the navigation to either screen which makes it easy to implement. Yet this isn’t the final implementation of the menu so more to come here. Now let’s get the bottom tabs in place.

src/BottomTabs.js

import React from 'react';
import { createStackNavigator, createBottomTabNavigator, createAppContainer } from "react-navigation";
import Icon from 'react-native-vector-icons/SimpleLineIcons';
import GreenScreen from "./GreenScreen";
import RedScreen from "./RedScreen";



const GreenTab = createStackNavigator({
    Green: GreenScreen
});

const RedTab = createStackNavigator({
    Red: RedScreen
});

const Tabs = createBottomTabNavigator({
    Green: GreenTab,
    Red: RedTab
}, {
    defaultNavigationOptions: ({ navigation }) => ({
        tabBarIcon: () => {
            const { routeName } = navigation.state;
            let tabName;
            tabName = routeName === 'Green' ? 'home' : 'grid';

            return <Icon name={tabName} size={20} />
        }
    })
});

export default createAppContainer(Tabs);

This file holds the logic for the bottom tab navigation.  Each tab has its own stack navigator that way if you wanted to add more screens on to one tab you could by simply adding them to the correct stack.  To set the icons for the specific tabs we use the tabBarIcon property within defaultNavigationOptions and set the icon based on the title of the tab.  You can do some more customizing of the tabs (active color, inactive color, padding, font etc.) with the tabBarOptions property. For now we are just sticking with the default tab styling.  For the sake of not showing too much of the same code GreenScreen and RedScreen are the same as the BlueScreen from earlier except for one minor difference.

import HamburgerIcon from './HamburgerIcon';

export default class GreenScreen extends Component {
    static navigationOptions = () => {
        return {
            headerLeft: <HamburgerIcon/>
        };
    };

In this snippet I created a hamburger icon to display on nav to allow access to drawer menu without the swipe gesture.  The hamburger icon was purely this custom component I created.

src/HamburgerIcon.js

import React, {Component} from 'react';
import { withNavigation } from 'react-navigation';
import { TouchableOpacity } from "react-native-gesture-handler";
import Icon from 'react-native-vector-icons/SimpleLineIcons';


class HamburgerIcon extends Component{
    render() {
 	 return (
        <TouchableOpacity
        style={{
            width: 44,
            height: 44,
            marginLeft: 20
        }}
        onPress={()=>{
            this.props.navigation.openDrawer();
        }}>
            <Icon name='menu' size={20} color='black'/>
        </TouchableOpacity>
 	 )
    };
}

export default withNavigation(HamburgerIcon);

 

We are almost there! We now have bottom tabs with a hamburger menu.  Last up is the top tabs using Native Base. For these we will just edit the GreenScreen render function to include the tabs.  

src/GreenScreen.js

import React, {Component} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import HamburgerIcon from './HamburgerIcon';
import { Container, Tab, Tabs, StyleProvider } from 'native-base';
import Tab3 from './Tab3.js';
import Tab2 from './Tab2.js';


export default class GreenScreen extends Component {
    static navigationOptions = () => {
        return {
            headerLeft: <HamburgerIcon/>
        };
    };
    render() {
        return (
            <Container>
                <Tabs>
                    <Tab
                    heading='Green Tab'>
                        <View style={styles.container}>
                            <Text style={styles.title}>Green Screen</Text>
                        </View>
                    </Tab>
                    <Tab heading='Tab 2'>
                        <Tab2 />
                    </Tab>
                    <Tab heading='Tab 3'>
                        <Tab3 />
                    </Tab>
                </Tabs>
            </Container>
        );
    }
 }
  const styles = StyleSheet.create({
    container: {
 	 flex: 1,
 	 justifyContent: 'center',
 	 alignItems: 'center',
 	 backgroundColor: 'green',
    },
    title: {
 	 fontSize: 20,
 	 textAlign: 'center',
 	 margin: 10,
    }
 });

 

First we imported the correct component we needed from Native Base and then updated the render function to include the tabs.  Within each tab we have added a couple components. For simplicity the Tab2 and Tab3 files are simply a component with a text within a view but you can make these looks however you want. If you wanted to you could create stack navigators for each of these tabs as well to allow you to open screens and still show the top tabs other wise you can use the stack navigator in which the GreenScreen already resides.  As mentioned earlier Native Base allows for easy customization of their components but for now we are sticking with the default. Check out the links at the bottom of the screen if you are interested in customizing Native Base components.

Now we are almost there!  A couple of things we need to clean up.  Number one we probably don’t want the tabs option actually showing in the drawer menu.  Number two need a back button for the screens with in the drawer menu. So for hiding the tabs from the drawer menu we will customize the content showing in the drawer menu using contentComponent property.  This allows us to customize exactly what shows in the drawer. We will just add two text components to access the two screens. You can do alot of cool thing with the ui of the drawer menu but this guide is focusing on the navigation structure. So let’s create a simple back button component to add to the BlueScreen and DefaultScreen.

src/BackButton.js

import React, { Component } from 'react';
import { StyleSheet } from 'react-native';
import { withNavigation } from 'react-navigation';
import { TouchableOpacity } from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
class BackButton extends Component {
 render() {
   return (
     <TouchableOpacity
       style={styles.backButton}
       onPress={() => {
         this.props.navigation.pop();
       }}
     >
       <Icon
         name= 'chevron-left'
         size={20}
       />
     </TouchableOpacity>
   );
 }
}
const styles = StyleSheet.create({
 backButton: {
   height: 44,
   width: 44,
   justifyContent: 'center',
   alignItems: 'center',
 },
});
export default withNavigation(BackButton);

This back button component is just an icon within a TouchableOpacity and onPress just pop the screen the user is on from the stack.  Then within the BlueScreen and DefaultScreen import the back button and add this snippet.

static navigationOptions = () => {
        return {
            headerLeft: <BackButton/>
        };
 };

Last but not least we need to update the HamburgerNav.

src/HamburgerNav

import React from 'react';
import {
 createAppContainer,
 createDrawerNavigator,
 createStackNavigator,
} from 'react-navigation';
import { ScrollView } from 'react-native-gesture-handler';
import { SafeAreaView } from 'react-navigation';
import { Text } from 'react-native';
import BlueScreen from './BlueScreen';
import DefaultScreen from './DefaultScreen';
import BottomTabs from './BottomTabs';


const HamburgerNavigation = createDrawerNavigator(
    {
        Tabs: BottomTabs,
    },
    {
        initialRouteName: 'Tabs',
        contentComponent: props => {
            return (
                <ScrollView>
                    <SafeAreaView
                    forceInset={{ top: 'always', horizontal: 'never' }}
                >
                    <Text
                        onPress={() => {
                        props.navigation.navigate('BlueScreen');
                        props.navigation.closeDrawer();
                        }}
                    >
                        BlueScreen
                    </Text>
                    <Text
                        onPress={() => {
                        props.navigation.navigate('DefaultScreen');
                        props.navigation.closeDrawer();
                        }}
                    >
                        DefaultScreen
                    </Text>
                    </SafeAreaView>
                </ScrollView>
            )
        }
    }
 );

 const Stack = createStackNavigator(
 	 {
        Drawer: {
            screen: HamburgerNavigation,
            navigationOptions: {
                header: null,
            },
     	 },
        BlueScreen: BlueScreen,
        DefaultScreen: {
            screen: DefaultScreen,
        }
 	 }
 );


export default createAppContainer(Stack);

Here you can see the BlueScreen and Default screen now aren’t inside of the DrawerNavigator.  We add these two screens to StackNavigator that also contains the DrawerNavigator. This way these screens are opened on top of the drawer navigator. This is all made possible by the contentComponent property on the DrawerNavigator, this allows us to make custom content for the menu and use the stack navigator to navigate to these pages.  Obviously in this example we made the drawer menu very simple in design but React Native gives you the tools to customize how you would like.

There you have it! A simple React Native application with bottom tab navigation, top tabs, and a hamburger menu.  No matter what direction you would like to take with navigation inside you app React Native gives you the freedom and the ability to create the experience you want.  See the finished product below! For more information on the packages I used check the links below.

 

Caleb Hatcher

Caleb Hatcher

Mobile software engineer with experience in cross-platform solutions such as react-native, flutter, and titanium. Graduated from Randolph Macon in 2016 with a major in Computer Science and a minor in Biology. Started the Shockoe indoor soccer team.

Three Reasons Flutter is a Viable Cross-Platform Framework

Three Reasons Flutter is a Viable Cross-Platform Framework

Flutter, as you may know from one of our previous blogs, is Google’s cross-platform mobile development framework. Flutter has recently entered beta as of February 27th. I have only been working with this framework for about a month here at Shockoe, but in my limited exposure, I have seen the many ways in which Flutter can improve our delivery to clients.

 

For most products in any field, there are two important aspects of development: time to market and product quality. If there are ways for us as a company to improve these areas we have an obligation to look into them. With that in mind, I was eager to explore Flutter. Here are three reasons I believe Flutter is a viable cross-platform framework.

 

1. Flutter’s Implementation of Widgets.

 

One of the key fundamentals of Flutter is that everything is a widget. I really like this concept as Google provides many out-of-the-box options to help speed up development. Flutter doesn’t have a bridge to the native world as many other cross-platform frameworks have. Instead, Flutter controls every pixel on the screen, which allows for various great options for custom UI to be tailored to a client’s every need. With that, the widget catalog also gives developers a plethora of options to mimic native controls. This enables any idea to use the catalog to create a beautiful product without compromising quality. The following graphic gives a great visual of how widgets can be laid out for specific components:

 

Flutter’s-Implementation-of-Widgets

 

The section above is made up of four types of widgets. The parent row would be the outermost element containing 3 columns which each contain an icon and text. There’s a huge advantage to having a widget for seemingly almost everything, and the list will only continue to grow.

 

2. Fantastic Debugging Tools.

 

Flutter includes plenty of tools that have sped up development, including one of my personal favorites: the hot reload. With almost any update to the UI, you can use hot reload to instantly see all changes made. Additionally, Flutter offers plug-ins for multiple IDE’s (VS Code, IntelliJ). I personally use IntelliJ and the plug-in provides autofill, debugging, among many others features. Debugging within the IDE contains an abundance of options, but overall, my favorite is the debug painting tool. This tool allows the developer to see the borders on all of their widgets along with the paddings and margins to help clarify where a widget might need to be adjusted.

 

Fantastic-Debugging-ToolsAnother notable feature is the toggle platform tool which allows users to see the UI differences between iOS and Android with the click of a button on the IDE. This debugging tool helps keep development quick and efficient while maintaining quality across platforms.

 

3. Flutter’s Great Documentation.

 

When I was introduced to Flutter, I was excited and daunted by the task of learning a new language. As I started to dive into the documentation and was pleasantly surprised by how thorough it was. It made it extremely easy to start creating applications. Flutter uses Dart, which is a programming language developed by Google. A Javascript or Java developer should be able to transition to Dart with relative ease.

 

Overall, I’m excited to see the future of Flutter. As the community grows, I’m looking forward to seeing the plug-ins that are created and the new projects we will undertake at Shockoe! I have confidence that if a client comes to us with an idea, we will exceed their every need using Flutter to increase development speed without compromising quality.

 

 

 

Editor’s Note: 

Check out the latest from our developers!

SQL vs NoSQL, the ultimate database match

Comparing React Native to Axway Titanium

Kotlin: Three Reasons To Start Using It Today

Debugging Titanium Applications using Safari Web Inspector

Debugging Titanium Applications using Safari Web Inspector

Debugging is one of the most frustrating aspects of software development of any kind – it is also one of the most essential. Finding a malfunction can be time-consuming; therefore, it is important to have effective tools that can decrease your debugging time. For Titanium, most of my debugging consisted of log statements and alerts. While this method can be useful, it can also be a little time consuming to rebuild and to log a different variable, collection or model.

One of my coworkers saw me using this log for debugging and suggested an alternative: using Safari Web Inspector. I was very surprised at how easy it was to set up and how effective it can be throughout the process. This one line is all you need to add to your “tiapp.xml” file in your project:

`<use-jscore-framework>true</use-jscore-framework>`

under the <iOS> flag. Unfortunately, this method only works on an iOS simulator. Once you have updated your tiapp.xml, build your project and navigate to the page you would like to inspect. Next, you will need to open Safari; if the develop tab isn’t visible you will need to follow a couple extra steps:

Select the Safari tab from that dropdown to navigate to preferences then check “Show develop menu in the bar.” After the Develop tab is visible you will open the Simulator option and then select JSContext.

This is where all the magic happens. The files where breakpoints can be inserted will be visible on the left panel of the screen. Breakpoints are very convenient for stepping through your code and seeing exactly what is happening. I suggest opening the right panel when the breakpoints are hit. This is where you will find local variables and can also add Watch Expressions. Watch Expressions is the place where you can add the variables that you would like to keep an eye on. You will be able to see and follow each variable through every step of your code.

The bottom console is also a very helpful aspect of this debugger. I use this for taking a look at any model or collection to inspect in detail what they contain. This has been a lifesaver for me. It makes it easy to investigate exactly what is going on with any unexpected behavior with your models or collections.

The safari web inspector has its problems and will, from time to time, crash the app – but overall this tool has helped me immensely debugging my titanium apps. It makes it so effortless to nail down exactly where the problem lies. As much as we all want to have flawless code without bugs, they will appear every once in a while. However, this tool can save you from the frustration those bugs can cause. As I stated before, it is very easy to set up, so jump in and play around with it a bit. Have any questions or comments? Feel free to share your tricks for debugging. Also, you can find our latest apps and check out our work here.

Editor: In case you need to know other ways we used to debug Titanium Apps, please also check Appcelerator Titanium iOS Debugging with XCode or Rapid Titanium WebView debugging with Chrome Developer Tools

 

Want to stay connected on all things mobile?

 

Sign up for the Shockoe newsletter and we'll keep you updated with the latest blogs, podcasts, and events focused on emerging mobile trends.