Skip to content

Instantly share code, notes, and snippets.

@nank1ro
Last active January 10, 2026 08:20
Show Gist options
  • Select an option

  • Save nank1ro/86f0f055956fe15d66b7a98806fac687 to your computer and use it in GitHub Desktop.

Select an option

Save nank1ro/86f0f055956fe15d66b7a98806fac687 to your computer and use it in GitHub Desktop.
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