Screens / settings
App settings
Groups the settings of the app: appearance, text size, language, haptics, a cache to clear after a confirmation, version, licenses and legal notices, with a link to the account.
Import
App settings 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 app-settingsAlso installs: core, alert, app-bar, avatar, confirm-delete, empty-state, list-row, page, profile-card, radio, settings-section, sheet, skeleton, switch, button, segmented
Examples
Custom
The settings of your app: a section of your own before About, and only the rows that apply (showHaptics and showStorage remove the haptics and the cache).
import 'package:your_app/ui/fliin/components/list_row/fl_list_row.dart';
import 'package:your_app/ui/fliin/components/settings_section/fl_settings_section.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_controller.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_data.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_screen.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_strings.dart';
import 'package:flutter/widgets.dart';
class AppSettingsCustomExample extends StatelessWidget {
const AppSettingsCustomExample({super.key});
@override
Widget build(BuildContext context) {
final strings = AppSettingsStrings.of(context);
return AppSettingsScreen.standalone(
service: const SampleAppSettingsService(latency: Duration(milliseconds: 600)),
initial: const AppSettingsReady(sampleAppSettings),
showHaptics: false,
showStorage: false,
onBack: () {},
onAccount: () {},
onTextSize: () {},
onLicenses: () {},
sections: [
FlSettingsSection(
title: strings.sampleHelp,
children: [
FlListRow(title: strings.sampleHelpCenter, trailing: const FlListRowChevron(), onPressed: () {}),
FlListRow(title: strings.sampleContact, trailing: const FlListRowChevron(), onPressed: () {}),
],
),
],
);
}
}Ready
The app settings: appearance, notifications, storage and the about section. standalone holds its own controller over the service you pass.
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_controller.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_data.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_screen.dart';
import 'package:flutter/widgets.dart';
class AppSettingsReadyExample extends StatelessWidget {
const AppSettingsReadyExample({super.key});
@override
Widget build(BuildContext context) {
return AppSettingsScreen.standalone(
service: const SampleAppSettingsService(latency: Duration(milliseconds: 600)),
initial: const AppSettingsReady(sampleAppSettings),
onBack: () {},
onAccount: () {},
onTextSize: () {},
onLicenses: () {},
onLegal: () {},
);
}
}Loading
While the service answers, placeholders keep the layout of each section.
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_controller.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_data.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_screen.dart';
import 'package:flutter/widgets.dart';
class AppSettingsLoadingExample extends StatelessWidget {
const AppSettingsLoadingExample({super.key});
@override
Widget build(BuildContext context) {
return AppSettingsScreen.standalone(
service: const SampleAppSettingsService(latency: Duration(milliseconds: 600)),
initial: const AppSettingsLoading(),
onBack: () {},
onAccount: () {},
onTextSize: () {},
onLicenses: () {},
onLegal: () {},
);
}
}Clearing
While the cache is cleared, its row shows a spinner and the rest stays usable.
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_controller.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_data.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_screen.dart';
import 'package:flutter/widgets.dart';
class AppSettingsClearingExample extends StatelessWidget {
const AppSettingsClearingExample({super.key});
@override
Widget build(BuildContext context) {
return AppSettingsScreen.standalone(
service: const SampleAppSettingsService(latency: Duration(milliseconds: 600)),
initial: const AppSettingsReady(sampleAppSettings, clearing: true),
onBack: () {},
onAccount: () {},
onTextSize: () {},
onLicenses: () {},
onLegal: () {},
);
}
}Error
When loading fails, the screen says so and offers to try again.
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_controller.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_data.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_screen.dart';
import 'package:flutter/widgets.dart';
class AppSettingsErrorExample extends StatelessWidget {
const AppSettingsErrorExample({super.key});
@override
Widget build(BuildContext context) {
return AppSettingsScreen.standalone(
service: const SampleAppSettingsService(latency: Duration(milliseconds: 600)),
initial: const AppSettingsFailed(),
onBack: () {},
onAccount: () {},
onTextSize: () {},
onLicenses: () {},
onLegal: () {},
);
}
}Routing
fliin add app-settings 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/app_settings_route.dart, next to app_settings_page.dart.
import 'package:go_router/go_router.dart';
import 'app_settings_page.dart';
/// Add appSettingsRoute to the routes of your GoRouter.
final appSettingsRoute = GoRoute(
path: '/settings',
builder: (context, state) => AppSettingsPage(
// Alone in the stack (deep link, go): your app's home.
onBack: () => context.canPop() ? context.pop() : context.go('/'),
),
);navigator
// lib/app_settings_route.dart, next to app_settings_page.dart.
import 'package:flutter/material.dart';
import 'app_settings_page.dart';
/// Opens “App settings”: `Navigator.of(context).push(appSettingsRoute())`.
Route<void> appSettingsRoute() => MaterialPageRoute<void>(
builder: (context) => AppSettingsPage(
onBack: () => Navigator.of(context).maybePop(),
),
);State
The controller is a ChangeNotifier from widgets.dart, with no package. fliin add app-settings 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/app_settings_page.dart
// Your backend: implement AppSettingsService (app_settings_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_data.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_screen.dart';
import 'package:flutter/widgets.dart';
class AppSettingsPage extends StatelessWidget {
const AppSettingsPage({
super.key,
required this.onBack,
});
final VoidCallback onBack;
@override
Widget build(BuildContext context) => AppSettingsScreen.standalone(
service: const SampleAppSettingsService(),
onBack: onBack,
);
}provider
// lib/app_settings_page.dart
// Your backend: implement AppSettingsService (app_settings_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_controller.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_data.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
class AppSettingsPage extends StatelessWidget {
const AppSettingsPage({
super.key,
required this.onBack,
});
final VoidCallback onBack;
@override
Widget build(BuildContext context) => ChangeNotifierProvider(
create: (_) => AppSettingsController(service: const SampleAppSettingsService())..load(),
child: Builder(
builder: (context) => AppSettingsScreen(
controller: context.read<AppSettingsController>(),
onBack: onBack,
),
),
);
}riverpod
// lib/app_settings_page.dart
// Your backend: implement AppSettingsService (app_settings_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_controller.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_data.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final appSettingsProvider = Provider.autoDispose<AppSettingsController>((ref) {
final controller = AppSettingsController(service: const SampleAppSettingsService())..load();
ref.onDispose(controller.dispose);
return controller;
});
class AppSettingsPage extends ConsumerWidget {
const AppSettingsPage({
super.key,
required this.onBack,
});
final VoidCallback onBack;
@override
Widget build(BuildContext context, WidgetRef ref) => AppSettingsScreen(
controller: ref.watch(appSettingsProvider),
onBack: onBack,
);
}bloc
// lib/app_settings_page.dart
// Your backend: implement AppSettingsService (app_settings_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_controller.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_data.dart';
import 'package:your_app/ui/fliin/screens/app_settings/app_settings_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 AppSettingsCubit extends Cubit<({AppSettingsState state, int version})> {
AppSettingsCubit(this.controller) : super((state: controller.state, version: 0)) {
controller.addListener(_changed);
}
final AppSettingsController controller;
void _changed() => emit((state: controller.state, version: state.version + 1));
@override
Future<void> close() {
controller
..removeListener(_changed)
..dispose();
return super.close();
}
}
class AppSettingsPage extends StatelessWidget {
const AppSettingsPage({
super.key,
required this.onBack,
});
final VoidCallback onBack;
@override
Widget build(BuildContext context) => BlocProvider(
create: (_) => AppSettingsCubit(AppSettingsController(service: const SampleAppSettingsService())..load()),
child: Builder(
builder: (context) => AppSettingsScreen(
controller: context.read<AppSettingsCubit>().controller,
onBack: onBack,
),
),
);
}API
AppSettingsScreen: class extends StatefulWidget
AppSettingsScreen.new: const new({super.key, required AppSettingsController controller, VoidCallback? onBack, VoidCallback? onAccount, VoidCallback? onTextSize, VoidCallback? onLicenses, VoidCallback? onLegal, List<Widget> sections, bool showHaptics, bool showStorage})
AppSettingsScreen.standalone: const new({super.key, required AppSettingsService service, AppSettingsState? initial, VoidCallback? onBack, VoidCallback? onAccount, VoidCallback? onTextSize, VoidCallback? onLicenses, VoidCallback? onLegal, List<Widget> sections, bool showHaptics, bool showStorage})
AppSettingsScreen.controller: get AppSettingsController?
AppSettingsScreen.service: get AppSettingsService?
AppSettingsScreen.initial: get AppSettingsState?
AppSettingsScreen.onBack: get VoidCallback?
AppSettingsScreen.onAccount: get VoidCallback?
AppSettingsScreen.onTextSize: get VoidCallback?
AppSettingsScreen.onLicenses: get VoidCallback?
AppSettingsScreen.onLegal: get VoidCallback?
AppSettingsScreen.sections: get List<Widget>
AppSettingsScreen.showHaptics: get bool
AppSettingsScreen.showStorage: get bool
AppSettingsScreen.createState: State<AppSettingsScreen>()
AppSettingsState: sealed class permits {AppSettingsFailed, AppSettingsLoading, AppSettingsReady}
AppSettingsState.new: const new()
AppSettingsLoading: final class extends AppSettingsState
AppSettingsLoading.new: const new()
AppSettingsNotice: enum {saveFailed, clearFailed}
AppSettingsNotice.saveFailed: static final AppSettingsNotice
AppSettingsNotice.clearFailed: static final AppSettingsNotice
AppSettingsReady: final class extends AppSettingsState
AppSettingsReady.new: const new(AppSettingsData data, {bool clearing, AppSettingsNotice? notice})
AppSettingsReady.data: get AppSettingsData
AppSettingsReady.clearing: get bool
AppSettingsReady.notice: get AppSettingsNotice?
AppSettingsFailed: final class extends AppSettingsState
AppSettingsFailed.new: const new()
AppSettingsController: class extends ChangeNotifier
AppSettingsController.new: new({required AppSettingsService service, AppSettingsState initial})
AppSettingsController.service: get AppSettingsService
AppSettingsController.state: get AppSettingsState
AppSettingsController.load: Future<void>()
AppSettingsController.setAppearance: Future<bool>(AppAppearance value)
AppSettingsController.setLanguage: Future<bool>(String code)
AppSettingsController.setHaptics: Future<bool>(bool value)
AppSettingsController.clearCache: Future<bool>()
AppSettingsController.dispose: void()
AppAppearance: enum {system, light, dark}
AppAppearance.system: static final AppAppearance
AppAppearance.light: static final AppAppearance
AppAppearance.dark: static final AppAppearance
AppLanguage: final class
AppLanguage.new: const new(String code, String name)
AppLanguage.code: get String
AppLanguage.name: get String
AppSettingsAccount: final class
AppSettingsAccount.new: const new({required String name, required String email, required String initials, ImageProvider? photo})
AppSettingsAccount.name: get String
AppSettingsAccount.email: get String
AppSettingsAccount.initials: get String
AppSettingsAccount.photo: get ImageProvider?
AppSettingsData: final class
AppSettingsData.new: const new({required AppAppearance appearance, required String language, required List<AppLanguage> languages, required bool haptics, required int cacheBytes, required String version, required AppSettingsAccount? account})
AppSettingsData.appearance: get AppAppearance
AppSettingsData.language: get String
AppSettingsData.languages: get List<AppLanguage>
AppSettingsData.haptics: get bool
AppSettingsData.cacheBytes: get int
AppSettingsData.version: get String
AppSettingsData.account: get AppSettingsAccount?
AppSettingsData.languageName: get String
AppSettingsData.copyWith: AppSettingsData({AppAppearance? appearance, String? language, bool? haptics, int? cacheBytes})
AppSettingsService: abstract interface class
AppSettingsService.new: new()
AppSettingsService.load: abstract Future<AppSettingsData>()
AppSettingsService.save: abstract Future<void>(AppSettingsData settings)
AppSettingsService.clearCache: abstract Future<void>()
sampleAppLanguages: const dynamic
sampleAppSettings: const dynamic
SampleAppSettingsService: final class implements AppSettingsService
SampleAppSettingsService.new: const new({AppSettingsData data, Duration latency})
SampleAppSettingsService.data: get AppSettingsData
SampleAppSettingsService.latency: get Duration
SampleAppSettingsService.load: Future<AppSettingsData>()
SampleAppSettingsService.save: Future<void>(AppSettingsData settings)
SampleAppSettingsService.clearCache: Future<void>()
appSettingsL10n: const Map<String, Map<String, String>>
AppSettingsStrings: final class
AppSettingsStrings.new: const new(String language)
AppSettingsStrings.of: factory new(BuildContext context)
AppSettingsStrings.language: get String
AppSettingsStrings.title: get String
AppSettingsStrings.back: get String
AppSettingsStrings.loading: get String
AppSettingsStrings.display: get String
AppSettingsStrings.appearance: get String
AppSettingsStrings.appearanceSystem: get String
AppSettingsStrings.appearanceLight: get String
AppSettingsStrings.appearanceDark: get String
AppSettingsStrings.textSize: get String
AppSettingsStrings.percent: String(String value)
AppSettingsStrings.general: get String
AppSettingsStrings.languageLabel: get String
AppSettingsStrings.haptics: get String
AppSettingsStrings.storage: get String
AppSettingsStrings.clearCache: get String
AppSettingsStrings.cacheEmpty: get String
AppSettingsStrings.clearTitle: get String
AppSettingsStrings.clearMessage: get String
AppSettingsStrings.clearConfirm: get String
AppSettingsStrings.cancel: get String
AppSettingsStrings.about: get String
AppSettingsStrings.version: get String
AppSettingsStrings.licenses: get String
AppSettingsStrings.legal: get String
AppSettingsStrings.close: get String
AppSettingsStrings.errorTitle: get String
AppSettingsStrings.errorMessage: get String
AppSettingsStrings.retry: get String
AppSettingsStrings.appearanceName: String(AppAppearance value)
AppSettingsStrings.cacheSize: String(int bytes)
AppSettingsStrings.saveFailed: get String
AppSettingsStrings.clearFailed: get String
AppSettingsStrings.textSizeDefault: get String
AppSettingsStrings.clear: get String
AppSettingsStrings.cacheUsed: String(int bytes)
AppSettingsStrings.sampleHelp: get String
AppSettingsStrings.sampleHelpCenter: get String
AppSettingsStrings.sampleContact: 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 |
|---|---|---|---|
appSettingsTitle | Settings | Réglages | الإعدادات |
appSettingsBack | Back | Retour | رجوع |
appSettingsLoading | Loading settings | Chargement des réglages | جارٍ تحميل الإعدادات |
appSettingsDisplay | Display | Affichage | العرض |
appSettingsAppearance | Appearance | Apparence | المظهر |
appSettingsAppearanceSystem | System | Système | النظام |
appSettingsAppearanceLight | Light | Clair | فاتح |
appSettingsAppearanceDark | Dark | Sombre | داكن |
appSettingsTextSize | Text size | Taille du texte | حجم النص |
appSettingsPercent | {value}% | {value} % | {value}٪ |
appSettingsTextSizeDefault | Default | Par défaut | افتراضي |
appSettingsGeneral | General | Général | عام |
appSettingsLanguage | Language | Langue | اللغة |
appSettingsHaptics | Haptic feedback | Retour haptique | الاهتزاز عند اللمس |
appSettingsStorage | Storage | Stockage | التخزين |
appSettingsClearCache | Clear cache | Vider le cache | مسح الذاكرة المؤقتة |
appSettingsClear | Clear | Vider | مسح |
appSettingsCacheUsed | Cache: {size} | Cache : {size} | الذاكرة المؤقتة: {size} |
appSettingsCacheEmpty | Empty | Vide | فارغة |
appSettingsKilobytes | {size} KB | {size} Ko | {size} ك.ب |
appSettingsMegabytes | {size} MB | {size} Mo | {size} م.ب |
appSettingsClearTitle | Clear the cache? | Vider le cache ? | مسح الذاكرة المؤقتة؟ |
appSettingsClearMessage | Downloaded images and files will be erased and fetched again when needed. Your settings and account stay. | Les images et fichiers téléchargés seront effacés, puis rechargés au besoin. Vos réglages et votre compte restent. | سيتم مسح الصور والملفات التي تم تنزيلها وإعادة تحميلها عند الحاجة. تبقى إعداداتك وحسابك. |
appSettingsClearConfirm | Clear | Vider | مسح |
appSettingsCancel | Cancel | Annuler | إلغاء |
appSettingsAbout | About | À propos | حول التطبيق |
appSettingsVersion | Version | Version | الإصدار |
appSettingsLicenses | Licenses | Licences | التراخيص |
appSettingsLegal | Legal notices | Mentions légales | الإشعارات القانونية |
appSettingsClose | Close | Fermer | إغلاق |
appSettingsErrorTitle | Settings could not load | Impossible de lire les réglages | تعذّر تحميل الإعدادات |
appSettingsErrorMessage | Something went wrong on this device. | Un problème est survenu sur l'appareil. | حدثت مشكلة على هذا الجهاز. |
appSettingsRetry | Try again | Réessayer | أعد المحاولة |
appSettingsSaveFailed | Your change couldn't be saved. The setting is back to its saved value. | Votre modification n'a pas pu être enregistrée. Le réglage a repris sa valeur enregistrée. | تعذّر حفظ التغيير. عاد الإعداد إلى قيمته المحفوظة. |
appSettingsClearFailed | The cache couldn't be cleared. Try again. | Le cache n'a pas pu être vidé. Réessayez. | تعذّر مسح الذاكرة المؤقتة. أعد المحاولة. |
appSettingsSampleHelp | Help | Aide | المساعدة |
appSettingsSampleHelpCenter | Help center | Centre d’aide | مركز المساعدة |
appSettingsSampleContact | Contact us | Nous contacter | اتصل بنا |
Reference renders
The 16 images published with version 0.2.4, 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 app-settings compares your copy to them.
















Use it to
- choose the light, dark or system theme
- change the interface language
- delete cached files after a confirmation
- show the version number and open-source licenses
- turn vibrations on or off
- Not for
- the signed-in account's profile and security: use the account screen (account-settings)
- a single switch in a page: use a list row (list-row)
- States