How to build TikTok with Flutter?
In this tutorial series we'll clone TikTok using Flutter.

Along the way we'll learn how to use the core widgets of Flutter.
- Col
- Row
- Material
- Scaffold
- AppBar
- PageView
- Drawer
- BottomNavigationBar
- SafeArea
- ListView
- GestureDetector
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.
Create New Project
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 12 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.
- Line 2 Import a custom widget,
DrawerNav. - Line 14 Pass the
DrawerNavwidget as a parameter toMaterialApp, specifically thehomeparameter. - Line 15 Hide the debug banner by passing
falseto thedebugShowCheckedModeBannerparameter ofMaterialApp.
Create Drawer Navigator
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 10 State<TikTokPage> createState() => _TikTokPageState();11}12 13class _TikTokPageState extends State<TikTokPage> {14 15 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 24 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 45 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 7 State<TikTokPage> createState() => _TikTokPageState();8}9 10class _TikTokPageState extends State<TikTokPage> {11 12 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.
- Line 2: Define
colorproperty for theTikTokPageclass/widget. - Line 4: Require the
colorparameter in the constructor of theTikTokPagewidget. - Line 2: Consume the
colorparameter in the build method ofTikTokPage, producing a dynamic background colors for each page/screen.
This pattern/technique is common with other JS frameworks such as React & Vue as well.
Create Bottom Navigator Bar
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 19 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}- Line 2: Define
_selectedIndexproperty which defaults to 0. This is which tab is selected. - Lines 4-10: Define array of page widgets which will be the pages/screens in the bottom navigation bar. Notice how each instance of TikTokPage is passed a different
colorparameter. - Lines 12-16: Define handler for when a tab bar item is tapped by the user.
- Lines 21: Select one page/screen inside of,
_widgetOptions, to pass to theScaffoldwidget'sbodyparameter. We use the_selectedIndexstate variable to identify which element/item/page/screen to pass. - Lines 24: Pass
_onItemTappedto the parameteronTapof `Scaffold. Once again this handles changing the page/screen viewed/focused. - Lines 29-50: Define the bottom navigation bar items including their label and icon.
Refactor TikTokPage
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); State<TikTokPage> createState() => _TikTokPageState();} class _TikTokPageState extends State<TikTokPage> { 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';Create Media Content Widget
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 5 State<MediaContent> createState() => _MediaContentState();6}7 8class _MediaContentState extends State<MediaContent> {9 10 Widget build(BuildContext context) {11 return Container(12 color: Colors.red,13 margin: const EdgeInsets.all(5),14 );15 }16}- Line 12: Give the screen a red background so we can more easily identify it.
- Line 13: Give the screen margin so we can more easily identify it.
Create Vertical Scroll through the list of videos
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 7 State<TikTokPage> createState() => _TikTokPageState();8}9 10class _TikTokPageState extends State<TikTokPage> {11 PageController controller = PageController(initialPage: 0);12 13 14 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
PageControllerwith aninitialPageof 0 which we'll use to control thePageView. -
Line 16: Pass the
PageControllerto thecontrollerparameter ofPageView. -
Line 19: Use a loop to create several instances of
MediaContentfor testing.

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