I'm new in Flutter and Dart and found a new "async_redux" package in https://pub.dev/packages/async_redux to develop my project easier way than traditional "redux" package. In readme document there is a short description about implement Route Navigation but I always receive:
"type 'NavigateAction' is not a subtype of type 'ReduxAction' of 'action'"
when i use -dispatch(NavigateAction.pushNamed("MyRoute"))- in "onChangePage".
Here the structure code:
Store<AppState> store;
final navigatorKey = GlobalKey<NavigatorState>();
void main() async{
NavigateAction.setNavigatorKey(navigatorKey);
var state = AppState.initialState();
store = Store<AppState>(initialState: state);
runApp(MyApp());
}
final routes={
'/': (BuildContext context) => First(),
"/myRoute": (BuildContext context) => Two(),
};
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
routes: routes,
navigatorKey: navigatorKey,
),
);
}
}
class AppState {
AppState(...);
AppState copy(...) =>
AppState(
...
);
static AppState initialState() => AppState(
...
);
#override
bool operator ==(Object other) => ...
#override
int get hashCode => ...;
}
class First extends StatelessWidget {
#override
Widget build(BuildContext context) => MyHomePageConnector();
}
class MyHomePageConnector extends StatelessWidget {
MyHomePageConnector({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, ViewModel>(
model: ViewModel(),
builder: (BuildContext context, ViewModel vm) => MyHomePage(
onChangePage: vm.onChangePage
),
);
}
}
class ViewModel extends BaseModel<AppState> {
ViewModel()
VoidCallback onChangePage;
ViewModel.build({
#required this.onChangePage,
}) : super(equals: []);
#override
ViewModel fromStore() => ViewModel.build(
onChangePage: () => dispatch (NavigateAction.pushNamed ("/myRoute"))
);
}
class MyHomePage extends StatefulWidget {
final VoidCallback onChangePage;
MyHomePage({
Key key,
this.onChangePage
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Icon(Icons.add_circle),
onPressed: widget.onChangePage
),
);
}
}
How and where implement "dispatch(NavigateAction.pushNamed ("/myRoute"))"?
Try this:
dispatch(NavigateAction<AppState>.pushNamed("/myRoute"))"
Update:
With recent async_redux: ^1.2.0 you don't need the <AppState> anymore, and can dispatch it like this:
dispatch(NavigateAction.pushNamed("/myRoute"))"
Related
Is it possible to update a variable outside a widget while calling it ?
Here's an example :
class Widget1 extends StatefulWidget {
#override
State<Widget1> createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
String example = 'A';
#override
Widget build(BuildContext context) {
return Column(children: [
Text(example),
Widget2(example: example)
],);
}
}
class Widget2 extends StatefulWidget {
final String example;
Widget2({required this.example});
#override
State<Widget2> createState() => _Widget2State();
}
class _Widget2State extends State<Widget2> {
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => setState(() {
widget.example = 'B'
}),
child: Text('update !'),
);
}
}
The idea here is that I want to update example using a button outside the widget.
This code is not working : example = 'A' no matter if I click the button or not, but I don't understand why since I'm calling the same variable.
Is there a simple solution to achieve this ? (by simple, I mean without the need of Provider or else.)
You can use callback method. Parent widget needed to updated, so setState is needed to be trigger on Widget1.
class Widget1 extends StatefulWidget {
#override
State<Widget1> createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
String example = 'A';
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(example),
Widget2(
example: example,
callback: (p0) {
setState(() {
example = p0;
});
},
),
],
);
}
}
class Widget2 extends StatefulWidget {
final String example;
final Function(String) callback;
Widget2({
required this.example,
required this.callback,
});
#override
State<Widget2> createState() => _Widget2State();
}
class _Widget2State extends State<Widget2> {
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
widget.callback("new data");
},
child: Text('update !'),
);
}
}
You can use Notifiers, here is an example:
import 'package:flutter/material.dart';
class ExampleNotifier with ChangeNotifier {
String example = 'A';
ExampleNotifier();
setText(string x) {
example = x;
notifyListeners();
}
}
and then use it like:
class Widget1 extends StatefulWidget {
#override
State<Widget1> createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
#override
Widget build(BuildContext context) {
var exampleNotifier = Provider.of<ExampleNotifier>(context);
return Column(
children: [
Text(exampleNotifier.example),
Widget2(),
],
);
}
}
class Widget2 extends StatefulWidget {
#override
State<Widget2> createState() => _Widget2State();
}
class _Widget2State extends State<Widget2> {
#override
Widget build(BuildContext context) {
var exampleNotifier = Provider.of<ExampleNotifier>(context, listen: false);
return ElevatedButton(
onPressed: () {
exampleNotifier.setText('B');
},
child: Text('update !'),
);
}
}
If you want to use setState, you can use this
class Widget1 extends StatefulWidget {
#override
State<Widget1> createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
String example = 'A';
void changeExample() {
setState(() => example = "B");
}
#override
Widget build(BuildContext context) {
return Column(
children: [Text(example), Widget2(changeExample: changeExample)],
);
}
}
class Widget2 extends StatelessWidget {
final void Function() changeExample;
Widget2({required this.changeExample});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: changeExample,
child: Text('update !'),
);
}
}
In a complex scenario I need to update the text of some TextFormFields when a notifyListeners() is sent by a Model extending ChangeNotifier.
The problem is that to change the text of a TextFormField you have to use the setter TextFormField.text which implies a rebuild, and so you can't use it into the build method. But to access the Provider of the model you need the context which is inside the build method.
MWE (obviously the button is in another Widget in the real project, and there are more TextFormFields)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyModel extends ChangeNotifier {
void updateCounter() {
++_counter;
notifyListeners();
}
MyModel() {
_counter = 1;
}
int _counter;
String get counter => _counter.toString();
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyModel(),
child: MaterialApp(
title: 'Test',
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _text1Ctl = TextEditingController();
var _text2Ctl = TextEditingController();
#override
void initState() {
super.initState();
final model = MyModel();
model.addListener(() {
_text1Ctl.text = model.counter;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
FlatButton(
onPressed: () {
Provider.of<MyModel>(context, listen: false).updateCounter();
},
child: Text('Press me'),
),
// 1st attempt
// Doesn't work because the listener isn't applied to the instance of the model provided by the provider.
TextFormField(controller: _text1Ctl),
// 2nd attempt
// Works but with `Another exception was thrown: setState() or markNeedsBuild() called during build.` because it changes text via controller (which implies a rebuild) during building.
Consumer<MyModel>(builder: (context, model, child) {
_text2Ctl.text = model.counter;
return TextFormField(controller: _text2Ctl);
})
]));
}
}
Your second example works without any errors when I run it:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyModel extends ChangeNotifier {
void updateCounter() {
++_counter;
notifyListeners();
}
MyModel() {
_counter = 1;
}
int _counter;
String get counter => _counter.toString();
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyModel(),
child: MaterialApp(
title: 'Test',
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _text2Ctl = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
FlatButton(
onPressed: () {
Provider.of<MyModel>(context, listen: false).updateCounter();
},
child: Text('Press me'),
),
// 2nd attempt
// Works but with `Another exception was thrown: setState() or markNeedsBuild() called during build.` because it changes text via controller (which implies a rebuild) during building.
Consumer<MyModel>(builder: (context, model, child) {
_text2Ctl.text = model.counter;
return TextFormField(controller: _text2Ctl);
})
]));
}
}
I would like to get some help.
I don't understand why something is not rebuilding properly.
I have an app.dart which provides all stores and the body of the Scafold is a home page,
class HomePage extends StatelessWidget {
const HomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<AuthStore>(
builder: (__, AuthStore authStore, _) {
debugPrint('--- $authStore');
return SafeArea(
child: authStore.authenticated
? const AuthenticatedScreen()
: const LoginScreen(),
);
},
);
}
}
the login screen does its job, and updates the store correctly, I don't understand why the home screen is not updated when authenticated changes to true
I tried adding an observer with the same result e.g. no rebuild
class HomePageStateless extends StatelessWidget {
const HomePageStateless({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<AuthStore>(builder: (__, AuthStore authStore, _) {
debugPrint('--- $authStore');
return Observer(
builder: (_) => SafeArea(
child: authStore.authenticated
? const AuthenticatedScreen()
: const LoginScreen(),
),
);
});
}
}
what I'm missing here
while this widget works as expected
class HomePage extends StatefulWidget {
const HomePage({
Key key,
}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
AuthStore _authStore;
final List<ReactionDisposer> _disposers = <ReactionDisposer>[];
bool _authenticated = false;
#override
void didChangeDependencies() {
super.didChangeDependencies();
for (ReactionDisposer disposer in _disposers) {
disposer();
}
_disposers.clear();
_authStore ??= Provider.of<AuthStore>(context);
_disposers.add(
autorun(
(_) {
print('home store $_authStore');
if (_authenticated != _authStore.authenticated) {
setState(() {
_authenticated = _authStore.authenticated;
});
}
},
),
);
}
#override
void dispose() {
for (ReactionDisposer disposer in _disposers) {
disposer();
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: _authenticated ? const AuthenticatedScreen() : const LoginScreen(),
);
}
}
And I know this shouldn't be the correct way, but the autorun fire consistently when the store changes
Use what you u want to rebuild in a void method below the state and statefull widget instead of stateless one.
I am building a podcasting type app, so need to call the record, stop, and play functions in many places, I created the methods, but difficulty to call these methods in other places.
main.dart
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String statusText = "";
bool isComplete = false;
void startRecord() //Need to call all of these method in coming stateful widgets
void stopRecord() //
void pauseRecord()//
void resumeRecord()//
void play() //
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => Scaffold(
drawer: Drawer(
elevation: 2.0,
child: ListView(
children: <Widget>[
ListTile(
title: Text('Home'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return MyApp();
},
),
);
},
),
//more code is here
Expanded(
child: GestureDetector(
child: IconButton(
icon: Icon(Icons.mic),
color: Colors.white,
iconSize: 40,
onPressed: () async {
startRecord();
}),
),
),
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
onPressed: () {
startRecord()
// need to call the method here.
}
Pressed: () {
stopRecord()
// need to call the method here.
}
Pressed: () {
play()
// need to call the method here.
}
),
}
Need to call all the methods from a first stateful widget for bottom stateful widgets
also, need to call these methods for other classes when code progress
both stateful widgets are in the main.dart. I could not call the method from the first class for the second stateful widget
This is not a rocket science, just a simple line of code, and you are done.
What you have to do, is to just call the MyHomePage() and let it accept the startRecording() to be used inside the Widget
1. Passing the data from MyApp() to MyHomePage()
#override
Widget build(BuildContext context) {
return MaterialApp(
// here you pass the your function
home: MyHomePage(onPressed: startRecording)
);
}
2. Receiving the data in MyHomePage()
class MyHomePage extends StatefulWidget {
// let it accept a function type onPressed argument
final Function onPressed;
// constructor
MyHomePage({Key key, this.onPressed}): super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
// simply call the onPressed which received your startRecording() from MyApp
onPressed: () => widget.onPressed()
}
You can get the state of a parent widget using the BuildContext of the child widget like so:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
static _MyAppState of(BuildContext context) {
return context.findAncestorStateOfType<_MyAppState>();
}
}
class _MyAppState extends State<MyApp> {
String statusText = "";
bool isComplete = false;
void startRecord() {
print('Hello');
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
MyApp.of(context).startRecord();
return Scaffold(
body: Placeholder(),
);
}
}
Simply define that function outside the class as a stand-alone function like this But if you want to call from inside the class. Heres the code.
inside a different class as a static function:
onPressed: () {
_MyAppState().startRecord(); //call using the class name.
}
Like this inside your onpressed Statement.
Should work.
Or else what you can do is define the function outside the class. Then use it where ever you want. Like this:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
void startRecord(){
.
.
.
} /// Like here outside the class
class _MyAppState extends State<MyApp> {
String statusText = "";
bool isComplete = false;
#override
Widget build(BuildContext context) {
return MaterialApp(.....
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
onPressed: () {
startRecord(); // call Here as follows.
}),
}
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'),
);
}
}