Flutter Riverpod Onboard Screen with PageView and Dots Indicator

Created At: 2023-03-05 10:13:19 Updated At: 2023-03-07 08:12:43

In this tutorial we will see how to use Riverpod state management for onboard screen using PageView and dots indicator. For PageView we don't need Riverpod, but for dots indicator we need that.

Here's how it will work. We will use a PageController to change the index of the PageView and inside PageView we will get the changed index and from PageView onPageChanged we will stored the changed index in our provider.

We will use the saved or stored index from provider and show in the indicator.

Make sure you install packages

flutter_riverpod

dotsindicator

We will use two main mechanism of Riverpod to do it. One StateNotifier and other one is StateNotifierProvider.

StateNotifier will hold our shared data that would be changed and StateNotifierProvider will help us to expose the provider (shared data) to the UI so that user can change app state.

StateNotifierProvider would do the below thing

  1. expose the provider to the UI
  2. help change the shared data from UI
  3. help call methods if we have methods or properties inside StateNotifier

In the riverpod example we will create few dart classes and files. Let's see the dart files

  1. WelcomeState
  2. WelcomeNotifier
  3. Welcome

WelcomeState

We will create a class name WelcomeState. This class will hold the variable that would be changed based click or user interaction. We will have a variable name page and we will change this variable from StateNotifier

Our WelcomeState class will extend equatable package.

class WelcomeState extends Equatable {
  const WelcomeState({
    this.page=0,
  });

  final int page;

  WelcomeState copyWith({
    int? page,
  }) {
    return WelcomeState(
      page: page ?? this.page,
    );
  }

  @override
  List<Object> get props => [page];
}

We also used copyWith() method to change the WelcomeState. We will call copyWith() from StateNotifier class. This method will change page variable. Changing this variable will help us change the WelcomeState object.

WelcomeNotifier

WelcomeState will hold variable that should be changed. And WelcomeNotifier would notify the UI that a variable has changed. But we need a mechanism to do it.

Our class WelcomeNotifier would extend StateNotifier. StateNotifier takes the state type. Here we will mention the type as WelcomeState.

class WelcomeNotifier extends StateNotifier<WelcomeState> {
  WelcomeNotifier() : super(const WelcomeState());

  void pageChanged(int page) {
     state.copyWith(page: page);
  }
}

Here we created a method name pageChanged(). This method calls copyWith() method which will change the page variable in WelcomeState. That means we are changing WelcomeState object.

After that we also created a provider to expose WelcomeNotifier and WelcomeState to the UI. 

final welcomeProvider = StateNotifierProvider<WelcomeNotifier, WelcomeState>((ref) {
  return WelcomeNotifier();
});

To expose the provider, we simply return WelcomeNotifier() which is wrapped around StateNotifierProvider.

Welcome

We have this Welcome class. This UI class will have ref.watch(welcomeProvider) to access the providers. If we can access the providers, then we will be able to change the state.

We need to make sure Welcome class extends ConsumerStatefulWidget. Because of this class, Welcome class would be stateful.


class Welcome extends ConsumerStatefulWidget {
  const Welcome({super.key});

  @override
  ConsumerState<Welcome> createState() => _WelcomePage();
}

class _WelcomePage extends ConsumerState<Welcome> {
  PageController pageController = new PageController(initialPage: 0);

  @override
  void initState() {
    super.initState();
    // "ref" can be used in all life-cycles of a StatefulWidget.
    //ref.read(welcomeProvider).page

  }

  @override
  Widget build(BuildContext context) {
      var state = ref.watch(welcomeProvider);
    return Container(
          color: Colors.white,
          child: SafeArea(
          child: Scaffold(
              body: Container(
        width: 375.w,
        margin: EdgeInsets.only(top: 34.h),
        child: Stack(
          alignment: Alignment.topCenter,
          children: [
            PageView(
              scrollDirection: Axis.horizontal,
              reverse: false,
              onPageChanged: (index) {
                print("------$index");
                ref.read(welcomeProvider.notifier).pageChanged(index);
              },
              controller: pageController,
              pageSnapping: true,
              physics: ClampingScrollPhysics(),
              children: [
                Column(
                  children: [
                    Container(
                      width: 345.w,
                      height: 345.w,
                      child: Image.asset("assets/images/reading.png",
                          fit: BoxFit.fitWidth),
                    ),
                    Container(
                      margin: EdgeInsets.only(top: 15.h),
                      child: Text("First See Learning",
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            color: AppColors.primaryText,
                            fontSize: 24.sp,
                            fontWeight: FontWeight.normal,
                          )),
                    ),
                    Container(
                      width: 375.w,
                      margin: EdgeInsets.only(top: 15.h),
                      padding: EdgeInsets.only(left: 30.w, right: 30.w),
                      child: Text(
                        "Forget about a for of paper all knowledge in one learning!",
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          color: AppColors.primarySecondaryElementText,
                          fontSize: 16.sp,
                          fontWeight: FontWeight.normal,
                        ),
                      ),
                    ),
                    buildTLogin(context, 1, "Next")
                  ],
                ),
                Column(
                  children: [
                    Container(
                      width: 345.w,
                      height: 345.w,
                      child: Image.asset("assets/images/man.png",
                          fit: BoxFit.fitWidth),
                    ),
                    Container(
                      margin: EdgeInsets.only(top: 15.h),
                      child: Text("Connect With Everyone",
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            color: AppColors.primaryText,
                            fontSize: 24.sp,
                            fontWeight: FontWeight.normal,
                          )),
                    ),
                    Container(
                      width: 375.w,
                      margin: EdgeInsets.only(top: 15.h),
                      padding: EdgeInsets.only(left: 30.w, right: 30.w),
                      child: Text(
                        "Always keep in touch with your tutor & friend. let’s get connected!",
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          color: AppColors.primarySecondaryElementText,
                          fontSize: 16.sp,
                          fontWeight: FontWeight.normal,
                        ),
                      ),
                    ),
                    buildTLogin(context, 2, "Next")
                  ],
                ),
                Column(
                  children: [
                    Container(
                      width: 345.w,
                      height: 345.w,
                      child: Image.asset("assets/images/boy.png",
                          fit: BoxFit.fitWidth),
                    ),
                    Container(
                      margin: EdgeInsets.only(top: 15.h),
                      child: Text("Always Fascinated Learning",
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            color: AppColors.primaryText,
                            fontSize: 24.sp,
                            fontWeight: FontWeight.normal,
                          )),
                    ),
                    Container(
                      width: 375.w,
                      margin: EdgeInsets.only(top: 15.h),
                      padding: EdgeInsets.only(left: 30.w, right: 30.w),
                      child: Text(
                        "Anywhere, anytime. The time is at your discretion so study whenever you want.",
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          color: AppColors.primarySecondaryElementText,
                          fontSize: 16.sp,
                          fontWeight: FontWeight.normal,
                        ),
                      ),
                    ),
                    buildTLogin(context, 3, "Get Started")
                  ],
                ),
              ],
            ),
             Positioned(
                 top: 460.h,
                 child: DotsIndicator(
                     mainAxisAlignment: MainAxisAlignment.center,
                     reversed: false,
                     dotsCount: 3,
                     position: state.page.toDouble(),
                     decorator: DotsDecorator(
                       color: AppColors.primaryThreeElementText,
                       activeColor: AppColors.primaryElement,
                       size: const Size.square(8.0),
                       activeSize: const Size(18.0, 8.0),
                       activeShape: RoundedRectangleBorder(
                           borderRadius: BorderRadius.circular(5.0)),
                     )))
          ],
        ),
      ))));
  }

  Widget buildTLogin(BuildContext context, int index, String title) {
    return GestureDetector(
        child: Container(
            width: 325.w,
            height: 50.h,
            margin: EdgeInsets.only(top: 100.h, left: 25.w, right: 25.w),
            decoration: BoxDecoration(
              color: AppColors.primaryElement,
              borderRadius: BorderRadius.all(Radius.circular(15.w)),
              boxShadow: [
                BoxShadow(
                  color: Colors.grey.withOpacity(0.1),
                  spreadRadius: 1,
                  blurRadius: 2,
                  offset: Offset(0, 1), // changes position of shadow
                ),
              ],
            ),
            child: Center(
                child: Text(
              "${title}",
              textAlign: TextAlign.center,
              style: TextStyle(
                color: AppColors.primaryBackground,
                fontWeight: FontWeight.normal,
                fontSize: 16.sp,
              ),
            ))),
        onTap: () {
          if (index < 3) {
            pageController.animateToPage(
              index,
              duration: Duration(milliseconds: 500),
              curve: Curves.ease,
            );
          } else {
            Global.storageService.setBool(STORAGE_DEVICE_FIRST_OPEN_KEY, true);
            Navigator.of(context).pushNamedAndRemoveUntil(
                AppRoutes.Sign_in, (Route<dynamic> route) => false);
          }
        });
  }
}

Our Welcome class extends  ConsumerStatefulWidget, because of this we can access the ref object. Since we can access the ref object, we would be able to access the Providers.

We used PageController to do change pages. It requires changing the index. An instance of PageController pageController has animateToPage() method. This method change the index and this change reflects in onPageChanged. onPageChanged calls pageChanged() method from StateNotifier WelcomeNotifer.

onPageChanged calls pageChanged() using ref.read() and saves the shared index.

 

See our Positioned() widget, inside this we access our provider. Provider gets the shared data index using ref.watch(welcomeProvider).

Since we can access the shared index, we assign it DotsIndicator() position property.

Comment

Add Reviews