Onboarding screens with Flutter using PageView.builder

Faiz Rhm
5 min readJul 9, 2023

--

Creating a seamless and engaging onboarding experience is crucial for mobile app success in today's digital landscape. They serve as an introduction and guide for users who are using an app for the first time. Onboarding screens provide essential information about the app’s features, benefits, and functionality, helping users navigate and understand how to make the most out of the app.

Flutter, the popular cross-platform framework, provides developers with a versatile toolkit for creating beautiful and interactive user interfaces. This article will explore how to leverage the Flutter framework to create captivating intro screens that guide users through the app’s features, promote user engagement, and ensure a delightful onboarding experience.

Setup

Create a new project with the command below

flutter create your_app_name

Open the folder on your preferred text editor and open the main.dart which is in the lib folder and replace the code with:

import 'package:flutter/material.dart';
import 'package:intro/intro_screen.dart';

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

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

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Intro',
theme: ThemeData(

primarySwatch: Colors.blue,
),
home: const IntroScreen(),
);
}
}

PageView.builder

The PageView.builder widget provides a convenient way to dynamically build pages within a PageView. This widget is useful when you have a large number of pages or when the pages need to be generated on-the-fly based on a data source.

Initializing PageController

Inside the stateful widget create a controller variable whose type is of PageViewController and another int variable to hold the current index of the page

final PageController  _pageController = PageController();

int _activePage = 0;

Data Source

Define the data source that will be used to generate the pages. This could be a list of objects, a list of URLs, or any other data structure that represents the content of each page.

final List<Map<String, dynamic>> _pages = [
{
'color': '#ffe24e',
'title': 'Hmmm, Healthy food',
'image': 'assets/images/image1.png',
'description': "A variety of foods made by the best chef. Ingredients are easy to find, all delicious flavors can only be found at cookbunda",
'skip': true
},
{
'color': '#a3e4f1',
'title': 'Fresh Drinks, Stay Fresh',
'image': 'assets/images/image2.png',
'description': 'Not all food, we provide clear healthy drink options for you. Fresh taste always accompanies you',
'skip': true
},
{
'color': '#31b77a',
'title': 'Let\'s Cooking',
'image': 'assets/images/image3.png',
'description': 'Are you ready to make a dish for your friends or family? create an account and cooks',
'skip': false
},
];

Building Screens

Create a PageView.builder widget and specify the itemBuilder property in your widget's build method. The itemBuilder function will be called for each page, and you can use the index parameter to access the corresponding data from the data source.

PageView.builder(
controller: _pageController,
itemCount: _pages.length,
onPageChanged: (int page) {
setState(() {
_activePage = page;
});
},
itemBuilder: (BuildContext context, int index){
return IntroWidget(
color: _pages[index]['color'],
title: _pages[index]['title'],
description: _pages[index]['description'],
image: _pages[index]['image'],
skip: _pages[index]['skip'],
onTab: onNextPage,
);
}
),

Adding Indicators

It is common in different applications with onboarding screens to see indicators to which screen is currently viewed. The PageView provides a function that listens to the change in screen and returns the index of the current screen. You will create a custom stateless widget for the indicator, and place it in the view. Create the Indicator widget with the code below outside the MyHomePage

Positioned(
top: MediaQuery.of(context).size.height / 1.75,
right: 0,
left: 0,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _buildIndicator()
)
],
),
)
//Indicator Builder
List<Widget> _buildIndicator() {
final indicators = <Widget>[];

for (var i = 0; i < _pages.length; i++) {

if (_activePage == i) {
indicators.add(_indicatorsTrue());
} else {
indicators.add(_indicatorsFalse());
}
}
return indicators;
}

// Changes colors based on screen
Widget _indicatorsTrue() {
final String color;
if (_activePage == 0) {
color = '#ffe24e';
} else if(_activePage == 1) {
color = '#a3e4f1';
} else {
color = '#31b77a';
}

//Active Indicator
return AnimatedContainer(
duration: const Duration(microseconds: 300),
height: 6,
width: 42,
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: hexToColor(color),
),
);
}

//Inactive Indicator
Widget _indicatorsFalse() {
return AnimatedContainer(
duration: const Duration(microseconds: 300),
height: 8,
width: 8,
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: Colors.grey.shade100,
),
)
}

Now you have to wrap the PageView widget in Stack widgets so you can position another thing on the screen and where ever you wish to place it.

Stack(
children: [
PageView.builder(
controller: _pageController,
itemCount: _pages.length,
onPageChanged: (int page) {
setState(() {
_activePage = page;
});
},
itemBuilder: (BuildContext context, int index){
return IntroWidget(
color: _pages[index]['color'],
title: _pages[index]['title'],
description: _pages[index]['description'],
image: _pages[index]['image'],
skip: _pages[index]['skip'],
onTab: onNextPage,
);
}
),
Positioned(
top: MediaQuery.of(context).size.height / 1.75,
right: 0,
left: 0,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _buildIndicator()
)
],
),
)

],
),

Now you will create a method to update the index of the page once it is changed

void onNextPage(){
if(_activePage < _pages.length - 1) {
_pageController.nextPage(duration: const Duration(milliseconds: 500), curve: Curves.linear,);
}
}

The PageView.builder widget is efficient as it only builds and renders the pages that are currently visible, making it suitable for cases where you have a large number of pages or when the pages are generated dynamically based on user input or data.

You can find a working example down here 👇 👇

Watch it on Youtube

I hope you found this article enjoyable! If you appreciate the information provided, you have the option to support me by Buying Me A Coffee! Your gesture would be greatly appreciated!

Follow Me

Thank you for taking the time to read this article. If you enjoyed it, feel free to explore more of my articles and consider following me for future updates. Your support is greatly appreciated!

Thank you for reading until the end. Before you go:

--

--