Last active
January 10, 2026 08:20
-
-
Save nank1ro/86f0f055956fe15d66b7a98806fac687 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| void main() { | |
| runApp(const MyApp()); | |
| } | |
| class MyApp extends StatelessWidget { | |
| const MyApp({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| debugShowCheckedModeBanner: false, | |
| builder: (context, child) { | |
| return PageLoader(child: child!); | |
| }, | |
| home: HomePage(), | |
| ); | |
| } | |
| } | |
| class HomePage extends StatelessWidget { | |
| Future<void> operation(BuildContext context) async { | |
| final pageLoader = PageLoader.of(context); | |
| try { | |
| pageLoader.show(); | |
| await Future<void>.delayed(const Duration(seconds: 3)); | |
| } finally { | |
| pageLoader.hide(); | |
| } | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| body: Center( | |
| child: Padding( | |
| padding: const EdgeInsets.only(top: 100), | |
| child: ElevatedButton( | |
| child: Text('Run Operation'), | |
| onPressed: () => operation(context), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| /// A simple widget that allows to present or hide a loader | |
| /// To use it, simply put it as the first child of the `builder` method of the | |
| /// MaterialApp or wrap your page body with the [PageLoader] widget | |
| /// | |
| /// * When changing its state, this widget is performant enough to avoid | |
| /// * rebuilding the [child] widget. | |
| /// | |
| /// To show the loader use | |
| /// ```dart | |
| /// PageLoader.of(context).show(); | |
| /// or | |
| /// context.showLoader(); | |
| /// ``` | |
| /// | |
| /// Similarly, to hide the loader use | |
| /// ```dart | |
| /// PageLoader.of(context).hide(); | |
| /// or | |
| /// context.hideLoader(); | |
| /// ``` | |
| class PageLoader extends StatefulWidget { | |
| const PageLoader({ | |
| super.key, | |
| required this.child, | |
| this.userInteractionEnabled = false, | |
| }); | |
| final Widget child; | |
| /// Tells if the child is clickable when the loader is visible, defaults to | |
| /// false. | |
| final bool userInteractionEnabled; | |
| @override | |
| State<PageLoader> createState() => _PageLoaderState(); | |
| // ignore: library_private_types_in_public_api | |
| static _InheritedLoading of(BuildContext context) => | |
| context | |
| .getElementForInheritedWidgetOfExactType<_InheritedLoading>()! | |
| .widget | |
| as _InheritedLoading; | |
| } | |
| class _PageLoaderState extends State<PageLoader> { | |
| /// Indicates whether the loading indicator is visible | |
| late final isLoading = ValueNotifier(false); | |
| @override | |
| void dispose() { | |
| isLoading.dispose(); | |
| super.dispose(); | |
| } | |
| // ignore: avoid_positional_boolean_parameters | |
| void setLoading(bool value) { | |
| Future.microtask(() => isLoading.value = value); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| final theme = Theme.of(context); | |
| return Directionality( | |
| textDirection: TextDirection.ltr, | |
| child: ValueListenableBuilder( | |
| valueListenable: isLoading, | |
| child: widget.child, | |
| builder: (context, loading, child) { | |
| return _InheritedLoading( | |
| isLoading: loading, | |
| show: () => setLoading(true), | |
| hide: () => setLoading(false), | |
| child: Stack( | |
| children: [ | |
| // Ignore user interaction is loading | |
| IgnorePointer( | |
| ignoring: loading && !widget.userInteractionEnabled, | |
| child: child, | |
| ), | |
| if (loading) | |
| DecoratedBox( | |
| decoration: BoxDecoration( | |
| color: theme.colorScheme.surface.withValues(alpha: .7), | |
| ), | |
| child: const LoadingSpin(), | |
| ), | |
| ], | |
| ), | |
| ); | |
| }, | |
| ), | |
| ); | |
| } | |
| } | |
| class LoadingSpin extends StatelessWidget { | |
| const LoadingSpin({super.key, this.color}); | |
| final Color? color; | |
| @override | |
| Widget build(BuildContext context) { | |
| return Center( | |
| child: CircularProgressIndicator( | |
| strokeCap: StrokeCap.round, | |
| strokeWidth: 6, | |
| color: color, | |
| ), | |
| ); | |
| } | |
| } | |
| @immutable | |
| class _InheritedLoading extends InheritedWidget { | |
| const _InheritedLoading({ | |
| required this.isLoading, | |
| required super.child, | |
| required this.show, | |
| required this.hide, | |
| }); | |
| // Indicates if the loading indicator is shown | |
| final bool isLoading; | |
| // FIred when the show loading action if performed | |
| final VoidCallback show; | |
| // FIred when the hide loading action if performed | |
| final VoidCallback hide; | |
| @override | |
| bool updateShouldNotify(covariant _InheritedLoading oldWidget) { | |
| return oldWidget.isLoading != isLoading; | |
| } | |
| } | |
| // Convenience extension | |
| extension PageLoaderX on BuildContext { | |
| void showLoader() => PageLoader.of(this).show(); | |
| void hideLoader() => PageLoader.of(this).hide(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment