- Published on
 
Project: How to build TikTok with Flutter? Part 1
- Authors
 - Name
 - Loi Tran
 
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.
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 fluttok
cd fluttok
flutter run
We're using Flutter 3.0.2, Dart 2.17.3 • DevTools 2.12.2
Replace everything inside of main.dart
import 'package:flutter/material.dart';
import 'package:fluttok/navigation/DrawerNav.dart';
main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: DrawerNav(),
      debugShowCheckedModeBanner: false,
    );
  }
}
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.dart
The 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
// ./lib/navigation/DrawerNav.dart
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);
  }
}
class DrawerNav extends StatefulWidget {
  const DrawerNav({Key? key}) : super(key: key);
  
  State<DrawerNav> createState() => _DrawerNav();
}
class _DrawerNav extends State<DrawerNav> {
  int _selectedIndex = 0;
  static List<Widget> get _widgetOptions => <Widget>[
    const TikTokPage(color: Colors.yellow),
    const TikTokPage(color: Colors.blue),
    const TikTokPage(color: Colors.green),
    const TikTokPage(color: Colors.teal),
    const TikTokPage(color: Colors.pink),
  ];
  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: _widgetOptions.elementAt(_selectedIndex),
      bottomNavigationBar: BottomNavigationBar(
        elevation: 0,
        onTap: _onItemTapped,
        showUnselectedLabels: true,
        currentIndex: _selectedIndex,
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.black87,
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            label: 'Home',
            icon: Icon(Icons.home),
          ),
          BottomNavigationBarItem(
            label: 'Discover',
            icon: Icon(Icons.arrow_circle_up_sharp),
          ),
          BottomNavigationBarItem(
            label: '',
            icon: Icon(Icons.add)
          ),
          BottomNavigationBarItem(
            label: 'Inbox',
            icon: Icon(Icons.inbox),
          ),
          BottomNavigationBarItem(
            label: 'Profile',
            icon: Icon(Icons.account_box_rounded),
          ),
        ],
      ),
    );
  }
}
Refresh and we'll our app works and has a bottom navigation bar, awesome.

There's a lot going on so let's review.
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);
  }
}
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.
class _DrawerNav extends State<DrawerNav> {
  int _selectedIndex = 0;
  static List<Widget> get _widgetOptions => <Widget>[
    const TikTokPage(color: Colors.yellow),
    const TikTokPage(color: Colors.blue),
    const TikTokPage(color: Colors.green),
    const TikTokPage(color: Colors.teal),
    const TikTokPage(color: Colors.pink),
  ];
  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: _widgetOptions.elementAt(_selectedIndex),
      bottomNavigationBar: BottomNavigationBar(
        elevation: 0,
        onTap: _onItemTapped,
        showUnselectedLabels: true,
        currentIndex: _selectedIndex,
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.black87,
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            label: 'Home',
            icon: Icon(Icons.home),
          ),
          BottomNavigationBarItem(
            label: 'Discover',
            icon: Icon(Icons.arrow_circle_up_sharp),
          ),
          BottomNavigationBarItem(
            label: '',
            icon: Icon(Icons.add)
          ),
          BottomNavigationBarItem(
            label: 'Inbox',
            icon: Icon(Icons.inbox),
          ),
          BottomNavigationBarItem(
            label: 'Profile',
            icon: Icon(Icons.account_box_rounded),
          ),
        ],
      ),
    );
  }
}
- 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.dart
Cut 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.dart
import '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.
class MediaContent extends StatefulWidget {
  const MediaContent({Key? key}) : super(key: key);
  
  State<MediaContent> createState() => _MediaContentState();
}
class _MediaContentState extends State<MediaContent> {
  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      margin: const EdgeInsets.all(5),
    );
  }
}
- 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.
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> {
  PageController controller = PageController(initialPage: 0);
  
  Widget build(BuildContext context) {
    return PageView(
      controller: controller,
      scrollDirection: Axis.vertical,
      children: [
        for (var i = 0; i < 5; i++) const MediaContent(),
      ],
    );
  }
}
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.