Skip to content

Instantly share code, notes, and snippets.

@Andrious
Last active August 20, 2023 03:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Andrious/da8348b60f81bb5e49c5dd5623d88b4c to your computer and use it in GitHub Desktop.
Save Andrious/da8348b60f81bb5e49c5dd5623d88b4c to your computer and use it in GitHub Desktop.
Counter App Example using the Pub.dev package, state_extended
//
import 'package:flutter/material.dart';
import 'package:state_extended/state_extended.dart';
void main() => runApp(const MyApp(key: Key('MyApp')));
/// README.md example app
class MyApp extends StatefulWidget {
///
const MyApp({super.key, this.title = 'StateX Demo App'});
/// Title of the screen
// Fields in a StatefulWidget should always be "final".
final String title;
/// This is the App's State object
@override
State createState() => _MyAppState();
}
class _MyAppState extends AppStateX<MyApp> {
factory _MyAppState() => _this ??= _MyAppState._();
_MyAppState._() : super(controller: AppController()) {
/// Acquire a reference to the passed Controller.
con = controller as AppController;
}
static _MyAppState? _this;
late AppController con;
/// Place a breakpoint here to see what's going on 'under the hood.'
@override
Widget build(BuildContext context) => super.build(context);
/// Define the 'look and fell' of the overall app.
/// The body: property takes in a separate widget for the 'home' page.
@override
Widget buildIn(BuildContext context) => MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(title: widget.title),
);
}
/// The Home page
class MyHomePage extends StatefulWidget {
///
const MyHomePage({super.key, this.title});
/// Title of the screen
// Fields in a StatefulWidget should always be "final".
final String? title;
@override
State createState() => _MyHomePageState();
}
/// This is a subclass of the State class.
/// This subclass is linked to the App's lifecycle using [WidgetsBindingObserver]
class _MyHomePageState extends StateX<MyHomePage> {
/// Let the 'business logic' run in a Controller
_MyHomePageState() : super(controller: HomeController(), useInherited: true) {
con = controller as HomeController;
}
late HomeController con;
@override
void initState() {
/// Look inside the parent function and see it calls
/// all it's Controllers if any.
super.initState();
/// Retrieve the 'app level' State object
appState = rootState!;
/// You're able to retrieve the Controller(s) from other State objects.
final con = appState.controller;
/// Another way to retrieve the 'app level' State object
appState = con?.state!.startState as AppStateX;
/// You can retrieve by type as well
appState = stateByType<AppStateX>()!;
}
late AppStateX appState;
/// Place a breakpoint here to see what's going on 'under the hood.'
@override
Widget build(BuildContext context) => super.build(context);
/// Place a breakpoint here to see what's going on 'under the hood.'
@override
Widget buildF(BuildContext context) => super.buildF(context);
@override
Widget buildIn(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(widget.title ?? ''),
// popup menu button
actions: [con.popupMenuButton],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
style: Theme.of(context).textTheme.bodyMedium,
),
/// Linked to the built-in InheritedWidget.
/// A Text widget to display the counter is in here.
/// ONLY THIS WIDGET is updated with every press of the button.
const CounterWidget(),
],
),
),
floatingActionButton: FloatingActionButton(
key: const Key('+'),
// rebuilds only the Text widget containing the counter.
onPressed: () => con.onPressed(),
child: const Icon(Icons.add),
),
);
}
/// Demonstrating the InheritedWidget's ability to spontaneously rebuild
/// its dependent widgets.
class CounterWidget extends StatefulWidget {
/// Pass along the State Object Controller to make this widget
/// dependent on the App's InheritedWidget.
const CounterWidget({super.key});
@override
State<StatefulWidget> createState() => _CounterState();
}
class _CounterState extends State<CounterWidget> {
@override
Widget build(BuildContext context) {
/// Making this widget dependent will cause the build() function below
/// to run again if and when the App's InheritedWidget calls its notifyClients() function.
final con = HomeController();
con.dependOnInheritedWidget(context);
return Text(
con.data,
style: Theme.of(context).textTheme.headlineMedium,
);
}
}
/// Everything a State object can do, this Controller can do as well!
class HomeController extends StateXController {
/// Utilizing the Singleton pattern is a good programming practice
factory HomeController() => _this ??= HomeController._();
// This constructor is hidden with the underscore.
HomeController._()
: _model = Model(),
_letters = AlphabetLetters(),
_primes = PrimeNumbers();
static HomeController? _this;
final Model _model;
final AlphabetLetters _letters;
final PrimeNumbers _primes;
/// Note, each count comes from a separate class.
String get data {
String data;
switch (_countType) {
case CountType.prime:
data = _primes.primeNumber.toString();
break;
case CountType.alphabet:
data = _letters.current;
break;
default:
data = _model.integer.toString();
}
return data;
}
CountType _countType = CountType.integer;
/// The Controller deals with the event handling and business logic.
void onPressed() {
switch (_countType) {
case CountType.prime:
_primes.next();
break;
case CountType.alphabet:
_letters.read();
break;
default:
_model.incrementCounter();
}
//
notifyClients();
}
/// Supply an 'error handler' routine if something goes wrong
/// in initAsync() routine above.
@override
bool onAsyncError(FlutterErrorDetails details) => false;
/// Provide a menu to this simple app.
PopupMenuButton get popupMenuButton => PopupMenuButton<CountType>(
itemBuilder: (context) => [
PopupMenuItem(
value: CountType.integer,
child: Row(
children: [
if (_countType == CountType.integer)
const Icon(Icons.star_rounded, color: Colors.black),
const Text("Integers")
],
),
),
PopupMenuItem(
value: CountType.alphabet,
child: Row(
children: [
if (_countType == CountType.alphabet)
const Icon(Icons.star_rounded, color: Colors.black),
const Text("Alphabet")
],
),
),
PopupMenuItem(
value: CountType.prime,
child: Row(
children: [
if (_countType == CountType.prime)
const Icon(Icons.star_rounded, color: Colors.black),
const Text("Prime Numbers")
],
),
),
],
onSelected: (value) {
switch (value) {
case CountType.prime:
_countType = value;
break;
case CountType.alphabet:
_countType = value;
break;
default:
// In case the enumeration class was unknowingly changed
// Default to integer
_countType = CountType.integer;
}
// 'Refresh' the home screen to show the new count option
setState(() {});
// var state = this.state; // The controller's current State object
// // If you're not confident the its the intended State class.
// state = stateOf<MyHomePage>();
// state = ofState<_MyHomePageState>();
// state?.setState(() {});
},
offset: const Offset(0, 40),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 14,
);
/// Like the State object, the Flutter framework will call this method exactly once.
/// Only when the [StateX] object is first created.
@override
void initState() {
super.initState();
/// A State object can reference it's 'current' State object controller.
var thisController = state?.controller;
/// The same controller can be retrieved by its unique identifier if you know it.
/// You then don't have to know the type or the type is private with a leading underscore.
/// Note, it has to be a Controller explicitly added to the State object at some time.
thisController = state?.controllerById(thisController?.identifier);
assert(thisController == this,
'Just demonstrating the means to retrieve a Controller.');
/// You can retrieve a Controller's state object by its StatefulWidget
/// Good if the state class type is unknown or private with a leading underscore.
//ignore: unused_local_variable
var stateObj = stateOf<MyHomePage>();
/// Retrieve the 'app level' State object
final appState = rootState;
assert(appState is _MyAppState,
"Every Controller has access to the 'first' State object.");
/// The 'app level' State object has *all* the Stat objects running in the App
/// at any one point of time.
stateObj = appState?.stateByType<_MyHomePageState>();
/// Retrieve the State object's controller.
final appController = appState?.controller;
/// You're able to retrieve the Controller(s) from other State objects.
/// if you know their unique identifier.
final con = appState?.controllerById(appController?.identifier);
assert(appController == con, 'They should be the same object.');
}
/// The framework calls this method whenever it removes this [StateX] object
/// from the tree.
@override
void deactivate() {
super.deactivate();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: deactivate in HomeController');
}
}
/// Called when this object is reinserted into the tree after having been
/// removed via [deactivate].
@override
void activate() {
super.activate();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: activate in HomeController');
}
}
/// The framework calls this method when this [StateX] object will never
/// build again.
/// Note: THERE IS NO GUARANTEE THIS METHOD WILL RUN in the Framework.
@override
void dispose() {
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: dispose in HomeController');
}
super.dispose();
}
/// Called when the corresponding [StatefulWidget] is recreated.
@override
void didUpdateWidget(StatefulWidget oldWidget) {
/// The framework always calls build() after calling [didUpdateWidget], which
/// means any calls to [setState] in [didUpdateWidget] are redundant.
super.didUpdateWidget(oldWidget);
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: didUpdateWidget in HomeController');
}
}
/// Called when this [StateX] object is first created immediately after [initState].
/// Otherwise called only if this [State] object's Widget
/// is a dependency of [InheritedWidget].
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: didChangeDependencies in HomeController');
}
}
/// Called whenever the application is reassembled during debugging, for
/// example during hot reload.
@override
void reassemble() {
super.reassemble();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: reassemble in HomeController');
}
}
/// Called when the system tells the app to pop the current route.
/// For example, on Android, this is called when the user presses
/// the back button.
///
/// Observers are notified in registration order until one returns
/// true. If none return true, the application quits.
/// This method exposes the `popRoute` notification from
// ignore: comment_references
/// [SystemChannels.navigation].
@override
Future<bool> didPopRoute() async {
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: didPopRoute in HomeController');
}
return super.didPopRoute();
}
/// Called when the host tells the app to push a new route onto the
/// navigator.
/// This method exposes the `pushRoute` notification from
// ignore: comment_references
/// [SystemChannels.navigation].
@override
Future<bool> didPushRoute(String route) async {
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: didPushRoute in HomeController');
}
return super.didPushRoute(route);
}
/// Called when the host tells the application to push a new
/// [RouteInformation] and a restoration state onto the router.
/// This method exposes the `popRoute` notification from
// ignore: comment_references
/// [SystemChannels.navigation].
///
/// The default implementation is to call the [didPushRoute] directly with the
/// [RouteInformation.location].
@override
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: didPushRouteInformation in HomeController');
}
return super.didPushRouteInformation(routeInformation);
}
/// Called when the application's dimensions change. For example,
/// when a phone is rotated.
@override
void didChangeMetrics() {
super.didChangeMetrics();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: didChangeMetrics in HomeController');
}
}
/// Called when the platform's text scale factor changes.
@override
void didChangeTextScaleFactor() {
super.didChangeTextScaleFactor();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: didChangeTextScaleFactor in HomeController');
}
}
/// Brightness changed.
@override
void didChangePlatformBrightness() {
super.didChangePlatformBrightness();
if (inDebugMode) {
//ignore: avoid_print
print(
'############ Event: didChangePlatformBrightness in HomeController');
}
}
/// Called when the system tells the app that the user's locale has changed.
@override
void didChangeLocales(List<Locale>? locales) {
super.didChangeLocales(locales);
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: didChangeLocales in HomeController');
}
}
/// Called when the system puts the app in the background or returns the app to the foreground.
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
/// Passing these possible values:
/// AppLifecycleState.inactive (may be paused at any time)
/// AppLifecycleState.paused (may enter the suspending state at any time)
/// AppLifecycleState.detach
/// AppLifecycleState.resumed
super.didChangeAppLifecycleState(state);
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: didChangeAppLifecycleState in HomeController');
}
}
/// The application is in an inactive state and is not receiving user input.
///
/// On iOS, this state corresponds to an app or the Flutter host view running
/// in the foreground inactive state. Apps transition to this state when in
/// a phone call, responding to a TouchID request, when entering the app
/// switcher or the control center, or when the UIViewController hosting the
/// Flutter app is transitioning.
///
/// On Android, this corresponds to an app or the Flutter host view running
/// in the foreground inactive state. Apps transition to this state when
/// another activity is focused, such as a split-screen app, a phone call,
/// a picture-in-picture app, a system dialog, or another window.
///
/// Apps in this state should assume that they may be [pausedLifecycleState] at any time.
@override
void inactiveLifecycleState() {
super.inactiveLifecycleState();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: inactiveLifecycleState in HomeController');
}
}
/// The application is not currently visible to the user, not responding to
/// user input, and running in the background.
@override
void pausedLifecycleState() {
super.pausedLifecycleState();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: pausedLifecycleState in HomeController');
}
}
/// Either be in the progress of attaching when the engine is first initializing
/// or after the view being destroyed due to a Navigator pop.
@override
void detachedLifecycleState() {
super.detachedLifecycleState();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: detachedLifecycleState in HomeController');
}
}
/// The application is visible and responding to user input.
@override
void resumedLifecycleState() {
super.resumedLifecycleState();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: resumedLifecycleState in HomeController');
}
}
/// Called when the system is running low on memory.
@override
void didHaveMemoryPressure() {
super.didHaveMemoryPressure();
if (inDebugMode) {
//ignore: avoid_print
print('############ Event: didHaveMemoryPressure in HomeController');
}
}
/// Called when the system changes the set of active accessibility features.
@override
void didChangeAccessibilityFeatures() {
super.didChangeAccessibilityFeatures();
if (inDebugMode) {
//ignore: avoid_print
print(
'############ Event: didChangeAccessibilityFeatures in HomeController');
}
}
}
// The means 'to talk' between the Controller and the Model
enum CountType { integer, prime, alphabet }
/// A separate class that contains the data.
class Model {
int _integer = 0;
// The external property transferring the value to the outside world.
int get integer => _integer;
/// The business logic involves incrementing something.
void incrementCounter() => ++_integer;
}
/// Goes through the alphabet.
class AlphabetLetters {
// Used for incrementing the alphabet
int start = "a".codeUnitAt(0);
int end = "z".codeUnitAt(0);
late int letter = start;
// The external property transferring the value to the outside world.
String get current => String.fromCharCode(letter);
/// The business logic involves incrementing something.
void read() {
letter++;
if (letter > end) {
letter = start;
}
}
}
/// Another class. A complete different type of data conceived.
class PrimeNumbers {
PrimeNumbers({int? start, int? end}) {
start = start ?? 1;
end = end ?? 1000;
if (start < 0) {
start = 1;
}
if (end <= start) {
end = 1000;
}
initPrimeNumbers(start, end);
}
final List<int> _numbers = [];
int _cnt = 0;
int get primeNumber => _numbers[_cnt];
void next() {
_cnt++;
if (_cnt > _numbers.length) {
_cnt = 0;
}
}
void initPrimeNumbers(int M, int N) {
a:
for (var k = M; k <= N; ++k) {
for (var i = 2; i <= k / i; ++i) {
if (k % i == 0) {
continue a;
}
}
_numbers.add(k);
}
}
}
/// Everything a State object can do, this Controller can do as well!
class AppController extends StateXController {
factory AppController() => _this ??= AppController._();
AppController._();
static AppController? _this;
/// Used for long asynchronous operations that need to be done
/// before the app can be fully available to the user.
/// e.g. Opening Databases, accessing Web servers, etc.
@override
Future<bool> initAsync() async {
// Simply wait for 10 seconds at startup.
/// In production, this is where databases are accessed, web services opened, login attempts, etc.
return Future.delayed(const Duration(seconds: 10), () {
return true;
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment