Published on

How to build TikTok with Flutter? Part 1

Authors

How to build TikTok with Flutter?

In this tutorial series we'll clone TikTok using Flutter.

TikTok Demo

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 DrawerNav widget as a parameter to MaterialApp, specifically the home parameter.
  • Line 15 Hide the debug banner by passing false to the debugShowCheckedModeBanner parameter of MaterialApp.

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.

Bottom Tab Nav

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 color property for the TikTokPage class/widget.
  • Line 4: Require the color parameter in the constructor of the TikTokPage widget.
  • Line 2: Consume the 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.

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 _selectedIndex property 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 color parameter.
  • 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 the Scaffold widget's body parameter. We use the _selectedIndex state variable to identify which element/item/page/screen to pass.
  • Lines 24: Pass _onItemTapped to the parameter onTap of `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 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.

Bottom Tab Nav

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

Need help? Have a better implementation? Comment below