User onboarding is a very important feature when you have a bunch of cool feature in your app. It’s a process of guiding your users to get more familiar with the app.

Let’s see the final output first.

Pretty cool! Right? So let’s get started.

UI Structure

  alignment: AlignmentDirectional.bottomCenter,
  children: <Widget>[

The Stack Widget is the best fit when we need to overlap one widget over other. So in our case, Stack has:

  • PageView.builder() to slide through different screens.
  • Stack() to contain a number of page indicators.
  • Visibility() to hide and show ‘Arrow’ button.

Now let’s see each widget in detail.

1) PageView.builder();

Creates horizontal/vertical slidable widget.

First of all, we will need to create a list of widgets. This list may contain different screens that will show up when sliding. It can be created like this

final List<Widget> introWidgetsList = <Widget>[

We can also create one common widget/screen and change the images based on passed params. This is optional but good to have.

  physics: ClampingScrollPhysics(),
  itemCount: introWidgetsList.length,
  onPageChanged: (int page) {
  controller: controller,
  itemBuilder: (context, index) {
    return introWidgetsList[index];

Now we can provide the list details to itemCount and itemBuilder property of PageView.builder. onPageChanged property is used to notify when pages change so that we can show the appropriate page indicator.

2) Stack();

I have used stack but you can use any widget to show page indicators.

  alignment: AlignmentDirectional.topStart,
  children: <Widget>[
      margin: EdgeInsets.only(bottom: 35),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          for (int i = 0; i < introWidgetsList.length; i++)
            if (i == currentPageValue) ...[circleBar(true)] else

Stack further contains the Row() that aligns the page indicators besides each other and circleBar(true/false) is the actual widget that shows Page indicator or Dot. Using Dart’s 2.3 new feature Collection for and Collection If we can show a number of circleBar() as the number of screens and pass True or False to notify whether isActive.

Let’s have a deeper look inside it.

Widget circleBar(bool isActive) {
  return AnimatedContainer(
    duration: Duration(milliseconds: 150),
    margin: EdgeInsets.symmetric(horizontal: 8),
    height: isActive ? 12 : 8,
    width: isActive ? 12 : 8,
    decoration: BoxDecoration(
        color: isActive ? kOrange : klightGrey,
        borderRadius: BorderRadius.all(Radius.circular(12))),

As you can see it’s just AnimatedContainer that changes its height and width and color based on the isActive parameter.

3) Visibility;

This widget controls the visibility of its child, and in our case its Floating action button.

  visible: currentPageValue == introWidgetsList.length - 1
      ? true
      : false,
  child: FloatingActionButton(
    onPressed: () {
    shape: BeveledRectangleBorder(
        borderRadius: BorderRadius.all(Radius.circular(26))),
    child: Icon(Icons.arrow_forward),

FloatingActionButton gets visible only when the last screen is showing up.

Finally here comes the magic.

Here is the implementation for the getChangedPageAndMoveBar that we are calling from onPageChange of PageView.

void getChangedPageAndMoveBar(int page) {
  currentPageValue = page;
  setState(() {});

Setting currentPageValue as of currently displayed screen on PageView and setState(() {}); will do rest of the magic. I would suggest you please try it from here.

That’s it.

I hope you have understood the basic idea of implementing this.

Thanks for reading this article. If you like it, click on 👏 to rate it out of 50and also share with your friends. It means a lot to me.

For more about programming, follow me and Aubergine Solutions, so you’ll get notified when we write new posts.

Would you like to check out my other articles

Little about me 🙂