Fliin
On this page

Screens / onboarding

Onboarding

Introduces the service on first launch in two to five illustrated pages that people swipe or skip, ending on a call to action and a link for people who already have an account.

16 of 16 variants pass
Onboarding, steps state
Platform
Brightness
Text scale
Direction

Import

Onboarding is part of Fliin Pro. With a license key, sign in once with fliin login, then add it in a Flutter app where fliin init has run:

fliin login
fliin add onboarding

Also installs: core, icons, button, link-button, page-indicator, result-card, scroll-shadow, text

Examples

Steps

Steps to read in order: an eyebrow above each title, and no Skip. startLabel renames the last button when your onboarding ends on an action of its own.

Onboarding, steps state
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_data.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_screen.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_strings.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    final strings = OnboardingStrings.of(context);
    return OnboardingScreen.standalone(
      pages: [
        for (final (index, page) in sampleOnboardingPages(context).indexed)
          OnboardingPage(
            illustration: page.illustration,
            title: page.title,
            text: page.text,
            eyebrow: strings.sampleStep(index + 1),
          ),
      ],
      showSkip: false,
      onFinished: (_) {},
    );
  }
}

First

The first page of three; swipe or touch Next to go on, or skip to the end.

Onboarding, first state
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_data.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return OnboardingScreen.standalone(
      pages: sampleOnboardingPages(context),
      onFinished: (_) {},
    );
  }
}

Middle

A middle page: the dots show where the user is.

Onboarding, middle state
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_data.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return OnboardingScreen.standalone(
      pages: sampleOnboardingPages(context),
      initialPage: 1,
      onFinished: (_) {},
    );
  }
}

Last

The last page: its action finishes the onboarding and calls onFinished.

Onboarding, last state
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_data.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return OnboardingScreen.standalone(
      pages: sampleOnboardingPages(context),
      initialPage: 2,
      onFinished: (_) {},
    );
  }
}

Routing

fliin add onboarding prints the route for the router it finds in your pubspec, a file next to the page that wires its navigation. Each of them:

go_router

// lib/onboarding_route.dart, next to onboarding_page.dart.
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_controller.dart';
import 'package:go_router/go_router.dart';

import 'onboarding_page.dart';

/// Add onboardingRoute to the routes of your GoRouter.
final onboardingRoute = GoRoute(
  path: '/welcome',
  builder: (context, state) => OnboardingScreenPage(
    onFinished: (exit) {
      // Record here that the introduction was seen (preferences, account).
      switch (exit) {
        case OnboardingExit.signIn:
          context.push('/sign-in');
        case OnboardingExit.completed || OnboardingExit.skipped:
          // '/': your app's home.
          context.go('/');
      }
    },
  ),
);

navigator

// lib/onboarding_route.dart, next to onboarding_page.dart.
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_controller.dart';
import 'package:flutter/material.dart';

import 'onboarding_page.dart';
import 'sign_in_route.dart';

/// Opens “Onboarding”: `Navigator.of(context).pushReplacement(onboardingRoute(homePage: …))`.
/// [homePage] builds your app's home.
Route<void> onboardingRoute({required WidgetBuilder homePage}) => MaterialPageRoute<void>(
  builder: (context) => OnboardingScreenPage(
    onFinished: (exit) {
      // Record here that the introduction was seen (preferences, account).
      final navigator = Navigator.of(context);
      switch (exit) {
        case OnboardingExit.signIn:
          navigator.push(signInRoute(homePage: homePage));
        case OnboardingExit.completed || OnboardingExit.skipped:
          navigator.pushAndRemoveUntil(MaterialPageRoute<void>(builder: homePage), (_) => false);
      }
    },
  ),
);

State

The controller is a ChangeNotifier from widgets.dart, with no package. fliin add onboarding prints the page that holds it with the state management it finds, its imports written for your package; below they read your_app. Your backend implements the Service interface of the screen's _data.dart file:

none

// lib/onboarding_page.dart
// Your content: replace sampleOnboardingPages (onboarding_data.dart) with your pages.
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_controller.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_data.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_screen.dart';
import 'package:flutter/widgets.dart';

class OnboardingScreenPage extends StatelessWidget {
  const OnboardingScreenPage({
    super.key,
    required this.onFinished,
  });

  final ValueChanged<OnboardingExit> onFinished;

  @override
  Widget build(BuildContext context) => OnboardingScreen.standalone(
    pages: sampleOnboardingPages(context),
    onFinished: onFinished,
  );
}

provider

// lib/onboarding_page.dart
// Your content: replace sampleOnboardingPages (onboarding_data.dart) with your pages.
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_controller.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_data.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

class OnboardingScreenPage extends StatelessWidget {
  const OnboardingScreenPage({
    super.key,
    required this.onFinished,
  });

  final ValueChanged<OnboardingExit> onFinished;

  @override
  Widget build(BuildContext context) => ChangeNotifierProvider(
    create: (_) => OnboardingController(pageCount: 3),
    child: Builder(
      builder: (context) => OnboardingScreen(
        controller: context.read<OnboardingController>(),
        pages: sampleOnboardingPages(context),
        onFinished: onFinished,
      ),
    ),
  );
}

riverpod

// lib/onboarding_page.dart
// Your content: replace sampleOnboardingPages (onboarding_data.dart) with your pages.
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_controller.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_data.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final onboardingControllerProvider = Provider.autoDispose<OnboardingController>((ref) {
  final controller = OnboardingController(pageCount: 3);
  ref.onDispose(controller.dispose);
  return controller;
});

class OnboardingScreenPage extends ConsumerWidget {
  const OnboardingScreenPage({
    super.key,
    required this.onFinished,
  });

  final ValueChanged<OnboardingExit> onFinished;

  @override
  Widget build(BuildContext context, WidgetRef ref) => OnboardingScreen(
    controller: ref.watch(onboardingControllerProvider),
    pages: sampleOnboardingPages(context),
    onFinished: onFinished,
  );
}

bloc

// lib/onboarding_page.dart
// Your content: replace sampleOnboardingPages (onboarding_data.dart) with your pages.
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_controller.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_data.dart';
import 'package:your_app/ui/fliin/screens/onboarding/onboarding_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

/// The screen's controller as a bloc, for your BlocListener and BlocBuilder:
/// one emission per controller notification. `version` tells them apart
/// when `state` does not change (mode, filters, text…, read them on
/// `controller`). The screen listens to the controller itself.
class OnboardingCubit extends Cubit<({OnboardingState state, int version})> {
  OnboardingCubit(this.controller) : super((state: controller.state, version: 0)) {
    controller.addListener(_changed);
  }

  final OnboardingController controller;

  void _changed() => emit((state: controller.state, version: state.version + 1));

  @override
  Future<void> close() {
    controller
      ..removeListener(_changed)
      ..dispose();
    return super.close();
  }
}

class OnboardingScreenPage extends StatelessWidget {
  const OnboardingScreenPage({
    super.key,
    required this.onFinished,
  });

  final ValueChanged<OnboardingExit> onFinished;

  @override
  Widget build(BuildContext context) => BlocProvider(
    create: (_) => OnboardingCubit(OnboardingController(pageCount: 3)),
    child: Builder(
      builder: (context) => OnboardingScreen(
        controller: context.read<OnboardingCubit>().controller,
        pages: sampleOnboardingPages(context),
        onFinished: onFinished,
      ),
    ),
  );
}

The snippets lead to sign-in: install these screens too and paste their snippets.

API

OnboardingScreen: class extends StatefulWidget
OnboardingScreen.new: const new({super.key, required OnboardingController controller, required List<OnboardingPage> pages, FutureOr<void> Function(OnboardingExit exit)? onFinished, bool showSignIn, bool showSkip, String? startLabel})
OnboardingScreen.standalone: const new({super.key, required List<OnboardingPage> pages, int initialPage, FutureOr<void> Function(OnboardingExit exit)? onFinished, bool showSignIn, bool showSkip, String? startLabel})
OnboardingScreen.controller: get OnboardingController?
OnboardingScreen.pages: get List<OnboardingPage>
OnboardingScreen.initialPage: get int
OnboardingScreen.onFinished: get FutureOr<void> Function(OnboardingExit exit)?
OnboardingScreen.showSignIn: get bool
OnboardingScreen.showSkip: get bool
OnboardingScreen.startLabel: get String?
OnboardingScreen.createState: State<OnboardingScreen>()
OnboardingExit: enum {skipped, completed, signIn}
OnboardingExit.skipped: static final OnboardingExit
OnboardingExit.completed: static final OnboardingExit
OnboardingExit.signIn: static final OnboardingExit
OnboardingState: sealed class permits {OnboardingLeaving, OnboardingShowing}
OnboardingState.new: const new(int page)
OnboardingState.page: get int
OnboardingShowing: final class extends OnboardingState
OnboardingShowing.new: const new(super.page)
OnboardingLeaving: final class extends OnboardingState
OnboardingLeaving.new: const new(super.page, OnboardingExit exit)
OnboardingLeaving.exit: get OnboardingExit
OnboardingController: class extends ChangeNotifier
OnboardingController.new: new({required int pageCount, int initialPage})
OnboardingController.pageCount: get int
OnboardingController.state: get OnboardingState
OnboardingController.onLastPage: get bool
OnboardingController.show: bool(int page)
OnboardingController.leave: Future<bool>(OnboardingExit exit, FutureOr<void> Function(OnboardingExit exit) finish)
OnboardingController.dispose: void()
OnboardingPage: final class
OnboardingPage.new: const new({required Widget illustration, required String title, required String text, String? eyebrow})
OnboardingPage.illustration: get Widget
OnboardingPage.title: get String
OnboardingPage.text: get String
OnboardingPage.eyebrow: get String?
sampleOnboardingPages: List<OnboardingPage>(BuildContext context)
SampleOnboardingIllustration: class extends StatelessWidget
SampleOnboardingIllustration.new: const new(FlSampleArt art, {super.key})
SampleOnboardingIllustration.art: get FlSampleArt
SampleOnboardingIllustration.build: Widget(BuildContext context)
onboardingL10n: const Map<String, Map<String, String>>
OnboardingStrings: final class
OnboardingStrings.new: const new(String language)
OnboardingStrings.of: factory new(BuildContext context)
OnboardingStrings.language: get String
OnboardingStrings.skip: get String
OnboardingStrings.next: get String
OnboardingStrings.previous: get String
OnboardingStrings.start: get String
OnboardingStrings.signIn: get String
OnboardingStrings.pages: get String
OnboardingStrings.pageOf: String(int page, int count)
OnboardingStrings.sampleBrowseTitle: get String
OnboardingStrings.sampleBrowseText: get String
OnboardingStrings.sampleSaveTitle: get String
OnboardingStrings.sampleSaveText: get String
OnboardingStrings.sampleDeliverTitle: get String
OnboardingStrings.sampleDeliverText: get String
OnboardingStrings.sampleStep: String(int number)

Strings

Every label in English, French and Arabic. The screen follows the closest Localizations; the same table is in the manifest, ready for your ARB files.

Keyenfrar
onboardingPreviousPrevious pagePage précédenteالصفحة السابقة
onboardingSkipSkipPasserتخطٍّ
onboardingNextNextSuivantالتالي
onboardingStartGet startedCommencerابدأ
onboardingSignInI already have an accountJ’ai déjà un compteلديّ حساب بالفعل
onboardingPagesIntroductionPrésentationالمقدمة
onboardingPageOfPage {page} of {count}Page {page} sur {count}الصفحة {page} من {count}
onboardingSampleBrowseTitleThings made to lastDes objets faits pour durerأشياء صُنعت لتدوم
onboardingSampleBrowseTextBrowse pieces chosen from independent workshops, with the story of who made them.Parcourez des pièces choisies dans des ateliers indépendants, avec l’histoire de ceux qui les font.تصفّح قطعًا مختارة من ورش مستقلة، مع قصة من صنعها.
onboardingSampleSaveTitleKeep what you loveGardez vos coups de cœurاحتفظ بما تحب
onboardingSampleSaveTextAdd a piece to your favorites to find it later, on all your devices.Ajoutez une pièce à vos favoris pour la retrouver plus tard, sur tous vos appareils.أضف قطعة إلى المفضلة لتجدها لاحقًا على كل أجهزتك.
onboardingSampleDeliverTitleDelivered to your doorLivré chez vousيصلك إلى بابك
onboardingSampleDeliverTextFollow your order from the workshop to your door, and send it back for free within 30 days.Suivez votre commande de l’atelier à votre porte, et renvoyez-la gratuitement sous 30 jours.تابع طلبك من الورشة إلى بابك، وأعده مجانًا خلال 30 يومًا.
onboardingSampleStepStep {number}Étape {number}الخطوة {number}

Reference renders

The 16 images published with version 0.2.3, rendered by the shared harness on macOS with Flutter 3.47.7. Each shows every state side by side, at the size of an iPhone (390 × 844) or of an Android phone (412 × 915). fliin verify onboarding compares your copy to them.

iOS · Light
Onboarding, iOS, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Onboarding, iOS, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Onboarding, iOS, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Onboarding, iOS, Light, text 2.0×, Right to left
Text 2.0× · Right to left
iOS · Dark
Onboarding, iOS, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Onboarding, iOS, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Onboarding, iOS, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Onboarding, iOS, Dark, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Light
Onboarding, Android, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Onboarding, Android, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Onboarding, Android, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Onboarding, Android, Light, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Dark
Onboarding, Android, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Onboarding, Android, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Onboarding, Android, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Onboarding, Android, Dark, text 2.0×, Right to left
Text 2.0× · Right to left

Use it to

  • present the service in a few pages to swipe through on first launch
  • explain the main features before sign up
  • let people skip the introduction and go straight to the home screen
  • offer sign in to returning users from the welcome pages
  • show what is new in a release as illustrated pages
  • give new users a short guided tour of the features
Not for
  • the dots alone of a sequence of pages: use the page indicator (page-indicator)
  • an empty page with an illustration and an action: use an empty state (empty-state)
States
  • First
  • Middle
  • Last

Arrows to move, Enter to open, Escape to close.