Last active
July 4, 2024 16:53
-
-
Save HenriqueNas/dedb6e6523f1ba03acedf780d2c89ef9 to your computer and use it in GitHub Desktop.
Easiest way to manager your Flutter page/widget state without any package.
This file contains 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'; | |
/// Function that receives a [BuildContext] and a [PageState] and returns a [Widget]. </br> | |
typedef WidgetBuilder = Widget Function(BuildContext context, PageState state); | |
/// Helper class to manage the state of a page/widget. </br> | |
class PageStateNotifier<Error> extends ChangeNotifier { | |
PageState _state = PageState.idle; | |
/// The current [PageState]. | |
/// This is the main state of the notifier | |
/// and should be used to control the UI. | |
PageState get state => _state; | |
/// Set the current [PageState] and notify the listeners. | |
set state(final PageState state) { | |
_state = state; | |
notifyListeners(); | |
} | |
Error? _error; | |
/// If the current state is [PageState.error], this will return the error. | |
Error? get error => _error; | |
/// Set the error and change the state to [PageState.error]. | |
void setError([final Error? error]) { | |
_error = error; | |
_state = PageState.error; | |
notifyListeners(); | |
} | |
/// Returns if the current state is [PageState.idle]. | |
bool get isIdle => _state.isIdle; | |
/// Returns if the current state is [PageState.error]. | |
bool get isError => _state.isError; | |
/// Returns if the current state is [PageState.loading]. | |
bool get isLoading => _state.isLoading; | |
/// Returns if the current state is [PageState.success]. | |
bool get isSuccess => _state.isSuccess; | |
/// Set the state to [PageState.loading] and notify the listeners. | |
void setLoading() { | |
_state = PageState.loading; | |
notifyListeners(); | |
} | |
/// Set the state to [PageState.success] and notify the listeners. | |
void setSuccess() { | |
_state = PageState.success; | |
notifyListeners(); | |
} | |
/// Set the state to [PageState.idle] and notify the listeners. | |
void setIdle() { | |
_state = PageState.idle; | |
notifyListeners(); | |
} | |
/// Add a listener to the notifier and execute the | |
/// callback when the state changes to [PageState.error]. | |
void onError(final void Function(Error? error) callback) { | |
addListener(() { | |
if (isError) callback(error); | |
}); | |
} | |
/// Add a listener to the notifier and execute the | |
/// callback when the state changes to [PageState.success]. | |
void onSuccess(final VoidCallback callback) { | |
addListener(() { | |
if (isSuccess) callback(); | |
}); | |
} | |
/// Add a listener to the notifier and execute the | |
/// callback when the state changes to [PageState.loading]. | |
void onLoading(final VoidCallback callback) { | |
addListener(() { | |
if (isLoading) callback(); | |
}); | |
} | |
/// Add a listener to the notifier and execute the | |
/// callback when the state changes to [PageState.idle]. | |
void onIdle(final VoidCallback callback) { | |
addListener(() { | |
if (isIdle) callback(); | |
}); | |
} | |
/// Returns a [ListenableBuilder] that listens to any change. | |
Widget builder(final WidgetBuilder builder) => ListenableBuilder( | |
listenable: this, | |
builder: (final context, final _) => builder(context, state), | |
); | |
/// Returns a [ListenableBuilder] that listens to any change. | |
Widget stateBuilder({ | |
final Widget Function() idle = SizedBox.shrink, | |
final Widget Function()? loading, | |
final Widget Function()? success, | |
final Widget Function(Error? error)? error, | |
final PageState fallbackState = PageState.idle, | |
}) { | |
assert( | |
fallbackState == PageState.loading && loading != null, | |
'Your fallbackState is loading, but you did not provide a loading widget', | |
); | |
assert( | |
fallbackState == PageState.error && error != null, | |
'Your fallbackState is error, but you did not provide an error widget', | |
); | |
assert( | |
fallbackState == PageState.success && success != null, | |
'Your fallbackState is success, but you did not provide a success widget', | |
); | |
final fallbackWidget = switch (fallbackState) { | |
PageState.idle => idle(), | |
PageState.loading => loading!.call(), | |
PageState.error => error!.call(_error), | |
PageState.success => success!.call(), | |
}; | |
final widget = switch (_state) { | |
PageState.idle => idle(), | |
PageState.loading => loading?.call(), | |
PageState.error => error?.call(_error), | |
PageState.success => success?.call(), | |
}; | |
return ListenableBuilder( | |
listenable: this, | |
builder: (final _, final ___) => widget ?? fallbackWidget, | |
); | |
} | |
} | |
enum PageState { | |
idle, | |
loading, | |
error, | |
success; | |
bool get isIdle => this == idle; | |
bool get isLoading => this == loading; | |
bool get isSuccess => this == success; | |
bool get isError => this == error; | |
} |
your page:
your page can build widgets with the builder
and stateBuilder
methods of your HomeController
:
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final controller = HomeController();
@override
void initState() {
super.initState();
// when the state is set to Error, call this:
controller.onError((final error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error ?? 'Unknown error')),
);
});
}
@override
Widget build(final BuildContext context) {
return controller.stateBuilder(
idle: () => Center(
child: ElevatedButton(
onPressed: controller.fetch,
child: const Text('Fetch'),
),
),
loading: () => const Center(child: CircularProgressIndicator()),
success: () => Center(child: Text(controller.userName ?? 'No user name')),
);
}
}
otima abordagem
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
how to use it:
Usually you will create a "controller" (or whatever you want to call it) that extends the
PageStateNotifier
and you can bind to the page that you want to control!Example: