diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bc2888..7c7d8ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,55 +1,57 @@ -## [3.0.2] +## 4.0.0 + +* Added support for using the loader in `StatelessWidget` too +* Using [`stream_mixin`](https://pub.dev/packages/stream_mixin/) instead of state to show/hide loader +* **BREAKING CHANGE**: Use `build` instead of `screen` function +* **BREAKING CHANGE**: `ScreenLoaderApp` widget is removed, use `configScreenLoader` function instead + +## 3.0.2 + * Fixed: [Issue #6](https://github.com/arnold-parge/screen_loader/issues/6): Screen is always blurry -## [3.0.1] +## 3.0.1 + * Updated README -## [3.0.0] +## 3.0.0 + * Migrating to null safety -## [2.0.1] -* Updated readme as per the previous version's breaking changes +## 2.0.1 -## [2.0.0] +* Updated readme as per the previous version's breaking changes -### BREAKING CHANGE -* `screenWrapper` function removed +## 2.0.0 +* **BREAKING CHANGE**: `screenWrapper` function removed * Now just override `screen` function instead of `build` function in your `StatefulWidget`s -## [1.1.1] +## 1.1.1 * Updated readme +## 1.1.0 -## [1.1.0] - -### Breaking changes -* Removed state parameter from `screenWrapper` function +* **BREAKING CHANGE**: Removed state parameter from `screenWrapper` function * Added performFuture * Added BasicScreen in example * Added loadingBgBlur in ScreenLoader - -## [1.0.4] +## 1.0.4 * Updated description - -## [1.0.3] +## 1.0.3 * Added example - -## [1.0.2] +## 1.0.2 * Updated description in pubspec.yaml - -## [1.0.1] +## 1.0.1 * Updated description in pubspec.yaml - -## [1.0.0] +## 1.0.0 * Initial release diff --git a/README.md b/README.md index a667f5c..0aca71d 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,25 @@ # screen_loader -Using `showDialog` for showing loader is a **BAD IDEA**. You may end up messing the navigation stack and `context` due to pushing and popping the loader(dialog). Hence, I have come up with an easy to use mixin `ScreenLoader`, which will handle the loading on the screen. You can customise the loading as well, check below how it is done. - -## Important -Replace your `build(BuildContext context)` function with `screen(BuildContext context)` - -```dart -@override -Widget screen(BuildContext context) { - return Scaffold( - appBar: _buildAppBar(), - body: _buildBody(), - ); -} -``` - -## Basic Usage - -Extend your screen(`StatefulWidget`) with `ScreenLoader`. Use `performFuture` to show loader while your future us being performed. That's it! +## Why ScreenLoader? +1. With the help of [`stream_mixin`](https://pub.dev/packages/stream_mixin/), it shows and hides the loader without updating the state of the widget which increases the performance +1. It does not push any sort of widget to navigation stack, this helps in not messing up the navigation stack and context +1. Easy to use, just use your screen with `ScreenLoader` mixin and wrap the widget with `loadableWidget` +1. The loader is customizable. You can use any widget as a loader +1. You can configure same loader for all the screens at once +1. You can also configure different loader for different screens + +--- +## Basic usage +1. Use your screen with the `ScreenLoader` mixin +1. Wrap the widget with `loadableWidget` +1. Use `performFuture` function to show loader while your future is being performed -## Override Loader - -Simply overide `loader()` method in your `_ScreenState` class +--- +## Configure different loader for individual screen +Simply overide `loader()` method ``` loader() { // here any widget would do @@ -36,39 +31,113 @@ loader() { -## Override Loader Gobally +--- +## Configure the loader for all screens at once ```dart -void main() => runApp(MyApp()); - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ScreenLoaderApp( - app: MaterialApp( - title: AppStrings.yapChat, - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: Screen(), - ), - globalLoader: AlertDialog( - title: Text('Gobal Loader..'), - ), - ); - } +void main() { + configScreenLoader( + loader: AlertDialog( + title: Text('Gobal Loader..'), + ), + bgBlur: 20.0, + ); + runApp(MyApp()); } - ``` -## Priority of loaders +--- +## Priority of loaders (_highest first_) -- **Local loader**: the one you override in the `_ScreenState` class -- **Global loader**: the one you specify in `ScreenLoaderApp`. Note: if you don't override `local()`, this loader will be used. +- **Local loader**: the one you override in your widget +- **Global loader**: the one you specify in `configScreenLoader`. Note: if you don't override `local()`, this loader will be used - **Default loader**: if you don't specify global loader or override local loader, this loader will be used +--- +# Migration guide to 4.0.0 +1. `ScreenLoaderApp` widget is removed. Now no need to wrap your App around any widget just to set the global configurations. Instead call `configScreenLoader()` before `runApp()` +```diff +-void main() => runApp(MyApp()); ++void main() { ++ configScreenLoader( ++ loader: AlertDialog( ++ title: Text('Gobal Loader..'), ++ ), ++ bgBlur: 20.0, ++ ); ++ runApp(MyApp()); ++} + + class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { +- return ScreenLoaderApp( +- app: MaterialApp( +- debugShowCheckedModeBanner: false, +- title: 'Screen Loader', +- theme: ThemeData( +- primarySwatch: Colors.blue, +- ), +- home: Screen(), +- ), +- globalLoader: AlertDialog( +- title: Text('Gobal Loader..'), ++ return MaterialApp( ++ debugShowCheckedModeBanner: false, ++ title: 'Screen Loader', ++ theme: ThemeData( ++ primarySwatch: Colors.blue, + ), +- globalLoadingBgBlur: 20.0, ++ home: Screen(), + ); + } + } +``` +2. Instead of replacing the `build` function with `screen` use the `build` function itself and wrap the widget you want to show loader on with the `loadableWidget`. +```diff + +-class _ScreenState extends State with ScreenLoader { ++class _ScreenState extends State with ScreenLoader { + @override + loader() { + return AlertDialog( +@@ -49,17 +51,19 @@ class _ScreenState extends State with ScreenLoader { + } + + @override +- Widget screen(BuildContext context) { +- return Scaffold( +- appBar: AppBar( +- title: Text('ScreenLoader Example'), +- ), +- body: _buildBody(), +- floatingActionButton: FloatingActionButton( +- onPressed: () async { +- await this.performFuture(NetworkService.getData); +- }, +- child: Icon(Icons.refresh), ++ Widget build(BuildContext context) { ++ return loadableWidget( ++ child: Scaffold( ++ appBar: AppBar( ++ title: Text('ScreenLoader Example'), ++ ), ++ body: _buildBody(), ++ floatingActionButton: FloatingActionButton( ++ onPressed: () async { ++ await this.performFuture(NetworkService.getData); ++ }, ++ child: Icon(Icons.refresh), ++ ), + ), + ); + } +``` + +--- ### PS - PRs are welcome - Please raise issues on https://github.com/arnold-parge/screen_loader. diff --git a/example/main.dart b/example/main.dart index bf6876b..6ef2745 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,24 +1,26 @@ import 'package:flutter/material.dart'; import 'package:screen_loader/screen_loader.dart'; -void main() => runApp(MyApp()); +void main() { + configScreenLoader( + loader: AlertDialog( + title: Text('Gobal Loader..'), + ), + bgBlur: 20.0, + ); + runApp(MyApp()); +} class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return ScreenLoaderApp( - app: MaterialApp( - debugShowCheckedModeBanner: false, - title: 'Screen Loader', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: Screen(), + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Screen Loader', + theme: ThemeData( + primarySwatch: Colors.blue, ), - globalLoader: AlertDialog( - title: Text('Gobal Loader..'), - ), - globalLoadingBgBlur: 20.0, + home: Screen(), ); } } @@ -28,7 +30,8 @@ class Screen extends StatefulWidget { _ScreenState createState() => _ScreenState(); } -class _ScreenState extends State with ScreenLoader { +/// A Stateful screen +class _ScreenState extends State with ScreenLoader { @override loader() { return AlertDialog( @@ -49,46 +52,47 @@ class _ScreenState extends State with ScreenLoader { } @override - Widget screen(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('ScreenLoader Example'), - ), - body: _buildBody(), - floatingActionButton: FloatingActionButton( - onPressed: () async { - await this.performFuture(NetworkService.getData); - }, - child: Icon(Icons.refresh), + Widget build(BuildContext context) { + return loadableWidget( + child: Scaffold( + appBar: AppBar( + title: Text('ScreenLoader Example'), + ), + body: _buildBody(), + floatingActionButton: FloatingActionButton( + onPressed: () async { + await this.performFuture(NetworkService.getData); + }, + child: Icon(Icons.refresh), + ), ), ); } } -class BasicScreen extends StatefulWidget { - @override - _BasicScreenState createState() => _BasicScreenState(); -} +/// A Stateless screen +class BasicScreen extends StatelessWidget with ScreenLoader { + BasicScreen({ Key? key }) : super(key: key); -class _BasicScreenState extends State - with ScreenLoader { @override - Widget screen(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Basic ScreenLoader Example'), - ), - body: Center( - child: Icon( - Icons.home, - size: MediaQuery.of(context).size.width, + Widget build(BuildContext context) { + return loadableWidget( + child: Scaffold( + appBar: AppBar( + title: Text('Basic ScreenLoader Example'), + ), + body: Center( + child: Icon( + Icons.home, + size: MediaQuery.of(context).size.width, + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () async { + await this.performFuture(NetworkService.getData); + }, + child: Icon(Icons.refresh), ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () async { - await this.performFuture(NetworkService.getData); - }, - child: Icon(Icons.refresh), ), ); } diff --git a/lib/screen_loader.dart b/lib/screen_loader.dart index d4d8707..0462a1d 100644 --- a/lib/screen_loader.dart +++ b/lib/screen_loader.dart @@ -2,32 +2,46 @@ library screen_loader; import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:stream_mixin/stream_mixin.dart'; -mixin ScreenLoader on State { - bool isLoading = false; - static Widget? _globalLoader; - static double? _globalLoadingBgBlur = 5.0; +void configScreenLoader({ + required Widget? loader, + required double bgBlur, +}) { + GlobalScreenLoader.loader = loader; + GlobalScreenLoader.bgBlur = bgBlur; +} + +abstract class GlobalScreenLoader { + static Widget? loader; + static double bgBlur = 5.0; +} + +class ScreenLoaderStream with StreamMixin { + @override + bool get lastUpdate => false; +} + + +mixin ScreenLoader { + final ScreenLoaderStream _screenLoaderStream = ScreenLoaderStream(); /// starts the [loader] startLoading() { - setState(() { - isLoading = true; - }); + _screenLoaderStream.update(true); } /// stops the [loader] stopLoading() { - setState(() { - isLoading = false; - }); + _screenLoaderStream.update(false); } - - /// DO NOT use this method in FutureBuilder because this methods - /// updates the state which will make future builder to call - /// this function again and it will go in loop - Future performFuture(Function futureCallback) async { + + /// To avoid use of [startLoading] and [stopLoading] you use use + /// [performFuture] which will show the loader until the passed future call + /// is done executing + Future performFuture(Function futureCallback) async { startLoading(); - T? data = await futureCallback(); + U? data = await futureCallback(); stopLoading(); return data; } @@ -38,7 +52,7 @@ mixin ScreenLoader on State { } double _loadingBgBlur() { - return loadingBgBlur() ?? ScreenLoader._globalLoadingBgBlur ?? 5.0; + return loadingBgBlur() ?? GlobalScreenLoader.bgBlur; } /// override [loader] if you wish to add custom loader in specific view @@ -48,7 +62,7 @@ mixin ScreenLoader on State { Widget _loader() { return loader() ?? - ScreenLoader._globalLoader ?? + GlobalScreenLoader.loader ?? const CircularProgressIndicator(); } @@ -61,43 +75,28 @@ mixin ScreenLoader on State { ); } - Widget screen(BuildContext context); - - @override - Widget build(BuildContext context) { + Widget loadableWidget({required Widget child}) { return Stack( children: [ - screen(context), - if (isLoading) - BackdropFilter( - child: _buildLoader(), - filter: ImageFilter.blur( - sigmaX: _loadingBgBlur(), - sigmaY: _loadingBgBlur(), - ), - ), + child, + StreamBuilder( + initialData: false, + stream: _screenLoaderStream.onChange, + builder: (ctx, snap) { + if (!(snap.data ?? false)) { + return const SizedBox(); + } else { + return BackdropFilter( + child: _buildLoader(), + filter: ImageFilter.blur( + sigmaX: _loadingBgBlur(), + sigmaY: _loadingBgBlur(), + ), + ); + } + }, + ), ], ); } } - -/// [ScreenLoaderApp] is used to provide global settings for the screen loader -class ScreenLoaderApp extends StatelessWidget { - final MaterialApp app; - final Widget? globalLoader; - final double? globalLoadingBgBlur; - - const ScreenLoaderApp({ - Key? key, - required this.app, - this.globalLoader, - this.globalLoadingBgBlur, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - ScreenLoader._globalLoader = globalLoader; - ScreenLoader._globalLoadingBgBlur = globalLoadingBgBlur; - return app; - } -} diff --git a/pubspec.lock b/pubspec.lock index 8077d23..5789602 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -107,6 +107,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_mixin: + dependency: "direct main" + description: + name: stream_mixin + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.4" string_scanner: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ca50190..e80f294 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: screen_loader -description: Easy to use mixin ScreenLoader, which will handle the loading on the screen. You can customise the loading as well. -version: 3.0.2 +description: Easy to use mixin ScreenLoader, which will handle the loading on the screen without using state or navigation stack. You can customise the loading as well. +version: 4.0.0 homepage: https://github.com/arnold-parge/screen_loader environment: @@ -9,6 +9,7 @@ environment: dependencies: flutter: sdk: flutter + stream_mixin: ^3.0.4 dev_dependencies: flutter_test: