Jane Doe
Pro Plan
In this tutorial series we'll clone TikTok using Flutter.

Along the way we'll learn how to use the core widgets of Flutter.
We'll begin by implementing the navigation features of TikTok.
Specifically, we'll create a BottomNavigationBar. This navigation bar will allow the user to access different content on different screens.
Instantiate a new project and run it.
flutter create fluttokcd fluttokflutter runWe're using Flutter 3.0.2, Dart 2.17.3 • DevTools 2.12.2
Replace everything inside of main.dart
1import 'package:flutter/material.dart';2import 'package:fluttok/navigation/DrawerNav.dart';3 4main() {5 runApp(const MyApp());6}7 8class MyApp extends StatelessWidget {9 const MyApp({super.key});10 11 @override12 Widget build(BuildContext context) {13 return const MaterialApp(14 home: DrawerNav(),15 debugShowCheckedModeBanner: false,16 );17 }18}We remove all the comments and do the following.
DrawerNav.DrawerNav widget as a parameter to MaterialApp, specifically the home parameter.false to the debugShowCheckedModeBanner parameter of MaterialApp.Structure/place project folders and files following best practice.
.├── ...├── pubspec.yaml└── lib/ ├── navigation/ # Create this folder │ └── DrawerNav.dart # Create this file └── main.dartThe path matches the import statement we used a moment ago, where lib is the name of our project, fluttok.
import 'package:fluttok/navigation/DrawerNav.dart';Define DrawerNav as a stateful widget inside of DrawerNav.dart. We need it to be stateful so we can navigate between the multiple pages/screens using the BottomNavigationBar
1// ./lib/navigation/DrawerNav.dart2import 'package:flutter/material.dart';3 4class TikTokPage extends StatefulWidget {5 final MaterialColor color;6 7 const TikTokPage({Key? key, required this.color}) : super(key: key);8 9 @override10 State<TikTokPage> createState() => _TikTokPageState();11}12 13class _TikTokPageState extends State<TikTokPage> {14 @override15 Widget build(BuildContext context) {16 return Container(color: widget.color);17 }18}19 20class DrawerNav extends StatefulWidget {21 const DrawerNav({Key? key}) : super(key: key);22 23 @override24 State<DrawerNav> createState() => _DrawerNav();25}26 27class _DrawerNav extends State<DrawerNav> {28 int _selectedIndex = 0;29 30 static List<Widget> get _widgetOptions => <Widget>[31 const TikTokPage(color: Colors.yellow),32 const TikTokPage(color: Colors.blue),33 const TikTokPage(color: Colors.green),34 const TikTokPage(color: Colors.teal),35 const TikTokPage(color: Colors.pink),36 ];37 38 void _onItemTapped(int index) {39 setState(() {40 _selectedIndex = index;41 });42 }43 44 @override45 Widget build(BuildContext context) {46 return Scaffold(47 body: _widgetOptions.elementAt(_selectedIndex),48 bottomNavigationBar: BottomNavigationBar(49 elevation: 0,50 onTap: _onItemTapped,51 showUnselectedLabels: true,52 currentIndex: _selectedIndex,53 type: BottomNavigationBarType.fixed,54 selectedItemColor: Colors.black87,55 items: const <BottomNavigationBarItem>[56 BottomNavigationBarItem(57 label: 'Home',58 icon: Icon(Icons.home),59 ),60 BottomNavigationBarItem(61 label: 'Discover',62 icon: Icon(Icons.arrow_circle_up_sharp),63 ),64 BottomNavigationBarItem(65 label: '',66 icon: Icon(Icons.add)67 ),68 BottomNavigationBarItem(69 label: 'Inbox',70 icon: Icon(Icons.inbox),71 ),72 BottomNavigationBarItem(73 label: 'Profile',74 icon: Icon(Icons.account_box_rounded),75 ),76 ],77 ),78 );79 }80}Refresh and we'll our app works and has a bottom navigation bar, awesome.

There's a lot going on so let's review.
1class TikTokPage extends StatefulWidget {2 final MaterialColor color;3 4 const TikTokPage({Key? key, required this.color}) : super(key: key);5 6 @override7 State<TikTokPage> createState() => _TikTokPageState();8}9 10class _TikTokPageState extends State<TikTokPage> {11 @override12 Widget build(BuildContext context) {13 return Container(color: widget.color);14 }15}The TikTokPage widget is a placeholder. The interesting part of this widget is that it requires a parameter color when created.
color property for the TikTokPage class/widget.color parameter in the constructor of the TikTokPage widget.color parameter in the build method of TikTokPage, producing a dynamic background colors for each page/screen.This pattern/technique is common with other JS frameworks such as React & Vue as well.
Key use of the Bottom Navigation Bar and it's required parameters/properties.
1class _DrawerNav extends State<DrawerNav> {2 int _selectedIndex = 0;3 4 static List<Widget> get _widgetOptions => <Widget>[5 const TikTokPage(color: Colors.yellow),6 const TikTokPage(color: Colors.blue),7 const TikTokPage(color: Colors.green),8 const TikTokPage(color: Colors.teal),9 const TikTokPage(color: Colors.pink),10 ];11 12 void _onItemTapped(int index) {13 setState(() {14 _selectedIndex = index;15 });16 }17 18 @override19 Widget build(BuildContext context) {20 return Scaffold(21 body: _widgetOptions.elementAt(_selectedIndex),22 bottomNavigationBar: BottomNavigationBar(23 elevation: 0,24 onTap: _onItemTapped,25 showUnselectedLabels: true,26 currentIndex: _selectedIndex,27 type: BottomNavigationBarType.fixed,28 selectedItemColor: Colors.black87,29 items: const <BottomNavigationBarItem>[30 BottomNavigationBarItem(31 label: 'Home',32 icon: Icon(Icons.home),33 ),34 BottomNavigationBarItem(35 label: 'Discover',36 icon: Icon(Icons.arrow_circle_up_sharp),37 ),38 BottomNavigationBarItem(39 label: '',40 icon: Icon(Icons.add)41 ),42 BottomNavigationBarItem(43 label: 'Inbox',44 icon: Icon(Icons.inbox),45 ),46 BottomNavigationBarItem(47 label: 'Profile',48 icon: Icon(Icons.account_box_rounded),49 ),50 ],51 ),52 );53 }54}_selectedIndex property which defaults to 0. This is which tab is selected.color parameter._widgetOptions, to pass to the Scaffold widget's body parameter. We use the _selectedIndex state variable to identify which element/item/page/screen to pass._onItemTapped to the parameter onTap of `Scaffold. Once again this handles changing the page/screen viewed/focused.Let's extract the TikTokPage widget to it's own file so we can follow best practices.
.├── ...├── pubspec.yaml└── lib/ ├── navigation/ │ └── DrawerNav.dart ├── pages/ # Create this folder │ └── TikTokPage.dart # Create this file └── main.dartCut and paste the TikTokPage widget to the TikTokPage.dart file.
import 'package:flutter/material.dart'; class TikTokPage extends StatefulWidget { final MaterialColor color; const TikTokPage({Key? key, required this.color}) : super(key: key); @override State<TikTokPage> createState() => _TikTokPageState();} class _TikTokPageState extends State<TikTokPage> { @override Widget build(BuildContext context) { return Container(color: widget.color); }}Import the TikTokPage file into the DrawerNav.dart file
// ./lib/navigation/DrawerNav.dartimport 'package:flutter/material.dart';import 'package:fluttok/pages/TikTokPage.dart';Above the TikTokPage widget, create a new MediaContent widget which will contain the logic for each of our videos.
1class MediaContent extends StatefulWidget {2 const MediaContent({Key? key}) : super(key: key);3 4 @override5 State<MediaContent> createState() => _MediaContentState();6}7 8class _MediaContentState extends State<MediaContent> {9 @override10 Widget build(BuildContext context) {11 return Container(12 color: Colors.red,13 margin: const EdgeInsets.all(5),14 );15 }16}Refactor TikTokPage to have a PageView in it's build method. This widget implements vertical scroll with the use of a controller.
1class TikTokPage extends StatefulWidget {2 final MaterialColor color;3 4 const TikTokPage({Key? key, required this.color}) : super(key: key);5 6 @override7 State<TikTokPage> createState() => _TikTokPageState();8}9 10class _TikTokPageState extends State<TikTokPage> {11 PageController controller = PageController(initialPage: 0);12 13 @override14 Widget build(BuildContext context) {15 return PageView(16 controller: controller,17 scrollDirection: Axis.vertical,18 children: [19 for (var i = 0; i < 5; i++) const MediaContent(),20 ],21 );22 }23}Line 11: Instantiate a PageController with an initialPage of 0 which we'll use to control the PageView.
Line 16: Pass the PageController to the controller parameter of PageView.
Line 19: Use a loop to create several instances of MediaContent for testing.

We should now see that we can scroll vertically, excellent.