Kinda Code
Home/Flutter/Flutter: 2 Ways to Create an Onboarding Flow (Intro Slider)

Flutter: 2 Ways to Create an Onboarding Flow (Intro Slider)

Last updated: February 12, 2023

The onboarding process in a mobile app is a series of introductory screens (or an intro slider) that aim to educate and familiarize new users with the app’s functionality and interface. This practical article will show you 2 different ways to implement a professional onboarding flow in Flutter. In the first approach, we will write code from scratch, while in the second one, we will take advantage of using a third-party package.

Without putting you through any more unhelpful talk, let’s get started.

Using self-written code

Not depending on a third-party library allows you to customize everything you want.

App Preview

The demo project we’re going to build has an intro slider that appears right when the user starts the app for the first time. This slider includes 3 pages. On each page, there is a pair of ‘previous’ and ‘next’ buttons for navigating.

  • On the first page: The ‘previous’ button is disabled.
  • On the last page: The ‘next’ button is replaced with a ‘Done’ button. If this button is pressed, the user will leave the intro flow and go to the Home page.

Here’s how it works:

The Code

The complete code with explanations in main.dart:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Remove the debug banner
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const KindaCodeDemo(),
    );
  }
}

class KindaCodeDemo extends StatefulWidget {
  const KindaCodeDemo({super.key});

  @override
  State<KindaCodeDemo> createState() => _KindaCodeDemoState();
}

class _KindaCodeDemoState extends State<KindaCodeDemo> {
  // Current page
  int _currentPage = 0;

  // page controller
  final PageController _pageController = PageController(initialPage: 0);

  final List<Widget> _pages = [
    // Page 1
    Container(
      color: Colors.amberAccent,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: const [
          SizedBox(height: 20),
          Text("Welcome to KindaCode.com",
              style: TextStyle(color: Colors.black, fontSize: 24)),
          // add more things here if you want
        ],
      ),
    ),
    // Page 2
    Container(
      color: Colors.red,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: const [
          SizedBox(height: 20),
          Text("Discover new experiences!",
              style: TextStyle(color: Colors.white, fontSize: 24)),
          // add more things here if you want
        ],
      ),
    ),
    // Page 3
    Container(
      color: Colors.green,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: const [
          SizedBox(height: 20),
          Text("Stay connected with code!",
              style: TextStyle(color: Colors.white, fontSize: 24)),
          // add more things here if you want
        ],
      ),
    )
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        // use a PageView widget to display the pages
        body: PageView(
          controller: _pageController,
          onPageChanged: (int page) {
            setState(() {
              _currentPage = page;
            });
          },
          children: _pages,
        ),
        // use a Stack widget to display the "previous" and "next" buttons
        floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        floatingActionButton: Stack(
          fit: StackFit.expand,
          children: [
            Positioned(
              left: 30,
              bottom: 20,
              child: FloatingActionButton(
                heroTag: 'previous',
                // if the current page is the first page, disable the button
                backgroundColor: _currentPage > 0 ? Colors.blue : Colors.grey,
                onPressed: _currentPage > 0
                    ? () {
                        // if the current page is not the first page, navigate to the previous page
                        // otherwise, do nothing
                        setState(() {
                          _currentPage -= 1;
                        });
                        _pageController.previousPage(
                            duration: const Duration(milliseconds: 500),
                            curve: Curves.ease);
                      }
                    : null,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10),
                ),
                child: const Icon(
                  Icons.arrow_left,
                  size: 40,
                ),
              ),
            ),
            Positioned(
              bottom: 20,
              right: 30,
              child: FloatingActionButton(
                heroTag: 'next',
                onPressed: _currentPage < _pages.length - 1
                    ? () {
                        // if the current page is not the last page, navigate to the next page
                        setState(() {
                          _currentPage += 1;
                        });

                        _pageController.nextPage(
                            duration: const Duration(milliseconds: 500),
                            curve: Curves.ease);
                      }
                    : () {
                        // if the current page is the last page, navigate to the home screen
                        Navigator.of(context).pushReplacement(MaterialPageRoute(
                            builder: (_) => const HomeScreen()));
                      },
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10),
                ),
                child: Icon(
                  _currentPage < _pages.length - 1
                      ? Icons.arrow_right
                      : Icons.done,
                  // if the current page is the last page, show the done icon
                  // otherwise, show the arrow right icon
                  size: 40,
                ),
              ),
            ),
          ],
        ));
  }
}

// the home screen
// you will go here after you finish the onboarding screen
// there is no way to go back to the onboarding screen
// unless you restart the app
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('KindaCode Home')),
      body: const Center(
        child: Text('Home Screen', style: TextStyle(fontSize: 40)),
      ),
    );
  }
}

Using a third-party library

There are several open-source libraries for creating beautiful intro sliders. One of the best libraries is intro_slider.

App Preview

The Code

Install the plugin by running this command:

flutter pub add intro_slider

And here’s the full source code in main.dart:

import 'package:flutter/material.dart';

// import the plugin
import 'package:intro_slider/intro_slider.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const type = 1;

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      // remove the debug banner
      debugShowCheckedModeBanner: false,
      title: 'KindaCode.com',
      home: KindaCodeDemo(),
    );
  }
}

class KindaCodeDemo extends StatefulWidget {
  const KindaCodeDemo({Key? key}) : super(key: key);

  @override
  State<KindaCodeDemo> createState() => _KindaCodeDemoState();
}

class _KindaCodeDemoState extends State<KindaCodeDemo> {
  final List<ContentConfig> _listContentConfig = [];

  @override
  void initState() {
    super.initState();

    // add content for the first slide
    _listContentConfig.add(
      const ContentConfig(
        title: "HEADING ONE",
        description:
            "I like playing Elden Ring and God of War. They are both great games.",
        backgroundColor: Colors.pink,
      ),
    );
    // add content for the second slide
    _listContentConfig.add(
      const ContentConfig(
        title: "HEADING TWO",
        description:
            "Never forgive. Never forget. The rain is coming. I see bad days and liars ahead.",
        backgroundColor: Colors.greenAccent,
      ),
    );
    // add content for the third slide
    _listContentConfig.add(
      const ContentConfig(
        title: "HEADING THREE",
        description: "Hesitation is defeat. Come on Sekiro. Yo",
        backgroundColor: Colors.orangeAccent,
      ),
    );
  }

  // This function will be called when the user press the "done" button
  // in the last slide
  void _onDonePress() {
    debugPrint('Done Pressed');
  }

  @override
  Widget build(BuildContext context) {
    return IntroSlider(
      key: UniqueKey(),
      listContentConfig: _listContentConfig,
      onDonePress: _onDonePress,
    );
  }
}

Conclusion

We’ve walked through 2 complete examples of implementing intro screens in Flutter. Which one will you choose to use? Please let us know by leaving a comment.

If you’d like to explore more new and exciting stuff in the realm of modern Flutter, take a look at the following articles:

You can also tour around our Flutter topic page or Dart topic page for the most recent tutorials and examples.