Saturday, June 30, 2018

Flutter - Tab bar controller and left drawer.

When you need to wrap multiple screen in a single screen and navigate through them, you have to use Tab and Drawer. When user select a tab, this object sends a message to the parent container for switch the displayed page. 

           Left drawer of mobile

Tab and Drawer provides all feature of an application on a single page. So, user can know about the app easily.

In this post, we'll see TabBar and Drawer implementation in Flutter Application. For achieve and maintain state of Application we'll use Redux architecture that we have discuss here

Here, we have complete project structure. 




Let's begin it's implementation with following steps.

1. Update pubspec.ymal dependencies.
pubspec.ymal
flutter_redux: ^0.5.0 intl: ^0.15.2 shared_preferences: ^0.4.0

2. Now, move on application entry point that is main.dart. Here, we have created store. It'll manage application state like selection of left drawer state. As we have explained all about Store in detail hereIf you have query regarding Redux. You should learn about it. Because, we going to use it in this project. As you can see, we have created store.
main.dart
Future<Null> main() async { var store = await createStore(); runApp(new BuyMeApp(store)); } 

After that create base and initialize state of application.
main.dart
class _BuyMeAppState extends State<BuyMeApp> { @override void initState() { super.initState(); widget.store.dispatch(new InitAction()); } @override Widget build(BuildContext context) { return new StoreProvider<AppState>( store: widget.store, child: new MaterialApp( title: 'BuyMe', theme: new ThemeData( primaryColor: const Color(0xFF02BB9F), primaryColorDark: const Color(0xFF167F67), accentColor: const Color(0xFFFFAD32), ), home: const HomeScreen(), ), ); } }

3. Create another dart file and name it home_screen.dart. Here, we'll create TabBar and Left Drawer menu. For handle Tabbar, we have created instance of TabController and given length 3 because we need three tab. 

For manage Drawer, we have created instance of Drawer and append instance of DrawerHeaderBox that'll contain header of drawer. As you can see complete file below.
home_screen.dart
import 'package:flutter/material.dart'; import 'package:flutter_drawer_tab_host/tab_screen.dart'; import 'package:flutter_drawer_tab_host/drawer/drawer_header.dart'; import 'package:flutter_drawer_tab_host/drawer/drawer_list.dart'; class HomeScreen extends StatefulWidget { const HomeScreen(); @override _HomeScreenState createState() => new _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateMixin { static final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(); TabController _controller; @override void initState() { super.initState(); _controller = new TabController(length: 3, vsync: this, ); } @override Widget build(BuildContext context) { return new Scaffold( key: scaffoldKey, appBar: new AppBar( iconTheme: new IconThemeData(color: const Color(0xFFFFFFFF),), title: new Text("BuyMe", style: new TextStyle( color: const Color(0xFFFFFFFF), fontSize: 20.0, fontWeight: FontWeight.w300, letterSpacing: 0.3, ), ), bottom: new TabBar( controller: _controller, indicatorSize: TabBarIndicatorSize.tab, labelColor: Color(0xFFFFFFFF), tabs: const <Tab>[ const Tab(text: 'Tranding'), const Tab(text: 'Men'), const Tab(text: 'Women'), ], ), ), drawer: new Drawer( child: new DrawerList( header: const DrawerHeaderBox(), onTheaterTapped: () => Navigator.pop(context), ), ), body: new TabBarView( controller: _controller, children: <Widget>[ new TabScreen("Tranding"), new TabScreen("Men"),
new TabScreen("Women"),
], ), ); } }



4. After that create tab_screen.dart file. It'll show tab screen content after switching. In this file, we just showing a text that display tab title. You can modify it according to your need.
tab_screen.dart
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class TabScreen extends StatelessWidget { TabScreen(this.listType); final String listType; @override Widget build(BuildContext context) { return new Scaffold( body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( listType, style: Theme.of(context).textTheme.display1, ), ], ), ), ); } }
5. After that create some redux dart that manage state of app.
app_reducer.dart
import 'package:flutter_drawer_tab_host/app_state.dart'; import 'package:flutter_drawer_tab_host/drawer/drawer_reducer.dart'; AppState appReducer(AppState state, dynamic action) { return new AppState( drawerState: drawerReducer(state.drawerState, action), ); }
app_state.dart
import 'package:flutter_drawer_tab_host/drawer/drawer_state.dart'; import 'package:meta/meta.dart'; @immutable class AppState { final DrawerState drawerState; AppState({ @required this.drawerState, }); factory AppState.initial() { return new AppState( drawerState: new DrawerState.initial(), ); } }

common_actions.dart
import 'package:flutter_drawer_tab_host/drawer/drawer_model.dart'; class InitAction {} class InitCompleteAction { InitCompleteAction( this.drawer, this.selectedDrawer, ); final List<DrawerModel> drawer; final DrawerModel selectedDrawer; } class ChangeCurrentTheaterAction { ChangeCurrentTheaterAction(this.selectedDrawer); final DrawerModel selectedDrawer; }

6. Now, Let's create our drawer screen. For it create a directory drawer. Here, We'll put all drawer files.



7. In drawer folder,  first of all create drawer_header.dart. Here, we using CircleAvatar, Image and two Text for screen header interface. 
drawer_header.dart
import 'package:flutter/material.dart'; class DrawerHeaderBox extends StatefulWidget { const DrawerHeaderBox(); @override _DrawerHeaderState createState() => new _DrawerHeaderState(); } class _DrawerHeaderState extends State<DrawerHeaderBox> { @override Widget build(BuildContext context) { var textTheme = Theme.of(context).textTheme; return new Container( color: const Color(0xFF02BB9F), constraints: const BoxConstraints.expand(height: 175.0), child: new Center( child: new Padding( padding: const EdgeInsets.all(16.0), child: new Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ new Stack( alignment: Alignment.center, children: <Widget>[ new CircleAvatar( radius: 30.0, backgroundColor: const Color(0xFF167F67), ), new Container( height: 30.0, width: 30.0, child: new Image.asset("assets/fevicn.png"), ) ], ), new Text( 'Developer Libs', style: textTheme.subhead.copyWith(color: Colors.white70), ), new Text( 'developerlibs@gmail.com', style: textTheme.subhead.copyWith(color: Colors.white70), ), ], ), ), ), ); } }


8. It's time to create dynamic list of drawer. To achieve it, Create drawer_list.dart. Here, We will display drawer list items.
drawer_list.dart
import 'package:flutter/material.dart'; import 'package:flutter_drawer_tab_host/app_state.dart'; import 'package:flutter_drawer_tab_host/drawer/drawer_list_view_model.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:meta/meta.dart'; class DrawerList extends StatelessWidget { DrawerList({ @required this.header, @required this.onItemTapped, }); final Widget header; final VoidCallback onItemTapped; @override Widget build(BuildContext context) { var statusBarHeight = MediaQuery.of(context).padding.vertical; return new Transform( transform: new Matrix4.translationValues(0.0, -statusBarHeight, 0.0), child: new StoreConnector<AppState, DrawerListViewModel>( distinct: true, converter: (store) => DrawerListViewModel.fromStore(store), builder: (BuildContext context, DrawerListViewModel viewModel) { return new DrawerListContent( header: header, onTheaterTapped: onItemTapped, viewModel: viewModel, ); }, ), ); } } class DrawerListContent extends StatelessWidget { DrawerListContent({ @required this.header, @required this.onTheaterTapped, @required this.viewModel, }); final Widget header; final VoidCallback onTheaterTapped; final DrawerListViewModel viewModel; @override Widget build(BuildContext context) { return new ListView.builder( itemCount: viewModel.drawer.length + 1, itemBuilder: (BuildContext context, int index) { if (index == 0) { return header; } var theater = viewModel.drawer[index - 1]; var isSelected = viewModel.currentDrawer.id == theater.id; var backgroundColor = isSelected ? const Color(0xFF01332b) : Theme.of(context).canvasColor; return new Material( color: backgroundColor, child: new Container( margin: EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 0.0), padding: EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 0.0), child: new Column( children: <Widget>[ new ListTile( onTap: () { viewModel.changeCurrentDrawer(theater); onTheaterTapped(); }, selected: isSelected, title: new Text( theater.name, style: new TextStyle( color: isSelected ? const Color(0xFFFFFFFF) : const Color(0xFF01332b), fontSize: 20.0, fontWeight: FontWeight.w300, letterSpacing: 0.3, ), ), ) ], ), decoration: new BoxDecoration(border: new Border(bottom: new BorderSide())), ), ); }, ); } }




9. Now create drawer_list_view_model.dart file. Here, we maintain state and keep drawer resource.
drawer_list_view_model.dart
import 'package:flutter_drawer_tab_host/app_state.dart'; import 'package:flutter_drawer_tab_host/common_actions.dart'; import 'package:flutter_drawer_tab_host/drawer/drawer_model.dart'; import 'package:flutter_drawer_tab_host/drawer/drawer_selectors.dart'; import 'package:meta/meta.dart'; import 'package:redux/redux.dart'; class DrawerListViewModel { DrawerListViewModel({ @required this.currentDrawer, @required this.drawer, @required this.changeCurrentDrawer, }); final DrawerModel currentDrawer; final List<DrawerModel> drawer; final Function(DrawerModel) changeCurrentDrawer; static DrawerListViewModel fromStore(Store<AppState> store) { return new DrawerListViewModel( currentDrawer: currentDrawerSelector(store.state), drawer: drawerSelector(store.state), changeCurrentDrawer: (theater) { store.dispatch(new ChangeCurrentTheaterAction(theater)); }, ); } }

10. After that create drawer_middleware.dart file. Here, You can change list item text and add new. In this file, we have write some code redux as discuss above.
drawer_middleware.dart
import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_drawer_tab_host/app_state.dart'; import 'package:flutter_drawer_tab_host/common_actions.dart'; import 'package:flutter_drawer_tab_host/drawer/drawer_model.dart'; import 'package:redux/redux.dart'; import 'package:shared_preferences/shared_preferences.dart'; class DrawerMiddleware extends MiddlewareClass<AppState> { static const String kDefaultTheaterId = 'default_theater_id'; final AssetBundle bundle; final SharedPreferences preferences; DrawerMiddleware(this.bundle, this.preferences); @override Future<Null> call(Store<AppState> store, action, NextDispatcher next) async { if (action is InitAction) { await _init(action, next); } else if (action is ChangeCurrentTheaterAction) { await _changeCurrentTheater(action, next); } else { next(action); } } Future<Null> _init( InitAction action, NextDispatcher next, ) async { List<DrawerModel> allTheaters = new List(); DrawerModel theater = new DrawerModel(id: "1030", name: "Electronics"); DrawerModel theater1 = new DrawerModel(id: "1014", name: "Fashion"); DrawerModel theater2 = new DrawerModel(id: "1012", name: "Furniture"); DrawerModel theater3 = new DrawerModel(id: "1039", name: "Sports"); DrawerModel theater4 = new DrawerModel(id: "1038", name: "Books"); DrawerModel theater5 = new DrawerModel(id: "1031", name: "Medical"); DrawerModel theater6 = new DrawerModel(id: "1011", name: "Vegetable"); DrawerModel theater7 = new DrawerModel(id: "1015", name: "Drink"); DrawerModel theater8 = new DrawerModel(id: "1016", name: "Watch"); allTheaters.add(theater); allTheaters.add(theater1); allTheaters.add(theater2); allTheaters.add(theater3); allTheaters.add(theater4); allTheaters.add(theater5); allTheaters.add(theater6); allTheaters.add(theater7); allTheaters.add(theater8); next(new InitCompleteAction(allTheaters, allTheaters.first)); } Future<Null> _changeCurrentTheater( ChangeCurrentTheaterAction action, NextDispatcher next, ) async { preferences.setString(kDefaultTheaterId, action.selectedDrawer.id); next(action); } }
11. Create drawer_model.dart  and put following model code.
drawer_model.dart
import 'package:meta/meta.dart'; class DrawerModel { DrawerModel({ @required this.id, @required this.name, }); final String id; final String name; }

12. Create drawer_reducer.dart. It will change state of drawer on basis of event.
drawer_reducer.dart
import 'package:flutter_drawer_tab_host/common_actions.dart'; import 'package:flutter_drawer_tab_host/drawer/drawer_state.dart'; import 'package:redux/redux.dart'; final drawerReducer = combineReducers<DrawerState>([ new TypedReducer<DrawerState, InitCompleteAction>(_initComplete), new TypedReducer<DrawerState, ChangeCurrentTheaterAction>( _currentTheaterChanged), ]); DrawerState _initComplete(DrawerState state, InitCompleteAction action) { return state.copyWith( currentTheater: action.selectedDrawer, theaters: action.drawer, ); } DrawerState _currentTheaterChanged(DrawerState state, ChangeCurrentTheaterAction action) { return state.copyWith(currentTheater: action.selectedDrawer); }
13. Create drawer_selectors.dart. It will maintain state of selected drawer item. 
drawer_selectors.dart
import 'package:flutter_drawer_tab_host/app_state.dart'; import 'package:flutter_drawer_tab_host/drawer/drawer_model.dart'; DrawerModel currentDrawerSelector(AppState state) => state.drawerState.currentTheater; List<DrawerModel> drawerSelector(AppState state) => state.drawerState.theaters;

14. Create drawer_state.dart. Here, we'll keep state of drawer.
drawer_state.dart
import 'package:flutter_drawer_tab_host/drawer/drawer_model.dart'; import 'package:meta/meta.dart'; @immutable class DrawerState { DrawerState({ @required this.currentTheater, @required this.theaters, }); final DrawerModel currentTheater; final List<DrawerModel> theaters; factory DrawerState.initial() { return new DrawerState( currentTheater: null, theaters: <DrawerModel>[], ); } DrawerState copyWith({ DrawerModel currentTheater, List<DrawerModel> theaters, }) { return new DrawerState( currentTheater: currentTheater ?? this.currentTheater, theaters: theaters ?? this.theaters, ); } @override bool operator ==(Object other) => identical(this, other) || other is DrawerState && runtimeType == other.runtimeType && currentTheater == other.currentTheater && theaters == other.theaters; @override int get hashCode => currentTheater.hashCode ^ theaters.hashCode; }

At the end,  merge all file and run it. You'll see 





You can get complete working project from Github. If you any query regarding above project feel free to ask it in comment section below.


Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...