Last active
December 27, 2023 20:24
-
-
Save slightfoot/80b9c97ff68a373e80d5d5982585736e to your computer and use it in GitHub Desktop.
Example of state management without Libraries - by Simon Lightfoot - Humpday Q&A :: 13th December 2023 #Flutter #Dart - https://www.youtube.com/live/7k_tMJF1B-0?si=l5u-AN9YrpN_il9i&t=4402
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
// MIT License | |
// | |
// Copyright (c) 2023 Simon Lightfoot | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in all | |
// copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
// | |
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(const ExampleApp()); | |
} | |
@immutable | |
class AppState { | |
const AppState({ | |
required this.counter, | |
}); | |
final int counter; | |
static const initial = AppState(counter: 0); | |
AppState copyWith({ | |
int? counter, | |
}) { | |
return AppState( | |
counter: counter ?? this.counter, | |
); | |
} | |
} | |
class AppStateRepo extends ChangeNotifier { | |
AppStateRepo() : _state = AppState.initial; | |
AppState _state; | |
AppState get state => _state; | |
void increaseCount(int increase) { | |
_state = _state.copyWith( | |
counter: _state.counter + increase, | |
); | |
print('inc: ${_state.counter}'); | |
notifyListeners(); | |
} | |
} | |
class ExampleApp extends StatefulWidget { | |
const ExampleApp({super.key}); | |
static AppStateRepo appStateRepo(BuildContext context) { | |
return context.findAncestorStateOfType<_ExampleAppState>()!.appStateRepo; | |
} | |
@override | |
State<ExampleApp> createState() => _ExampleAppState(); | |
} | |
class _ExampleAppState extends State<ExampleApp> { | |
final appStateRepo = AppStateRepo(); | |
@override | |
Widget build(BuildContext context) { | |
return ListenableBuilder( | |
listenable: appStateRepo, | |
builder: (BuildContext context, Widget? child) { | |
return AppStateScope( | |
appState: appStateRepo.state, | |
child: child!, | |
); | |
}, | |
child: const MaterialApp( | |
debugShowCheckedModeBanner: false, | |
home: HomeScreen(), | |
), | |
); | |
} | |
} | |
class AppStateScope extends InheritedWidget { | |
const AppStateScope({ | |
super.key, | |
required this.appState, | |
required super.child, | |
}); | |
final AppState appState; | |
static AppState of(BuildContext context) { | |
return context | |
.dependOnInheritedWidgetOfExactType<AppStateScope>()! | |
.appState; | |
} | |
@override | |
bool updateShouldNotify(AppStateScope oldWidget) { | |
return appState != oldWidget.appState; | |
} | |
} | |
mixin AppStateRepoMixin<T extends StatefulWidget> on State<T> { | |
late final AppStateRepo appStateRepo = context.appStateRepo; | |
late final AppState appState = context.appStateRepo.state; | |
} | |
extension AppStateRepoExtension on BuildContext { | |
AppStateRepo get appStateRepo => ExampleApp.appStateRepo(this); | |
AppState get appState => AppStateScope.of(this); | |
} | |
class HomeScreen extends StatelessWidget { | |
const HomeScreen({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar(title: const Text('Example')), | |
body: const PageContent(), | |
); | |
} | |
} | |
class PageContent extends StatelessWidget { | |
const PageContent({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return SizedBox.expand( | |
child: DefaultTextStyle.merge( | |
style: const TextStyle(fontSize: 32.0), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
Text('Count: ${context.appState.counter}'), | |
const SizedBox(height: 32.0), | |
if (context.appState.counter < 5) // | |
const IncreaseCounterButton(increase: 1, label: 'First'), | |
const SizedBox(height: 32.0), | |
const IncreaseCounterButton(increase: 2, label: 'Second'), | |
], | |
), | |
), | |
); | |
} | |
} | |
class IncreaseCounterButton extends StatelessWidget { | |
const IncreaseCounterButton({ | |
super.key, | |
required this.increase, | |
required this.label, | |
}); | |
final int increase; | |
final String label; | |
@override | |
Widget build(BuildContext context) { | |
return InkWell( | |
onTap: () => context.appStateRepo.increaseCount(increase), | |
child: Padding( | |
padding: const EdgeInsets.all(12.0), | |
child: Text('Button $label'), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment