Screens / search
Help center
Shows frequently asked questions by topic in accordions, searchable without accents with the matching words in bold, a helpful or not vote on each answer, and readable offline.
Import
Help center 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 help-centerAlso installs: core, accordion, alert, app-bar, button, empty-state, list-row, page, search-field, section-header, skeleton, text, surface
Examples
Ready
The FAQ by topic, one answer open. standalone holds the help center over the HelpCenterService you pass.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
class HelpCenterReadyExample extends StatelessWidget {
const HelpCenterReadyExample({super.key});
@override
Widget build(BuildContext context) {
return HelpCenterScreen.standalone(
service: SampleHelpCenterService(latency: const Duration(milliseconds: 900)),
initial: HelpCenterReady(sampleHelpFaq, expanded: const {'track'}),
onBack: () {},
onContactSupport: () {},
);
}
}Search
A search narrows the questions to those that match, and an answer already rated keeps its vote.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
class HelpCenterSearchExample extends StatelessWidget {
const HelpCenterSearchExample({super.key});
@override
Widget build(BuildContext context) {
return HelpCenterScreen.standalone(
service: SampleHelpCenterService(latency: const Duration(milliseconds: 900)),
initial: HelpCenterReady(
sampleHelpFaq,
query: 'delivery',
expanded: const {'address'},
votes: const {'address': HelpVote.helpful},
),
onBack: () {},
onContactSupport: () {},
);
}
}No results
A search that matches nothing offers to contact support or clear the search.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
class HelpCenterNoResultsExample extends StatelessWidget {
const HelpCenterNoResultsExample({super.key});
@override
Widget build(BuildContext context) {
return HelpCenterScreen.standalone(
service: SampleHelpCenterService(latency: const Duration(milliseconds: 900)),
initial: HelpCenterReady(sampleHelpFaq, query: 'subscription'),
onBack: () {},
onContactSupport: () {},
);
}
}Cached
The FAQ shown from cache, under a notice that it may be out of date.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
class HelpCenterCachedExample extends StatelessWidget {
const HelpCenterCachedExample({super.key});
@override
Widget build(BuildContext context) {
return HelpCenterScreen.standalone(
service: SampleHelpCenterService(latency: const Duration(milliseconds: 900)),
initial: HelpCenterReady(sampleHelpFaq, cached: true),
onBack: () {},
onContactSupport: () {},
);
}
}Empty
No question yet: the screen points to support instead.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
class HelpCenterEmptyExample extends StatelessWidget {
const HelpCenterEmptyExample({super.key});
@override
Widget build(BuildContext context) {
return HelpCenterScreen.standalone(
service: SampleHelpCenterService(latency: const Duration(milliseconds: 900)),
initial: const HelpCenterEmpty(),
onBack: () {},
onContactSupport: () {},
);
}
}Loading
While the FAQ loads, skeletons keep the place of the topics.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
class HelpCenterLoadingExample extends StatelessWidget {
const HelpCenterLoadingExample({super.key});
@override
Widget build(BuildContext context) {
return HelpCenterScreen.standalone(
service: SampleHelpCenterService(latency: const Duration(milliseconds: 900)),
initial: const HelpCenterLoading(),
onBack: () {},
onContactSupport: () {},
);
}
}Error
When loading fails, the screen says so and offers to try again.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
class HelpCenterErrorExample extends StatelessWidget {
const HelpCenterErrorExample({super.key});
@override
Widget build(BuildContext context) {
return HelpCenterScreen.standalone(
service: SampleHelpCenterService(latency: const Duration(milliseconds: 900)),
initial: const HelpCenterFailed(),
onBack: () {},
onContactSupport: () {},
);
}
}Offline
Without a connection and without cache, the screen asks to reconnect.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
class HelpCenterOfflineExample extends StatelessWidget {
const HelpCenterOfflineExample({super.key});
@override
Widget build(BuildContext context) {
return HelpCenterScreen.standalone(
service: SampleHelpCenterService(latency: const Duration(milliseconds: 900)),
initial: const HelpCenterOffline(),
onBack: () {},
onContactSupport: () {},
);
}
}No feedback
An answer without "Was this answer helpful?": showFeedback: false, for an app that does not collect it.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
class HelpCenterNoFeedbackExample extends StatelessWidget {
const HelpCenterNoFeedbackExample({super.key});
@override
Widget build(BuildContext context) {
return HelpCenterScreen.standalone(
service: SampleHelpCenterService(latency: const Duration(milliseconds: 900)),
initial: HelpCenterReady(sampleHelpFaq, expanded: const {'track'}),
showFeedback: false,
onBack: () {},
onContactSupport: () {},
);
}
}Routing
fliin add help-center 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/help_center_route.dart, next to help_center_page.dart.
import 'package:go_router/go_router.dart';
import 'help_center_page.dart';
/// Add helpCenterRoute to the routes of your GoRouter.
final helpCenterRoute = GoRoute(
path: '/help',
builder: (context, state) => HelpCenterPage(
// Alone in the stack (deep link, go): your app's home.
onBack: () => context.canPop() ? context.pop() : context.go('/'),
onContactSupport: () => context.push('/support'),
),
);navigator
// lib/help_center_route.dart, next to help_center_page.dart.
import 'package:flutter/material.dart';
import 'contact_support_route.dart';
import 'help_center_page.dart';
/// Opens “Help center”: `Navigator.of(context).push(helpCenterRoute())`.
Route<void> helpCenterRoute() => MaterialPageRoute<void>(
builder: (context) => HelpCenterPage(
onBack: () => Navigator.of(context).maybePop(),
onContactSupport: () => Navigator.of(context).push(contactSupportRoute()),
),
);State
The controller is a ChangeNotifier from widgets.dart, with no package. fliin add help-center 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/help_center_page.dart
// Your backend: implement HelpCenterService (help_center_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
class HelpCenterPage extends StatelessWidget {
const HelpCenterPage({
super.key,
required this.onBack,
required this.onContactSupport,
});
final VoidCallback onBack;
final VoidCallback onContactSupport;
@override
Widget build(BuildContext context) => HelpCenterScreen.standalone(
service: SampleHelpCenterService(),
onBack: onBack,
onContactSupport: onContactSupport,
);
}provider
// lib/help_center_page.dart
// Your backend: implement HelpCenterService (help_center_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
class HelpCenterPage extends StatelessWidget {
const HelpCenterPage({
super.key,
required this.onBack,
required this.onContactSupport,
});
final VoidCallback onBack;
final VoidCallback onContactSupport;
@override
Widget build(BuildContext context) => ChangeNotifierProvider(
create: (_) => HelpCenterController(service: SampleHelpCenterService())..load(),
child: Builder(
builder: (context) => HelpCenterScreen(
controller: context.read<HelpCenterController>(),
onBack: onBack,
onContactSupport: onContactSupport,
),
),
);
}riverpod
// lib/help_center_page.dart
// Your backend: implement HelpCenterService (help_center_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final helpCenterControllerProvider = Provider.autoDispose<HelpCenterController>((ref) {
final controller = HelpCenterController(service: SampleHelpCenterService())..load();
ref.onDispose(controller.dispose);
return controller;
});
class HelpCenterPage extends ConsumerWidget {
const HelpCenterPage({
super.key,
required this.onBack,
required this.onContactSupport,
});
final VoidCallback onBack;
final VoidCallback onContactSupport;
@override
Widget build(BuildContext context, WidgetRef ref) => HelpCenterScreen(
controller: ref.watch(helpCenterControllerProvider),
onBack: onBack,
onContactSupport: onContactSupport,
);
}bloc
// lib/help_center_page.dart
// Your backend: implement HelpCenterService (help_center_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/help_center/help_center_controller.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_data.dart';
import 'package:your_app/ui/fliin/screens/help_center/help_center_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 HelpCenterCubit extends Cubit<({HelpCenterState state, int version})> {
HelpCenterCubit(this.controller) : super((state: controller.state, version: 0)) {
controller.addListener(_changed);
}
final HelpCenterController controller;
void _changed() => emit((state: controller.state, version: state.version + 1));
@override
Future<void> close() {
controller
..removeListener(_changed)
..dispose();
return super.close();
}
}
class HelpCenterPage extends StatelessWidget {
const HelpCenterPage({
super.key,
required this.onBack,
required this.onContactSupport,
});
final VoidCallback onBack;
final VoidCallback onContactSupport;
@override
Widget build(BuildContext context) => BlocProvider(
create: (_) => HelpCenterCubit(HelpCenterController(service: SampleHelpCenterService())..load()),
child: Builder(
builder: (context) => HelpCenterScreen(
controller: context.read<HelpCenterCubit>().controller,
onBack: onBack,
onContactSupport: onContactSupport,
),
),
);
}The snippets lead to contact-support: install these screens too and paste their snippets.
API
HelpCenterScreen: class extends StatefulWidget
HelpCenterScreen.new: const new({super.key, required HelpCenterController controller, VoidCallback? onBack, VoidCallback? onContactSupport, ValueChanged<HelpArticle>? onOpenArticle, bool showFeedback})
HelpCenterScreen.standalone: const new({super.key, required HelpCenterService service, HelpCenterState? initial, VoidCallback? onBack, VoidCallback? onContactSupport, ValueChanged<HelpArticle>? onOpenArticle, bool showFeedback})
HelpCenterScreen.controller: get HelpCenterController?
HelpCenterScreen.service: get HelpCenterService?
HelpCenterScreen.initial: get HelpCenterState?
HelpCenterScreen.onBack: get VoidCallback?
HelpCenterScreen.onContactSupport: get VoidCallback?
HelpCenterScreen.onOpenArticle: get ValueChanged<HelpArticle>?
HelpCenterScreen.showFeedback: get bool
HelpCenterScreen.createState: State<HelpCenterScreen>()
HelpCenterState: sealed class permits {HelpCenterEmpty, HelpCenterFailed, HelpCenterLoading, HelpCenterOffline, HelpCenterReady}
HelpCenterState.new: const new()
HelpCenterLoading: final class extends HelpCenterState
HelpCenterLoading.new: const new()
HelpCenterFailed: final class extends HelpCenterState
HelpCenterFailed.new: const new()
HelpCenterOffline: final class extends HelpCenterState
HelpCenterOffline.new: const new()
HelpCenterEmpty: final class extends HelpCenterState
HelpCenterEmpty.new: const new()
HelpCenterNotice: enum {refreshFailed, refreshOffline}
HelpCenterNotice.refreshFailed: static final HelpCenterNotice
HelpCenterNotice.refreshOffline: static final HelpCenterNotice
HelpCenterReady: final class extends HelpCenterState
HelpCenterReady.new: new(HelpFaq faq, {String query, Set<String> expanded, Map<String, HelpVote> votes, Set<String> failedVotes, bool cached, bool refreshing, HelpCenterNotice? notice})
HelpCenterReady.faq: get HelpFaq
HelpCenterReady.query: get String
HelpCenterReady.expanded: get Set<String>
HelpCenterReady.votes: get Map<String, HelpVote>
HelpCenterReady.failedVotes: get Set<String>
HelpCenterReady.cached: get bool
HelpCenterReady.refreshing: get bool
HelpCenterReady.notice: get HelpCenterNotice?
HelpCenterReady.results: get List<HelpTopicMatch>
HelpCenterController: class extends ChangeNotifier
HelpCenterController.new: new({required HelpCenterService service, HelpCenterState initial})
HelpCenterController.service: get HelpCenterService
HelpCenterController.state: get HelpCenterState
HelpCenterController.load: Future<bool>()
HelpCenterController.refresh: Future<bool>()
HelpCenterController.search: void(String query)
HelpCenterController.setExpanded: void(Set<String> expanded)
HelpCenterController.vote: Future<bool>(String articleId, HelpVote vote)
HelpCenterController.dispose: void()
HelpArticle: final class
HelpArticle.new: const new({required String id, required String question, required String answer})
HelpArticle.id: get String
HelpArticle.question: get String
HelpArticle.answer: get String
HelpTopicLayout: enum {answers, links}
HelpTopicLayout.answers: static final HelpTopicLayout
HelpTopicLayout.links: static final HelpTopicLayout
HelpTopic: final class
HelpTopic.new: const new({required String id, required String title, required List<HelpArticle> articles, HelpTopicLayout layout})
HelpTopic.id: get String
HelpTopic.title: get String
HelpTopic.articles: get List<HelpArticle>
HelpTopic.layout: get HelpTopicLayout
HelpVote: enum {helpful, notHelpful}
HelpVote.helpful: static final HelpVote
HelpVote.notHelpful: static final HelpVote
HelpFaq: final class
HelpFaq.new: const new({required List<HelpTopic> topics, Map<String, HelpVote> votes})
HelpFaq.topics: get List<HelpTopic>
HelpFaq.votes: get Map<String, HelpVote>
HelpFaq.isEmpty: get bool
HelpNoNetwork: final class implements Exception
HelpNoNetwork.new: const new({HelpFaq? cached})
HelpNoNetwork.cached: get HelpFaq?
HelpCenterService: abstract interface class
HelpCenterService.new: new()
HelpCenterService.load: abstract Future<HelpFaq>()
HelpCenterService.vote: abstract Future<void>(String articleId, HelpVote vote)
HelpRange: typedef = ({int start, int end})
HelpMatch: final class
HelpMatch.new: const new(HelpArticle article, {List<HelpRange> inQuestion, List<HelpRange> inAnswer})
HelpMatch.article: get HelpArticle
HelpMatch.inQuestion: get List<HelpRange>
HelpMatch.inAnswer: get List<HelpRange>
HelpTopicMatch: typedef = ({HelpTopic topic, List<HelpMatch> matches})
searchHelp: List<HelpTopicMatch>(HelpFaq faq, String query)
HelpFolded: final class
HelpFolded.text: get String
HelpFolded.find: List<HelpRange>(List<String> words)
helpFold: HelpFolded(String text)
sampleHelpFaq: const dynamic
SampleHelpCenterService: final class implements HelpCenterService
SampleHelpCenterService.new: new({HelpFaq faq, Duration latency, bool offline, HelpFaq? cached, bool failVotes})
SampleHelpCenterService.faq: get HelpFaq
SampleHelpCenterService.latency: get Duration
SampleHelpCenterService.offline: get bool
SampleHelpCenterService.offline=: set bool
SampleHelpCenterService.cached: get HelpFaq?
SampleHelpCenterService.failVotes: get bool
SampleHelpCenterService.failVotes=: set bool
SampleHelpCenterService.load: Future<HelpFaq>()
SampleHelpCenterService.vote: Future<void>(String articleId, HelpVote vote)
helpCenterL10n: const Map<String, Map<String, String>>
HelpCenterStrings: final class
HelpCenterStrings.new: const new(String language)
HelpCenterStrings.of: factory new(BuildContext context)
HelpCenterStrings.language: get String
HelpCenterStrings.digits: String(int n)
HelpCenterStrings.title: get String
HelpCenterStrings.back: get String
HelpCenterStrings.loading: get String
HelpCenterStrings.refresh: get String
HelpCenterStrings.search: get String
HelpCenterStrings.searchPlaceholder: get String
HelpCenterStrings.clear: get String
HelpCenterStrings.results: String(int count)
HelpCenterStrings.noResultsTitle: String(String query)
HelpCenterStrings.noResultsMessage: get String
HelpCenterStrings.contact: get String
HelpCenterStrings.moreHelp: get String
HelpCenterStrings.votePrompt: get String
HelpCenterStrings.helpful: get String
HelpCenterStrings.notHelpful: get String
HelpCenterStrings.thanks: get String
HelpCenterStrings.voteFailed: get String
HelpCenterStrings.cached: get String
HelpCenterStrings.refreshFailed: get String
HelpCenterStrings.refreshOffline: get String
HelpCenterStrings.emptyTitle: get String
HelpCenterStrings.emptyMessage: get String
HelpCenterStrings.errorTitle: get String
HelpCenterStrings.errorMessage: get String
HelpCenterStrings.offlineTitle: get String
HelpCenterStrings.offlineMessage: get String
HelpCenterStrings.retry: get String
HelpCenterStrings.teamReplies: get StringStrings
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.
| Key | en | fr | ar |
|---|---|---|---|
helpCenterTitle | Help | Aide | المساعدة |
helpCenterBack | Back | Retour | رجوع |
helpCenterLoading | Loading help | Chargement de l’aide | جارٍ تحميل المساعدة |
helpCenterRefresh | Refresh help | Actualiser l’aide | تحديث المساعدة |
helpCenterSearch | Search help | Rechercher dans l’aide | البحث في المساعدة |
helpCenterSearchPlaceholder | Delivery, refund, password… | Livraison, remboursement, mot de passe… | التوصيل، الاسترداد، كلمة المرور… |
helpCenterClear | Clear search | Effacer la recherche | مسح البحث |
helpCenterResultsZero | No questions found | Aucune question trouvée | لم يُعثر على أي سؤال |
helpCenterResultsOne | 1 question | 1 question | سؤال واحد |
helpCenterResultsTwo | 2 questions | 2 questions | سؤالان |
helpCenterResults | {count} questions | {count} questions | {count} أسئلة |
helpCenterResultsMany | {count} questions | {count} questions | {count} سؤالًا |
helpCenterResultsOther | {count} questions | {count} questions | {count} سؤال |
helpCenterNoResultsTitle | No results for “{query}” | Aucun résultat pour « {query} » | لا نتائج عن «{query}» |
helpCenterNoResultsMessage | Try other words, or write to us. | Essayez d’autres mots, ou écrivez-nous. | جرّب كلمات أخرى، أو راسلنا. |
helpCenterContact | Contact support | Contacter le support | التواصل مع الدعم |
helpCenterMoreHelp | Still need help? | Besoin d’aide ? | هل ما زلت بحاجة إلى مساعدة؟ |
helpCenterTeamReplies | Our team will reply. | Notre équipe vous répond. | سيرد عليك فريقنا. |
helpCenterVotePrompt | Was this answer helpful? | Cette réponse vous a-t-elle aidé ? | هل أفادتك هذه الإجابة؟ |
helpCenterHelpful | Helpful | Utile | مفيدة |
helpCenterNotHelpful | Not helpful | Pas utile | غير مفيدة |
helpCenterThanks | Thanks for your feedback. | Merci pour votre avis. | شكرًا على رأيك. |
helpCenterVoteFailed | Your feedback was not sent. Try again. | Votre avis n’a pas été envoyé. Réessayez. | لم يُرسل رأيك. أعد المحاولة. |
helpCenterCached | You are offline. This help may not be up to date. | Vous êtes hors ligne : cette aide n’est peut-être pas à jour. | أنت غير متصل، وقد لا تكون هذه المساعدة محدّثة. |
helpCenterRefreshFailed | The help could not be refreshed. | L’aide n’a pas pu être actualisée. | تعذّر تحديث المساعدة. |
helpCenterRefreshOffline | You are offline. The help could not be refreshed. | Vous êtes hors ligne : l’aide n’a pas pu être actualisée. | أنت غير متصل، وتعذّر تحديث المساعدة. |
helpCenterEmptyTitle | No questions yet | Aucune question pour l’instant | لا أسئلة بعد |
helpCenterEmptyMessage | Write to us and our team will answer you. | Écrivez-nous, notre équipe vous répondra. | راسلنا وسيرد عليك فريقنا. |
helpCenterErrorTitle | Help unavailable | Aide indisponible | المساعدة غير متاحة |
helpCenterErrorMessage | Something went wrong. Try again in a moment. | Un problème est survenu. Réessayez dans un instant. | حدث خطأ. أعد المحاولة بعد قليل. |
helpCenterOfflineTitle | You are offline | Vous êtes hors ligne | أنت غير متصل |
helpCenterOfflineMessage | Connect to read the help. | Connectez-vous pour lire l’aide. | اتصل لقراءة المساعدة. |
helpCenterRetry | Try again | Réessayer | أعد المحاولة |
Reference renders
The 16 images published with version 0.2.5, 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 help-center compares your copy to them.
















Use it to
- let people look through a FAQ with highlights
- show the FAQ by topic in an accordion
- let readers rate a FAQ answer as helpful
- offer customer service when no FAQ entry fits
- keep the saved FAQ readable offline
- Not for
- a single collapsible topic in a screen: use an accordion (accordion)
- the request sent to support and its progress: use contact support (contact-support)
- States