I've the below custom widget that make a Switch and reads its status (true/false)
Then I add this one to my main app widget (parent), how can I make the parent knows the value of the switch!
import 'package:flutter/material.dart';
class Switchy extends StatefulWidget{
Switchy({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new _SwitchyState();
}
class _SwitchyState extends State<Switchy> {
var myvalue = true;
void onchange(bool value) {
setState(() {
this.myvalue = value; // I need the parent to receive this one!
print('value is: $value');
});
}
#override
Widget build(BuildContext context) {
return
new Card(
child: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text("Enable/Disable the app in the background",
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,),
new Switch(value: myvalue, onChanged: (bool value) => onchange(value)),
],
),
),
);
}
}
In the main.dart (parent) file, I started with this:
import 'widgets.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.deepOrange,
),
home: new MyHomePage(title: 'My App settup'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget e = new Switchy();
//...
}
The first possibility is to pass a callback into your child, and the second is to use the of pattern for your stateful widget. See below.
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyStatefulWidgetState();
// note: updated as context.ancestorStateOfType is now deprecated
static MyStatefulWidgetState of(BuildContext context) =>
context.findAncestorStateOfType<MyStatefulWidgetState>();
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
String _string = "Not set yet";
set string(String value) => setState(() => _string = value);
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Text(_string),
new MyChildClass(callback: (val) => setState(() => _string = val))
],
);
}
}
typedef void StringCallback(String val);
class MyChildClass extends StatelessWidget {
final StringCallback callback;
MyChildClass({this.callback});
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new FlatButton(
onPressed: () {
callback("String from method 1");
},
child: new Text("Method 1"),
),
new FlatButton(
onPressed: () {
MyStatefulWidget.of(context).string = "String from method 2";
},
child: new Text("Method 2"),
)
],
);
}
}
void main() => runApp(
new MaterialApp(
builder: (context, child) => new SafeArea(child: new Material(color: Colors.white, child: child)),
home: new MyStatefulWidget(),
),
);
There is also the alternative of using an InheritedWidget instead of a StatefulWidget; this is particularly useful if you want your child widgets to rebuild if the parent widget's data changes and the parent isn't a direct parent. See the inherited widget documentation
In 2020, the function in the highest voted answer is marked deprecated. So here is the modified solution based on that answer.
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyStatefulWidgetState();
// --> NOTE this! <--
static MyStatefulWidgetState of(BuildContext context) =>
context.findAncestorStateOfType<MyStatefulWidgetState>();
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
String _string = "Not set yet";
set string(String value) => setState(() => _string = value);
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Text(_string),
new MyChildClass(callback: (val) => setState(() => _string = val))
],
);
}
}
typedef void StringCallback(String val);
class MyChildClass extends StatelessWidget {
final StringCallback callback;
MyChildClass({this.callback});
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new FlatButton(
onPressed: () {
callback("String from method 1");
},
child: new Text("Method 1"),
),
new FlatButton(
onPressed: () {
MyStatefulWidget.of(context).string = "String from method 2";
},
child: new Text("Method 2"),
)
],
);
}
}
void main() => runApp(
new MaterialApp(
builder: (context, child) => new SafeArea(child: new Material(color: Colors.white, child: child)),
home: new MyStatefulWidget(),
),
);
However, the methods mentioned in the answers of this question has a drawback. From doc:
In general, though, consider using a callback that triggers a stateful change in the ancestor rather than using the imperative style implied by this method. This will usually lead to more maintainable and reusable code since it decouples widgets from each other.
Calling this method is relatively expensive (O(N) in the depth of the tree). Only call this method if the distance from this widget to the desired ancestor is known to be small and bounded.
I think notifications are quite a civilized solution and they allow for a very clean communication without variable juggling and they bubble up if you need them to:
Define a notification:
class SwitchChanged extends Notification {
final bool val
SwitchChanged(this.val);
}
Raise notification in your child's event handler:
onPressed: () {
SwitchChanged(true).dispatch(context);
}
Finally, wrap your parent with notification listener:
NotificationListener<SwitchChanged>(
child: YourParent(...),
onNotification: (n) {
setState(() {
// Trigger action on parent via setState or do whatever you like.
});
return true;
}
)
You can pass a callback defined in the parent widget to the child widget and as soon as an action is performed in the child widget, the callback gets invoked.
class ParentWidget extends StatelessWidget {
// This gets called when the button is pressed in the ChildWidget.
void _onData(String data) {
print(data); // Hello World
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ChildWidget(onData: _onData),
);
}
}
class ChildWidget extends StatelessWidget {
final void Function(String) onData;
ChildWidget({
super.key,
required this.onData,
});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// Pass 'Hello World' to parent widget.
onData('Hello World');
},
child: Text('Button'),
);
}
}
Use InheritedWidget - https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html
This lets you access data of the parent in all the children
I found a way to do this which was fairly simple, I'm a flutter noob so maybe it isn't the best way. If someone sees something wrong with it, feel free to leave a comment. Basically state is set in parent widget, child widget updates the state of the parent, and any child widgets of the parents which use the state values are redrawn when the value is updated.
Parent widget:
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String _stringToChange = ""; // the string you want to update in child
// function to update state with changes to term
_updateStringToChange(String stringToChange) {
setState(() {
_stringToChange = stringToChange;
// Other logic you might want to do as string value changes
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'title',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
appBar: AppBar(
title: const Center(
child: Text("app bar title"),
),
),
body: Column(children: <Widget>[
ChildWhichMakesChanges(
updateStringToChange: _updateStringToChange,
),
Expanded(
child: Container(
padding: const EdgeInsets.fromLTRB(20, 10, 0, 10),
child: ChildWhichUsesChanges(
stringToChange: _stringToChange,
)))
]),
));
}
}
ChildWhichMakesChanges (this example uses a text box to enter input):
class ChildWhichMakesChanges extends StatefulWidget {
final ValueChanged<String> updateStringToChange;
const ChildWhichMakesChanges({Key? key, required this.updateStringToChange}) : super(key: key);
#override
_TextInputState createState() => _TextInputState();
}
class _TextInputState extends State<ChildWhichMakesChanges> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 25),
child: TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter text',
),
onChanged: (String stringToChange) {
widget.updateStringToChange(stringToChange);
})),
]);
}
}
Using the changed string value in ChildWhichUsesChanges:
class ChildWhichUsesChanges extends StatelessWidget {
final String stringToChange;
const ChildWhichUsesChanges(
{Key? key,
required this.stringToChange})
: super(key: key);
#override
Widget build(BuildContext context) {
return Text(stringToChange)
}
}
2022 Solution:
A simple one.
Make it work like interface.
You can make your own custom CallBack Function just by defining typedef. It will just work as an interface between child to parent widget.
This is an IMP function:
typedef void GetColor(Color? color, String? string);
Following is Parent Widget:
import 'package:flutter/material.dart';
typedef void GetColor(Color? color, String? string);
class NavigationDialog extends StatefulWidget {
const NavigationDialog({Key? key}) : super(key: key);
#override
_NavigationDialogState createState() => _NavigationDialogState();
}
class _NavigationDialogState extends State<NavigationDialog> {
Color? color = Colors.blue[700];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: color,
appBar: AppBar(
title: const Text('Navigation Dialog Screen'),
),
body: Center(
child: ElevatedButton(
child: const Text('Change Color'),
onPressed: () {
_showColorDialog(context, (value, string) {
setState(() {
color = value;
print(string);
});
});
}),
),
);
}
And Following is a child Widget Code:
_showColorDialog(BuildContext context, Function getColor) async {
color = null;
await showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return AlertDialog(
title: const Text('Very important question'),
content: const Text('Please choose a color'),
actions: <Widget>[
TextButton(
child: const Text('Red'),
onPressed: () {
color = Colors.red[700];
getColor(color, 'Red');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
TextButton(
child: const Text('Green'),
onPressed: () {
color = Colors.green[700];
getColor(color, 'Green');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
TextButton(
child: const Text('Blue'),
onPressed: () {
color = Colors.blue[700];
getColor(color, 'Blue');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
],
);
},
);
}
}
In this example, We are selecting a color from Child Alert Dialog widget and pass to Parent widget.
Store the value in that child widget in shared preference, then access that shared preference value in the parent widget.
Related
I have an AppBar in main.dart and I want to defined it as primary on it's child, But I want to change the title of AppBar itself when I'm on child's page, how can i do that properly?
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter App",
theme: ThemeData(
primaryColor: Colors.cyan,
brightness: Brightness.dark
),
home: Scaffold(
appBar: AppBar(
title: Text("Main Dart"),
),
body: HomeScreen(),
),
routes: <String, WidgetBuilder>{
'/homeScreen': (buildContext)=>HomeScreen(),
'/second': (buildContext)=>Second()
},
);
}
}
//HomeScreen or Second Widget on different dart file
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
//here I want to change the title of Main Dart to HomeScreen
return Container(
child: Center(
child: FlatButton(
child: new Text("Home screen"),
onPressed: (){
Route route = MaterialPageRoute(builder: (context) => Second());
Navigator.push(context, route);
},
),
),
);
}
}
or I need to put Scaffold(appBar:AppBar(...), ...) in every screen? it is the best approach?
Have a BLoC for app properties in app_properties_bloc.dart
final appBloc = AppPropertiesBloc();
class AppPropertiesBloc{
StreamController<String> _title = StreamController<String>();
Stream<String> get titleStream => _title.stream;
updateTitle(String newTitle){
_title.sink.add(newTitle);
}
dispose() {
_title.close();
}
}
Use stream builder in AppBar like this:
AppBar(
title: StreamBuilder<Object>(
stream: appBloc.titleStream,
initialData: "Main Dart",
builder: (context, snapshot) {
return Text(snapshot.data);
}
),
),
Use this to update title on button's onPressed()
onPressed: () {
appBloc.updateTitle('new title');
},
Just in case you are changing only the title of Scaffold then this will work.
I am creating a DefaultScaffold with the title each screen provides. Here the code will show the MainPage and two other pages which have the same AppBar with changed titles.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(initialRoute: 'home', routes: <String, WidgetBuilder>{
'home': (context) => SOMain(),
'/secondPage': (context) => DefaultScaffold("Second Screen", SOSecond()),
'/thirdPage': (context) => DefaultScaffold("Third Screen", SOThird()),
});
}
}
class DefaultScaffold extends StatelessWidget {
String title;
Widget body;
DefaultScaffold(this.title, this.body);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: body,
);
}
}
class SOMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultScaffold(
"Main Screen",
Center(
child: RaisedButton(
child: Text("Go to second screen"),
onPressed: () {
Navigator.pushNamed(context, '/secondPage');
}),
),
);
}
}
class SOSecond extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("Go the 3rd screen"),
onPressed: () => Navigator.pushNamed(context, "/thirdPage"),
),
);
}
}
class SOThird extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(child: Text("You are on last screen"));
}
}
Note: This is a simple workaround and may not be the best way to do this.
You can accomplish updating the state of the parent from a child by using a callback function.
Parent Class:
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return ParentState();
}
}
class ParentState extends State<Parent> {
String title = "Old Title";
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: DaysFragmentView(onTitleSelect: (String value) {
setTitle(value);
}
),
);
}
void setTitle(String value) {
setState(() {
title = value;
});
}
}
Child Class
typedef TitleCallback = void Function(Title color);
class DaysFragmentView extends StatelessWidget {
const DaysFragmentView({this.onTitleSelect});
final TitleCallback onTitleSelect;
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
RaisedButton(
child: Text('One'),
onPressed: () {
onTitleSelect("TITLE ONE");
},
),
RaisedButton(
child: Text('Two'),
onPressed: () {
onTitleSelect("TITLE TWO");
},
)
],
);
}
}
Reference:
call-method-in-one-stateful-widget-from-another-stateful-widget-flutter
working-with-callback-in-flutter
Using ValueListenableBuilder is an option.
Use an instance variable
String appTitle;
Then set the app bar as in the following block:
appBar: AppBar(
ValueListenableBuilder<String>(
valueListenable: appTitle,
builder: (context, value, child) {
return Text(appTitle.value);
},
),
After that you can simply set appTitle.value in the other class. The title will be changed too because it listens to that value.
appTitle.value = "Home Screen";
Some answer here are too complicated. Here is a full working example using app bar update from child with scafold widget.
You can run the example in dart pad
import 'package:flutter/material.dart';
void main() {
runApp(const MyHomePage(title: 'init title'));
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ValueNotifier<String?> _appBarTitleNotifier = ValueNotifier<String?>(null);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: ValueListenableBuilder<String?>(
builder: (BuildContext context, String? value, Widget? child) {
return Text(value ?? widget.title);
},
valueListenable: _appBarTitleNotifier,
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ChildDemoTitleBar(titleNotifier: _appBarTitleNotifier)
],
),
),
),
);
}
}
class ChildDemoTitleBar extends StatefulWidget {
final ValueNotifier<String?> titleNotifier;
const ChildDemoTitleBar({Key? key, required this.titleNotifier})
: super(key: key);
#override
State<ChildDemoTitleBar> createState() => _ChildDemoTitleBarState();
}
class _ChildDemoTitleBarState extends State<ChildDemoTitleBar> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: InkWell(
onTap: () {
_counter++;
widget.titleNotifier.value = "title updated $_counter";
},
child: const Text("tap to update title")));
}
}
I'm trying to set the text from child widget to parent widget. But the text is not reflecting in parent widget.
Tried to use setState() also but still unable to get expected result.
Following is my code:
void main() => runApp(new TestApp());
class TestApp extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<TestApp>{
String abc = "";
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Column(
children: <Widget>[
new Text("This is text $abc"),
TestApp2(abc)
],
),
),
);
}
}
class TestApp2 extends StatefulWidget {
String abc;
TestApp2(this.abc);
#override
_TestState2 createState() => new _TestState2();
}
class _TestState2 extends State<TestApp2>{
#override
Widget build(BuildContext context) {
return new Container(
width: 150.0,
height: 30.0,
margin: EdgeInsets.only(top: 50.0),
child: new FlatButton(
onPressed: (){
setState(() {
widget.abc = "RANDON TEXT";
});
},
child: new Text("BUTTON"),
color: Colors.red,
),
);
}
}
Am i missing something ?
In your example, a few assumptions were made. I will try to remove one by one.
You pass abc from parent to child and you mutated the child value on press on button. As primitive types are pass by value in dart, change in the value of abc in child will not change the value of parent abc. Refer the below snippet.
void main() {
String abc = "oldValue";
changeIt(abc);
print(abc); // oldValue
}
void changeIt(String abc) {
abc = "newValue";
print(abc); //newValue
}
Let's assume the first one is wrong(for understanding purpose). Then changing the value of abc in child will change the value of abc in parent. But without calling that inside setState of parent, parent will not reflect the change. In your case if you change the code as below, it will change the button text alone on click (as setState of child is called).
new FlatButton(
onPressed: () {
setState(
() {
widget.abc = "RANDON TEXT";
},
);
},
child:
new Text(widget.abc), // setting the text based on abc
color: Colors.red,
),
Instead of using globalState which will be very difficult to maintain/debug as app grows, I would recommend using callbacks. Please refer the below code.
void main() => runApp(new TestApp());
class TestApp extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<TestApp> {
String abc = "bb";
callback(newAbc) {
setState(() {
abc = newAbc;
});
}
#override
Widget build(BuildContext context) {
var column = new Column(
children: <Widget>[
new Text("This is text $abc"),
TestApp2(abc, callback)
],
);
return new MaterialApp(
home: new Scaffold(
body: new Padding(padding: EdgeInsets.all(30.0), child: column),
),
);
}
}
class TestApp2 extends StatefulWidget {
String abc;
Function(String) callback;
TestApp2(this.abc, this.callback);
#override
_TestState2 createState() => new _TestState2();
}
class _TestState2 extends State<TestApp2> {
#override
Widget build(BuildContext context) {
return new Container(
width: 150.0,
height: 30.0,
margin: EdgeInsets.only(top: 50.0),
child: new FlatButton(
onPressed: () {
widget.callback("RANDON TEXT"); //call to parent
},
child: new Text(widget.abc),
color: Colors.red,
),
);
}
}
To write the very precise answer. Just use the call back like the above answer use this.
So you want to call the state of ParentScreen from the another function/widget/class. Just follow this code
import 'package:showErrorMessage.dart';
class ParentScreen extends StatefulWidget {
ParentScreen({Key key}) : super(key: key);
#override
_ParentScreenState createState() => _ParentScreenState();
}
class _ParentScreenState extends State<ParentScreen> {
callback() {
setState(() {});
}
#override
Widget build(BuildContext context) {
String message = "hello";
return Container(
child: showErrorMessage(message, callback);,
);
}
}
And here is the child widget/function/class
import 'package:flutter/material.dart';
showErrorMessage(message, Function callback) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
message,
style: TextStyle(color: Colors.white, fontSize: 16),
),
GestureDetector(
onTap: () {
callback(); // ------ this will change/rebuild the state of its parent class
},
child: Icon(
Icons.refresh,
size: 30,
color: Colors.white,
)),
],
));
}
The point that you are missing is your setState method call. You call the setState of the TestState2.
For fixing that, there are two ways.
First way is to create a GlobalKey(https://docs.flutter.io/flutter/widgets/GlobalKey-class.html) and pass it as a parameter to the child widget.
And the second way is to create a global variable for the parent state and use it in the child state.
I modified the code below with the second approach.
_TestState _globalState = new _TestState();
class TestApp extends StatefulWidget {
#override
_TestState createState() => _globalState;
}
class _TestState extends State<TestApp>{
String abc = "";
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Column(
children: <Widget>[
new Text("This is text $abc"),
TestApp2()
],
),
),
);
}
}
class TestApp2 extends StatefulWidget {
TestApp2();
#override
_TestState2 createState() => new _TestState2();
}
class _TestState2 extends State<TestApp2>{
#override
Widget build(BuildContext context) {
return new Container(
width: 150.0,
height: 30.0,
margin: EdgeInsets.only(top: 50.0),
child: new FlatButton(
onPressed: (){
_globalState.setState((){
_globalState.abc = "Button clicked";
});
},
child: new Text("BUTTON"),
color: Colors.red,
),
);
}
}
For Example This is the First Dropdownbutton
For Example This is the First Dropdown Sorry i dont have enough Reputation to post the images
Where the Tag will be Select A Region
and Another one will be showing which will be the cities where the cities will be
listed down there depends on the region selected above somewhat like that.
Each time you call setState the build method of your widget will be called and the visual tree gets reconstructed where needed. So, in the onChanged handler for your DropdownButton, save the selection in setState and conditionally add the second DropdownButton. Here's a working example (which may be a little rough around the edges :) ):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
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> {
String _selectedRegion;
String _selectedSecond;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Something before'),
DropdownButton<String>(
value: _selectedRegion,
items: ['Arizona', 'California']
.map((region) => DropdownMenuItem<String>(
child: Text(region), value: region))
.toList(),
onChanged: (newValue) {
setState(() {
_selectedRegion = newValue;
});
},
),
_addSecondDropdown(),
Text('Something after'),
],
),
),
);
}
Widget _addSecondDropdown() {
return _selectedRegion != null
? DropdownButton<String>(
value: _selectedSecond,
items: ['First', 'Second']
.map((region) => DropdownMenuItem<String>(
child: Text(region), value: region))
.toList(),
onChanged: (newValue) {
setState(() {
_selectedSecond = newValue;
});
})
: Container(); // Return an empty Container instead.
}
}
Luke Freeman has a great blog post about Managing visibility in Flutter if you need this in a more extensive/reusable way.
I'm trying to replace the increment flutter app code, by using Streams from Dart API without using scoped_model or rxdart.
So I read this and watched this, but could not get it work for me, my codes are:
StreamProvider.dart:
import 'package:flutter/widgets.dart';
import 'businessLogic.dart';
import 'dart:async';
class Something {
final _additionalContrllerr = StreamController<int>();
Sink<int> get addition => _additionalContrllerr.sink;
Stream<int> get itemCount => _additionalContrllerr.stream;
}
class StreemProvider extends InheritedWidget {
final Something myBloc; // Business Logic Component
StreemProvider({
Key key,
#required this.myBloc,
Widget child,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static Something of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(StreemProvider) as StreemProvider)
.myBloc;
}
main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_app/StreemProvider.dart';
void main() => runApp(MyApp(
textInput: Text("Provided By the Main"),
));
class MyApp extends StatefulWidget {
final Widget textInput;
MyApp({this.textInput});
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
bool checkBoxValue = false;
int _counter = 0;
#override
Widget build(BuildContext ctxt) {
var x = Something(); //// Not sure if have to use this!
return StreemProvider(
myBloc: x, //// Not sure about this!!
child: MaterialApp(
home: SafeArea(
child: Scaffold(
body: new Center(
child: new Column(
children: <Widget>[
widget.textInput,
Text("clickec $_counter times"),
Text("clickec ${x.itemCount.listen((int i) => i)} times"),
/// How to get the value of i??!
Checkbox(
value: checkBoxValue,
onChanged: (bool newValue){
setState(() {
checkBoxValue = newValue;
});
}
)
],
)),
floatingActionButton: Incrementer(_increment),
// floatingActionButton: Incrementer(x),
),
),
),
);
}
_increment() {
setState(() {
_counter += 1;
});
}
}
class Incrementer extends StatefulWidget {
final Function increment;
Incrementer(this.increment);
#override
State<StatefulWidget> createState() {
return IncrementerState();
}
}
class IncrementerState extends State<Incrementer>{
#override
Widget build(BuildContext ctxt) {
final myBloc = StreemProvider.of(context);
return new FloatingActionButton(
//onPressed: widget.increment,
// How ot get the latest value!!
onPressed: () async {
var y = await myBloc.itemCount.last;
if (y.isNaN) y = 0;
myBloc.addition.add(y+1);
},
child: new Icon(Icons.add),
);
}
}
don't know the restrictions on rx_dart, but I can only try to answer by you using it. lol
your bloc doesnt define wht to listen in your input stream, this is how I could get it to work
counter_bloc.dart
import 'package:rxdart/rxdart.dart';
import 'dart:async';
class CounterBloc {
int _count = 0;
ReplaySubject<int> _increment = ReplaySubject<int>();
Sink<int> get increment => _increment;
BehaviorSubject<int> _countStream = BehaviorSubject<int>(seedValue: 0);
Stream<int> get count => _countStream.stream;
CounterBloc() {
_increment.listen((increment) {
_count += increment;
_countStream.add(_count);
});
}
}
In the constructor the listen method is set for that stream. for each increment sent, it'll increment the counter and send the current count to another stream.
In main.dart, removed the _counter property since that's now being handled by the BLOC. and to display I used a stream builder.
also added a second fab, with a +2 increment to test the logic.
hope this helps you model your bloc class. :)
a good bloc reference: https://www.youtube.com/watch?v=PLHln7wHgPE
main.dart
import 'counter_bloc.dart';
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,
),
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> {
CounterBloc bloc = CounterBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder<int>(
stream: bloc.count,
initialData: 0,
builder: (BuildContext c, AsyncSnapshot<int> data) {
return Text(
'${data.data}',
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () {
bloc.increment.add(2);
},
tooltip: 'Increment 2',
child: Text("+2"),
),
FloatingActionButton(
onPressed: () {
bloc.increment.add(1);
},
tooltip: 'Increment 1',
child: Text("+1"),
),
],
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Thanks a lot to vbandrade his answer helped me figuring t out. the solution worked with me is:
I need to have 2 StreamController if I need to listen to a sink in my bloc Business Logic Component, then process and stream the output to other elements.
The counter_bloc.dart is:
import 'dart:async';
class CounterBloc {
int _count = 0;
// The controller to stream the final output to the required StreamBuilder
final _counter = StreamController.broadcast<int>();
Stream<int> get counter => _counter.stream;
// The controller to receive the input form the app elements
final _query = StreamController<int>();
Sink<int> get query => _query.sink;
Stream<int> get result => _query.stream;
// The business logic
CounterBloc() {
result.listen((increment) { // Listen for incoming input
_count += increment; // Process the required data
_counter.add(_count); // Stream the required output
});
}
void dispose(){
_query.close();
_counter.close();
}
}
And the main.dart is:
import 'counter_bloc.dart';
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,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
State<StatefulWidget> createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
var bloc = CounterBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder<int>( // Listen to the final output sent from the Bloc
stream: bloc.counter,
initialData: 0,
builder: (BuildContext c, AsyncSnapshot<int> data) {
return Text(
'${data.data}',
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () {
bloc.query.add(2); // Send input to the Bloc
},
tooltip: 'Increment 2',
child: Text("+2"),
),
FloatingActionButton(
onPressed: () {
bloc.query.add(1); // Send input to the Bloc
},
tooltip: 'Increment 1',
child: Text("+1"),
),
],
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
A simple implementation
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
int _counter = 0;
final StreamController<int> _streamController =
StreamController<int>.broadcast();
Stream<int> get _stream => _streamController.stream;
void incrementCounter() {
_counter++;
_streamController.add(_counter);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter demo'),
),
body: Center(
child: StreamBuilder<int>(
stream: _stream,
builder: (ctxt, snapshot) {
if (snapshot.hasData) {
return Text(
'You have pushed this button ${snapshot.data} times');
}
return Text('You have pushed this button ${0} times');
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
I'm building a Flutter app and I'm trying to wrap my head around Navigation and State. I built a very simple app below that has two pages that both have the increment button on them. They both share a Scaffold definition so that there is a consistent navigation drawer on both pages.
Basically the functionality I want is that FirstPage and SecondPage are singletons. So if you increment the counter on FirstPage a few times, go to the SecondPage and then return to the FirstPage through the drawer (and not the back button), the FirstPage should still have its counter incremented.
Right now if you do this it appears to create a new instance of FirstPage due to the Navigation.push(). Additionally if you're on the FirstPage and use the drawer to click "First Page" again for some reason, you should not lose state.
I saw a Flutter dev on here mention the term "non-linear navigation" and that made me think something like this is possible. Thanks for your help.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: "Navigation", home: FirstPage());
}
}
class MyScaffold extends Scaffold {
final BuildContext context;
final Text title;
final Widget body;
MyScaffold({
#required this.context,
#required this.title,
#required this.body,
}) : assert(context != null),
assert(title != null),
assert(body != null),
super(
appBar: AppBar(title: title),
drawer: Drawer(
child: ListView(padding: EdgeInsets.zero, children: <Widget>[
SizedBox(height: 100.0),
ListTile(
title: Text("First Page"),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => FirstPage()))),
ListTile(
title: Text("Second Page"),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => SecondPage()))),
])),
body: body);
}
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
int _counter = 0;
void _increment() => setState(() => _counter++);
#override
Widget build(BuildContext context) {
return MyScaffold(
context: context,
title: Text("First Page"),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('$_counter'),
RaisedButton(onPressed: _increment, child: Icon(Icons.add))
])));
}
}
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
int _counter = 0;
void _increment() => setState(() => _counter++);
#override
Widget build(BuildContext context) {
return MyScaffold(
context: context,
title: Text("Second Page"),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('$_counter'),
RaisedButton(onPressed: _increment, child: Icon(Icons.add))
])));
}
}
Rather than extending Scaffold, I would recommend making a StatelessWidget that builds a scaffold and takes a child widget as a parameter. By doing it the way you're doing it, you don't get a context or anything, and encapsulation is generally recommended over inheritance in Flutter.
In addition to doing that you can separate the logic for forward/back. You can do a push(secondPage) and then pop back to the first page, rather than pushing a new page each time. The navigation in flutter is a stack; each time you push you're adding to the top of it and each time you pop you're removing the top element.
If you want SecondPage to retain its counter, that's going to be a little more difficult. You have a couple of options here - the first would be to pass in an initial value when you build it. It would then increment that value internally, and then when you call Navigator.pop(context, [value]) you would pass that value back. It would be saved into the first screen's state, and then when you push the page again you'd pass the new initial value.
The second option would be to retain the counter's value at a level higher than the second page (using a StatefulWidget and possibly InheritedWidget) - i.e. in whichever widget holds your navigator/materialapp. This is probably a little overkill though.
EDIT: in response to comment from OP, it has been made clear that there are actually several pages and more complex information than just a counter.
There are various ways to go about handling this; one would be to implement the app using Redux as strategy; when used with flutter_persist it can make a quite powerful tool for persistent state and I believe there's also a plugin for integrating with firestore for cloud backup (not sure about that).
I'm not a huge fan of redux myself though; it adds quite a lot of overhead which seems to me to go against flutter's simplicity (although I could see how for a large app or team it could create consistency).
A simpler option is as described before the edit; use InheritedWidget to contain the state at a higher level. I've made a quick example below:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return CounterInfo(
child: new MaterialApp(
routes: {
"/": (context) => new FirstPage(),
"/second": (context) => new SecondPage(),
},
),
);
}
}
class _InheritedCounterInfo extends InheritedWidget {
final CounterInfoState data;
_InheritedCounterInfo({
Key key,
#required this.data,
#required Widget child,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(_InheritedCounterInfo old) => true;
}
class CounterInfo extends StatefulWidget {
final Widget child;
const CounterInfo({Key key, this.child}) : super(key: key);
static CounterInfoState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(_InheritedCounterInfo) as _InheritedCounterInfo).data;
}
#override
State<StatefulWidget> createState() => new CounterInfoState();
}
class CounterInfoState extends State<CounterInfo> {
int firstCounter = 0;
int secondCounter = 0;
void incrementFirst() {
setState(() {
firstCounter++;
});
}
void incrementSecond() {
setState(() {
secondCounter++;
});
}
#override
Widget build(BuildContext context) => new _InheritedCounterInfo(data: this, child: widget.child);
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final counterInfo = CounterInfo.of(context);
return new MyScaffold(
title: "First page",
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("${counterInfo.firstCounter}"),
RaisedButton(onPressed: counterInfo.incrementFirst, child: Icon(Icons.add)),
],
),
),
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final counterInfo = CounterInfo.of(context);
return new MyScaffold(
title: "Second page",
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("${counterInfo.secondCounter}"),
RaisedButton(onPressed: counterInfo.incrementSecond, child: Icon(Icons.add)),
],
),
),
);
}
}
class MyScaffold extends StatelessWidget {
final String title;
final Widget child;
const MyScaffold({Key key, this.title, this.child}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(title: new Text(title)),
drawer: Drawer(
child: ListView(padding: EdgeInsets.zero, children: <Widget>[
SizedBox(height: 100.0),
ListTile(
title: Text("First Page"),
onTap: () => Navigator.of(context).pushReplacementNamed("/"),
),
ListTile(title: Text("Second Page"), onTap: () => Navigator.of(context).pushReplacementNamed("/second")),
])),
body: child,
);
}
}
Whether you go this way or look into using Redux, this is a useful website with various flutter architecture models including using inherited widget.