Separating UI and Logic in Flutter - flutter

Normally, I use a separate class with an object declared on the top of the widget. I wish to know what is the problem with that architecture.
I came across an entire package in Flutter, WidgetView, which needs to declare a dependency, then make a state object, and then do the same thing.
Why not just a simple class for achieving the same. like below
class NewAccountComponent extends StatelessWidget {
final NewAccountComponentLogic logic = NewAccountComponentLogic();
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Enter a Unique Account Number'),
titlePadding: EdgeInsets.all(20.0),
content: TextFormField(
controller: logic.controller,
onPressed: () => logic.clearTextFormField(),
),
),
}
class NewAccountComponentLogic {
static String accountNumber;
static bool existsAccountNumber;
TextEditingController controller = TextEditingController();
clearTextFormField() {
controller.text = '';
accountNumber = '';
}

You can separate widget logic and presentation in many ways. One that I've seen (and that you mention) is using the WidgetView pattern. You can do it without any dependency:
Create an abstract class thats contains the logic that all WidgetViews should be implement:
For Stateless widgets:
abstract class StatelessView<T1> extends StatelessWidget {
final T1 widget;
const StatelessView(this.widget, {Key key}) : super(key: key);
#override
Widget build(BuildContext context);
}
For Stateful widgets:
abstract class WidgetView<T1, T2> extends StatelessWidget {
final T2 state;
T1 get widget => (state as State).widget as T1;
const WidgetView(this.state, {Key key}) : super(key: key);
#override
Widget build(BuildContext context);
}
Create your widget normmally:
// Note it's a StatefulWidget because accountNumber mutates
class NewAccountComponent extends StatefulWidget {
#override
_NewAccountComponentState createState() => _NewAccountComponentState();
}
class _NewAccountComponentState extends State<NewAccountComponent> {
String accountNumber;
bool existsAccountNumber;
final TextEditingController controller = TextEditingController();
clearTextFormField() {
controller.text = '';
accountNumber = '';
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Enter a Unique Account Number'),
titlePadding: EdgeInsets.all(20.0),
content: TextFormField(
controller: controller,
onSaved: (value) => clearTextFormField(),
),
);
}
}
If the widget is a Stateful
class NewAccountComponent extends StatefulWidget {
#override
_NewAccountComponentController createState() => _NewAccountComponentController();
}
// State suffix renamed to Controller
// This class has all widget logic
class _NewAccountComponentController extends State<NewAccountComponent> {
String accountNumber;
bool existsAccountNumber;
final TextEditingController controller = TextEditingController();
clearTextFormField() {
controller.text = '';
accountNumber = '';
}
// In build, returns a new instance of your view, sending the current state
#override
Widget build(BuildContext context) => _NewAccountComponentView(this);
}
// View extends of WidgetView and has a current state to access widget logic
// with widget you can access to StatefulWidget parent
class _NewAccountComponentView
extends WidgetView<NewAccountComponent, _NewAccountComponentController> {
_NewAccountComponentView(_NewAccountComponentController state): super(state);
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Enter a Unique Account Number'),
titlePadding: EdgeInsets.all(20.0),
content: TextFormField(
controller: state.controller,
onSaved: (value) => state.clearTextFormField(),
),
);
}
}
If it's Stateless, change from:
class MyStatelessWidget extends StatelessWidget {
final String textContent = "Hello!";
const MyStatelessWidget({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text(textContent),
);
}
}
to:
// Widget and logic controller are unit
class MyStatelessWidget extends StatelessWidget {
final String textContent = "Hello!";
const MyStatelessWidget({Key key}) : super(key: key);
#override
Widget build(BuildContext context) => _MyStatelessView(this);
}
// The view is separately
class _MyStatelessView extends StatelessView<MyStatelessWidget> {
_MyStatelessView(MyStatelessWidget widget) : super(widget);
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.textContent),
);
}
}
References:
Flutter: WidgetView — A Simple Separation of Layout and Logic

I've modified your code a little bit. If you change your code as like the following code, hopefully, you will get the expected output.
class NewAccountComponent extends StatelessWidget {
final NewAccountComponentLogic logic = NewAccountComponentLogic(
'123456',
true,
TextEditingController(),
);
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Enter a Unique Account Number'),
titlePadding: EdgeInsets.all(20.0),
content: TextFormField(
controller: logic.controller,
),
actions: <Widget>[
TextButton(
child: Text('Done'),
onPressed: () {
print(logic.controller.text);
logic.clearTextFormField();
},
),
],
);
}
}
class NewAccountComponentLogic {
String accountNumber;
bool existsAccountNumber;
TextEditingController controller;
NewAccountComponentLogic(
this.accountNumber,
this.existsAccountNumber,
this.controller,
);
void clearTextFormField() {
controller.text = '';
accountNumber = '';
}
#Ignacior has also given a nice solution which you can follow.

Related

how to give a choice for a parameter with an already created widget group(class)?

I have some some stless wrap widget:
class SomeWrap extends StatelessWidget {
// it's work, but i can put any widget, but I want only widget form class MyChoises
Widget MyValue
const SomeWrap({
required this.MyValue,
Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
// Here some settings for UI
// here my value as a widget
child: MyValue;
);
}
}
Here class MyChoises, that return couple widgets:
abstract class StatusTextOrder {
Text processing = Text('Processing',style: TextStyle(color:Colors.Yellow)));
Text delivered = Text('Delivered',style: TextStyle(color:Colors.Green)));
IconButton canceled = IconButton(icon: Text('Canceled', onPressed: ()=>{}))
}
What the correct way to use this "choices" for a value ?
usage:
SomeWrap(MyValue: StatusTextOrder.delivered)
Now into MyValue I can put any Widget, its not that im looking for.
I tried to use none abstract class, and put StatusTextOrder or Widget , but all of this gives me an errors.
Someone said that it will work :
import 'package:flutter/material.dart';
class StatusTextOrder {
static final processing =
Text('Processing', style: TextStyle(color: Colors.yellow));
static final delivered =
Text('Delivered', style: TextStyle(color: Colors.green));
static final canceled = IconButton(
onPressed: () {},
icon: Icon(
Icons.cancel,
color: Colors.red,
));
}
class Wrapper extends StatelessWidget {
StatusTextOrder widget;
Wrapper({Key? key, required this.widget}) : super(key: key);
#override
Widget build(BuildContext context) {
return Placeholder(
child: widget,
);
}
}
class ErrorPage extends StatelessWidget {
const ErrorPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(child:Row(
children: [
Wrapper(widget: StatusTextOrder.delivered),
],
)),
);
}
}
no, it gives errors:
The argument type 'StatusTextOrder' can't be assigned to the parameter type 'Widget?'.
The argument type 'Text' can't be assigned to the parameter type 'StatusTextOrder'.
As comment section included desire behavior, It can be
wrapper class,
//wrapper class
class SomeWrap extends StatelessWidget {
final StatusTextOrder statusTextOrder;
const SomeWrap({required this.statusTextOrder, Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return statusTextOrder;
}
}
//base class
abstract class StatusTextOrder extends Widget {
const StatusTextOrder({super.key});
}
/// concrete class
class Processing extends StatelessWidget implements StatusTextOrder {
const Processing({super.key});
#override
Widget build(BuildContext context) {
return const Text('processing');
}
}
And you need to use like
SomeWrap(statusTextOrder: Processing());
Also it can be
abstract class StatusTextOrder extends StatelessWidget {
const StatusTextOrder({super.key});
}
class Processing extends StatusTextOrder {
const Processing({super.key});
#override
Widget build(BuildContext context) {
return const Text('processing');
}
}
You can create another class with theses concrete Class as static variable and pass like old part.
old:
To use like StatusTextOrder.delivered, you need to make those variable as statics,
abstract class StatusTextOrder {
static Text processing = Text('Processing');
static Text delivered = Text('Delivered');
static IconButton canceled = IconButton(icon: Text('Canceled'), onPressed: () => {});
}
class SomeWrap extends StatelessWidget {
final Widget MyValue;
const SomeWrap({required this.MyValue, Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(child: MyValue);
}
}
You can find more about class-variables-and-methods

Flutter setState function doesn't work when used to change class member

i have the following codes,
class mWidget extends StatefulWidget {
mWidget({super.key, required this.text});
String text;
#override
State<mWidget> createState() => _mWidgetState();
}
class _mWidgetState extends State<mWidget> {
#override
Widget build(BuildContext context) {
return Center(
child: Text(widget.text),
);
}
}
This is my custom widget,
class _MainState extends State<Main> {
var n = mWidget(text: "Hi");
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
n,
ElevatedButton(
onPressed: () {
setState(() {
n.text = "Hello";
});
},
child: Text("Click me"),
),
],
),
);
}
}
And this is the code in the main.dart file.
The problem is that pressing the button doesn't change the output on the screen unless a hot reload even though I am calling the setState function.
I wonder why is that.
Thanks in advance!
You made a couple of mistakes in this!
In your code, you made a widget named mWidget and created an instance of it, it is not the right approach to access any widget using an instance, as state of instances cannot be updated.
You are using the state of mWidget outside of its scope, where it is not accessible.
You can use keys to achieve what you want. (It is not advisable to use this for large-scale project)
Here is a small code which can help you to achieve the functionality you want.
class mWidget extends StatefulWidget {
mWidget({Key? key, required this.text}) : super(key: key);
String text;
#override
State<mWidget> createState() => _mWidgetState();
}
class _mWidgetState extends State<mWidget> {
String text = "";
#override
void initState() {
text = widget.text;
super.initState();
}
void updateValue(String newData) {
setState(() {
text = newData;
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Text(text),
);
}
}
class _Main extends StatefulWidget {
const _Main({Key? key}) : super(key: key);
#override
State<_Main> createState() => _MainState();
}
class _MainState extends State<_Main> {
GlobalKey<_mWidgetState> _mWidgetStateKey = GlobalKey(); // This is the key declaration of _mWidgetState type
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
mWidget(text: "Hi", key: _mWidgetStateKey),
ElevatedButton(
onPressed: () =>
_mWidgetStateKey.currentState!.updateValue("Hello"), // Calling the method of _mWidgetState class.
child: Text("Click me"),
),
],
),
);
}
}
You can reinitialize the n on easy approach like
n = mWidget(text: "Hello");
Or use state-management property like riverpod/bloc. callback method may also help. I am using ValueNotifier, you dont need to make theses statefulWidget
class Main extends StatefulWidget {
const Main({super.key});
#override
State<Main> createState() => _MainState();
}
class _MainState extends State<Main> {
final ValueNotifier textNotifier = ValueNotifier('Hi');
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
mWidget(text: textNotifier),
ElevatedButton(
onPressed: () {
setState(() {
textNotifier.value = "Hello";
});
},
child: Text("Click me"),
),
],
),
);
}
}
class mWidget extends StatefulWidget {
mWidget({super.key, required this.text});
ValueNotifier text;
#override
State<mWidget> createState() => _mWidgetState();
}
class _mWidgetState extends State<mWidget> {
#override
Widget build(BuildContext context) {
return Center(
child: ValueListenableBuilder(
valueListenable: widget.text,
builder: (context, value, child) => Text(value),
));
}
}

Flutter Pass Function With Generic Parameter

I am trying to write a Typehead package. In case of that, I need to pass functions with generic parameters but I am getting a type error.
Here is my simplified code:
Models:
class BaseModel {
BaseModel({required this.title});
String title;
}
class SearchModel extends BaseModel {
SearchModel({required super.title, required this.myText});
String myText;
}
TypeHead Class:
class HorizontalTypeHead<T extends BaseModel> extends StatefulWidget {
const HorizontalTypeHead({
Key? key,
required this.onLookup,
required this.onSelected,
}) : super(key: key);
final Future<Iterable<T>> Function(String value) onLookup;
final Function(T model) onSelected;
#override
State<HorizontalTypeHead> createState() => _HorizontalTypeHeadState<T>();
}
class _HorizontalTypeHeadState<T extends BaseModel> extends State<HorizontalTypeHead> {
Iterable<T> _data = [];
List<Widget> renderColumn(BuildContext context) {
List<Widget> list = [
TextField(
onChanged: (String? val) async {
if (val != null) {
_data = await widget.onLookup(val) as Iterable<T>;
setState(() {});
}
},
),
];
if (_data.isNotEmpty) {
list.add(
SizedBox(
height: 100,
child: ListView(
scrollDirection: Axis.horizontal,
children: _data
.map((e) => ResultWidget<T>(
model: e, onSelected: widget.onSelected))
.toList(),
),
),
);
}
return list;
}
#override
Widget build(BuildContext context) {
return Column(children: renderColumn(context));
}
}
Result Widget:
class ResultWidget<T extends BaseModel> extends StatelessWidget {
const ResultWidget({required this.model, required this.onSelected, Key? key})
: super(key: key);
final T model;
final Function(T selected) onSelected;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onSelected(model),
child: Container(
color: Colors.red,
height: 100,
width: 100,
child: Text(model.title),
));
}
}
My main goal is to access data in SearchModel (myText field). But when I am assigning SearchModel as type, I get a type error.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Future<Iterable<SearchModel>> onLookup(String pattern) async {
return [SearchModel(title: "title", myText: "myText")];
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: HorizontalTypeHead<SearchModel>(
onSelected: ((model) => print(model.myText)), //I need to get myText
onLookup: onLookup,
),
));
}
}
_TypeError (type '(SearchModel) => void' is not a subtype of type '(BaseModel) => dynamic')
I would like to ask what I need to do for accessing SearchModel without any error from main class. Thanks in advance!
I just made generic my state class and solved it :)
class _HorizontalTypeHeadState<T extends BaseModel>
extends State<HorizontalTypeHead<T>>
instead of
class _HorizontalTypeHeadState<T extends BaseModel>
extends State<HorizontalTypeHead>

How to pass text value between two widgets not existing within same file

I have the following widgets where each widget exists in their own file.
(There is alot more going on in each file. I have condensed it to keep it minimal to only see what's needed for this query).
I wish to capture values passed into a TextFormField within one widget and print out those values in another widget.
There is no visual state changes going on thus trying not to store these values via Provider which I felt would be silly for this usecase.
Thus the query is on how to pass down the value captured in a TextEditingController for each widget's instance and pass it down to another widget?
To reiterate, the following 3 classes, they exist in their own Dart file.
I was initially sticking with stateless widget for all 3 but from what I read up, advice is to user a stateful widget where TextEditingController is involved.
Thus the MyField widget is stateful.
MyField Widget - This is where the value is expected to get stored to controller based on what's been typed in.
class MyField extends StatefulWidget {
final String title;
final TextEditingController controller;
const MyField({this.controller, this.title});
#override
_MyFieldState createState() => _MyFieldState();
}
class _MyFieldState extends State<MyField> {
#override
Widget build(BuildContext context) {
return TextFormField(
controller: widget.controller,
);
}
}
MyForm Widget - This takes in 2 instances of above widget, each having its own controller.
This widget helps pass on the text values down to the MyButton widget.
class MyForm extends StatelessWidget {
final formKey = GlobalKey<FormState>();
final nameController = TextEditingController();
final passController = TextEditingController();
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Stack(
children: [
MyField(
title: 'name',
controller: nameController,
),
MyField(
title: 'pass',
controller: passController,
),
MyButton(
name: nameController.text,
pass: passController.text,
formKey: formKey)
],
),
);
}
}
MyButton Widget - This widget captures those text values and tries to print out the values and it currently comes out empty.
class MyButton extends StatelessWidget {
final formKey;
final String name;
final String pass;
const MyButton({Key key, this.formKey, this.name, this.pass}) : super(key: key);
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
// I want to be able to retrieve the text via the controllers for the 2 text fields.
// currently these values are empty which is the issue.
print('name: $name pass: $pass');
},
);
}
}
You can store the value in an object file and get or edit its value from any other file in the project.. for example in file named user.dart :
class user {
static String name;
static String pass;
}
then at any other place import the file and set or get its values as you want:
user.name = nameController.text
user.pass = passController.text
print('name: ' + user.name + 'pass: ' + user.pass);
if your problem is that you want to show the text before the button is clicked, I think you might make your widget stateful and then you can use change event in the Text field:
onChanged: (value) {
setState(() {
user.name = nameController.text;
});}
Here is my try.
Need to change from 'Stack' to 'Column' in MyForm class.
getting value from TextFormField using 'formKey'
(Although I used a formkey, but I hope to control value from outside of 'MyForm' than Getting a value by using 'formkey')
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
Widget _buildBody() {
return MyForm();
}
}
class MyButton extends StatelessWidget {
final formKey;
final String label;
const MyButton({Key key, this.formKey, this.label}) : super(key: key);
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
// I want to be able to retrieve the text via the controllers for the 2 text fields.
// currently these values are empty which is the issue.
print(
'name: ${formKey.currentWidget.child.children[0].controller.text} ');
print(
'pass: ${formKey.currentWidget.child.children[1].controller.text} ');
},
child: Text(label),
);
}
}
class MyForm extends StatelessWidget {
final formKey = GlobalKey<FormState>();
final nameController = TextEditingController();
final passController = TextEditingController();
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
children: [
MyField(
title: 'name',
controller: nameController,
),
MyField(
title: 'pass',
controller: passController,
),
MyButton(label: 'Button', formKey: formKey)
],
),
);
}
}
class MyField extends StatefulWidget {
final String title;
final TextEditingController controller;
const MyField({this.controller, this.title});
#override
_MyFieldState createState() => _MyFieldState();
}
class _MyFieldState extends State<MyField> {
#override
Widget build(BuildContext context) {
return TextFormField(
controller: widget.controller,
);
}
}
Well if you are not using the user typed texts to update the UI state of MyButton widget you don't even need it you can just access the controllers texts in MyForm widget.
class MyForm extends StatelessWidget {
final formKey = GlobalKey<FormState>();
final nameController = TextEditingController();
final passController = TextEditingController();
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
children: [
MyField(
title: 'name',
controller: nameController,
),
MyField(
title: 'pass',
controller: passController,
),
RaisedButton(
onPressed() {
print("${nameController.text}");
print("${passController.text}");
}
),
],
),
);
}
}
But if you want to update MyButton widget on the fly while the user is typing a text so MyForm widgets needs to be Statefull and must rebuild in every user type event.
//NOTE: Assuming `MyForm` is a Statefull widget
final nameController = TextEditingController();
final passController = TextEditingController();
#override
void initState() {
// listening the textfield.
nameController.addListener(_controllerListener);
passController.addListener(_controllerListener);
super.initState();
}
void _controllerListener(){
if(mounted)
setState((){});
}
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Stack(
children: [
MyField(
title: 'name',
controller: nameController,
),
MyField(
title: 'pass',
controller: passController,
),
MyButton(
name: nameController.text,
pass: passController.text,
onPressed: () {
print("${nameController.text} - ${passController.text}");
})
],
),
);
}
}
class MyButton extends StatelessWidget {
final String name;
final String pass;
final VoidCallback onPressed;
const MyButton({Key key, this.onPressed, this.name, this.pass}) : super(key: key);
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: this.onPressed,
// updating UI on type event.
child: Text('$name and $pass'),
);
}
}
please change MyForm class to below Class...
How It Works ?
your Button Should rebuild after each change on TextFormField so I used a Stream builder and a Stream
class MyForm extends StatelessWidget {
final formKey = GlobalKey<FormState>();
final nameController = TextEditingController();
final passController = TextEditingController();
StreamController<int> sc = StreamController<int>();
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
onChanged: () {
sc.add(1);
},
child: Column(
children: [
MyField(
title: 'name',
controller: nameController,
),
MyField(
title: 'pass',
controller: passController,
),
StreamBuilder<int>(
stream: sc.stream,
builder: (context, snapshot) {
return MyButton(
name: nameController.text,
pass: passController.text,
formKey: formKey);
}
)
],
),
);
}
}

How to call setState or update value in InheritedWidget?

Reproducible Code:
void main() => runApp(MaterialApp(home: CountInheritedWidget(child: HomePage())));
class CountInheritedWidget extends InheritedWidget {
CountInheritedWidget({Widget child}) : super(child: child);
final Map<String, int> _map = {"count": 0};
// getter
int get value => _map["count"];
// setter
set value(int x) => _map["count"] = x; // is there anything like setState here?
#override
bool updateShouldNotify(CountInheritedWidget oldCounter) => true;
static CountInheritedWidget of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextWidget(),
ButtonWidget(),
],
),
),
);
}
}
class TextWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
int count = CountInheritedWidget.of(context)?.value ?? -1;
return Text("Count = $count");
}
}
class ButtonWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("Increment"),
onPressed: () {
CountInheritedWidget counter = CountInheritedWidget.of(context);
int count = counter?.value ?? -1;
counter.value = ++count;
},
);
}
}
I'd like to update the value of count from ButtonWidget, I am sure it is getting updated in the CounterInheritedWidget class but it isn't reflecting on the screen. How can I call setState or something like that from InheritedWidget?
Any help will be appreciated, I am newbie to Flutter and Dart so having tough time in solving this kind of issue. Thank you and have a great day.
Note: I am not looking for some plugins like Provider, ScopedModel, Redux for this kinda work.
InheritedWidgets cannot do that. They are completely immutable with no mechanism for triggering updates.
If you want to emit updates, you will have to combine your InheritedWidget with a StatefulWidget, typically done in such way:
class MyWidget extends StatefulWidget {
const MyWidget({Key key, this.child}) : super(key: key);
final Widget child;
#override
MyState createState() => MyState();
}
class MyState extends State<MyWidget> {
String name;
int age;
#override
Widget build(BuildContext context) {
return MyInherited(
name: name,
age: age,
child: widget.child,
);
}
}
Where MyInheritedWidget is:
class MyInherited extends InheritedWidget {
MyInherited({
Key key,
this.name,
this.age,
Widget child,
}) : super(key: key, child: child);
final String name;
final int age;
#override
bool updateShouldNotify(MyInherited oldWidget) {
return name != oldWidget.name && age != oldWidget.age;
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('age', age));
properties.add(StringProperty('name', name));
}
}
Yup. That's verbose. Which is why provider exists.
Here is a complete example:
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(
home: Scaffold(
body: MyStateful(
child: Column(
children: <Widget>[
MyCounter(),
MyButton(),
],
),
),
),
);
}
}
// MyStateful and MyInherited together act like a Provider
class MyStateful extends StatefulWidget {
const MyStateful({Key? key, required this.child}) : super(key: key);
final Widget child;
#override
MyState createState() => MyState();
}
class MyState extends State<MyStateful> {
int _count = 0;
void increment() {
setState(() {
_count += 1;
});
}
#override
Widget build(BuildContext context) {
return MyInherited(
count: _count,
increment: this.increment,
child: widget.child,
);
}
}
// Whenever state values are changes a new MyInherited is created
// with new parameters.
class MyInherited extends InheritedWidget {
MyInherited({
Key? key,
required this.count,
required this.increment,
required Widget child,
}) : super(key: key, child: child);
final int count;
final void Function() increment;
#override
bool updateShouldNotify(MyInherited oldWidget) {
return count != oldWidget.count;
}
static MyInherited of(BuildContext context) {
final MyInherited? result =
context.dependOnInheritedWidgetOfExactType<MyInherited>();
assert(result != null, 'No MyInherited found in context');
return result!;
}
}
class MyCounter extends StatelessWidget {
const MyCounter({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text('Count: ${MyInherited.of(context).count}');
}
}
class MyButton extends StatelessWidget {
const MyButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
MyInherited.of(context).increment();
},
child: const Text('Increment'),
);
}
}