Flutter Trouble with multiselect checkboxes - data from Firestore - flutter

The following code displays the items listed in my collection (Firestore)
I am attempting to create the ability to check any item(s) and then have those items store into a "Favorites" on the next screen.
Currently, the checkboxes are an all or nothing. Either all items are unchecked or checked once tapped.
class _SelectScreenState extends State<SelectScreen> {
bool _isChecked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Select Exercises')),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('exercises').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot)
{
return ListView(
padding: const EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListItem(context,
data)).toList(),
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Checkbox(
value: _isChecked,
onChanged: (bool value) {
setState(() {
_isChecked = value;
});
},
)
),
),
);
}
}
class Record {
final String name;
final DocumentReference reference;
Record(this.name, this.reference);
Record.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
name = map['name'];
Record.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Record<$name:>";
}

It is because you are making use of a single variable for all the checkboxes.
To fix that you could create a dedicated stateful widget, which would handle the state of each of the checkbox's separately from the rest.
So you could replace your ListTile with something like
Exercise(
title: record.name,
)
and then you could define the Exercise widget as follows
class Exercise extends StatefulWidget {
final String title;
Exercise({this.title});
#override
_ExerciseState createState() => _ExerciseState();
}
class _ExerciseState extends State<Exercise> {
bool selected = false;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(widget.title),
trailing: Checkbox(
value: selected,
onChanged: (bool val) {
setState(() {
selected = val;
});
}),
);
}
}
Here is a complete working example
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#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> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
children: <Widget>[
Exercise(
title: "Exercises 1",
),
Exercise(
title: "Exercises 2",
),
],
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class Exercise extends StatefulWidget {
final String title;
Exercise({this.title});
#override
_ExerciseState createState() => _ExerciseState();
}
class _ExerciseState extends State<Exercise> {
bool selected = false;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(widget.title),
trailing: Checkbox(
value: selected,
onChanged: (bool val) {
setState(() {
selected = val;
});
}),
);
}
}

Because you have a global variable _isChecked, this needs to be created with each listTile.
Try moving the variable
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
bool _isChecked = false; //try moving it here
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Checkbox(
value: _isChecked,
onChanged: (bool value) {
setState(() {
_isChecked = value;
});
},
)
),
),
);
}

Related

Flutter unable to update dynamic TextEditingController text

I'm generating TextFormFields dynamically and assigning unique TextEditingControllers individually. I then only update the text of the TextFormField that's currently in focus
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
setState(() {
_selectedField = textEditingController;
});
},
),
);
n--;
}
return Column(children: listForm);
}
with
InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
/// On tap is able to fetch the correct active TextFormField
debugPrint('Active field: $_selectedField');
_selectedField!.text = 'Hello!'; // doesn't work
setState(() {
/// Setting TextEditingController.text doesn't work
_selectedField!.text = 'Item $index'; // doesn't work
});
}
},
I'm able to successfully fetch the TextEditingController, but unable to update their text. Any idea why TextEditingController.text doesnt work?
Minimal repro
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
setState(() {
_selectedField = textEditingController;
});
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
/// On tap is able to fetch the correct active TextFormField
debugPrint('Active field: $_selectedField');
_selectedField!.text = 'Hello!'; // doesn't work
setState(() {
/// Setting TextEditingController.text doesn't work
_selectedField!.text = 'Item $index'; // doesn't work
});
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}
TextEditingValue() will work:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField = TextEditingController();
List<Widget> listForm = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
while (n > 0) {
TextEditingController _textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: _textEditingController,
onTap: () {
_selectedField = _textEditingController;
debugPrint( 'selected' + _selectedField!.value.text );
debugPrint('main' + _textEditingController.toString());
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
_selectedField!.value =
TextEditingValue(text: 'Item $index'); // doesn't work
debugPrint(_selectedField?.value.text);
debugPrint(_selectedField.hashCode.toString());
debugPrint('Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}

Flutter Switch will not work inside AlertBox

I am having a problem where when I try to use a switch widget it will not work properly inside of an alert box as in it does not switch over to the second state it just bounces whenever I try to flick it. I am wondering if this is because there is a problem with the switch itself or how I displayed it in the box? Thanks!
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(),
home: SwitchDemo(),
);
}
}
class SwitchDemo extends StatefulWidget {
const SwitchDemo({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new _TabsPageState();
}
class _TabsPageState extends State<SwitchDemo> {
bool isInstructionView;
#override
void initState() {
super.initState();
isInstructionView = Global.shared.isInstructionView;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("add data"),
),
body: Container(
child: TextButton(
child: Text('Open Alert Box'),
onPressed: () => {
showDialog(
context: context,
builder: (BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width / 20,
vertical:
MediaQuery.of(context).size.height / 20,
),
child: AlertDialog(
content: Container(
child: Switch(
value: isInstructionView,
onChanged: (bool isOn) {
if (isInstructionView == false) {
} else if (isInstructionView == true) {}
setState(() {
isInstructionView = isOn;
Global.shared.isInstructionView = isOn;
isOn = !isOn;
});
},
activeColor: Colors.blue,
inactiveTrackColor: Colors.grey,
inactiveThumbColor: Colors.grey,
),
),
),
);
})
}),
));
}
}
class Global {
static final shared = Global();
bool isInstructionView = false;
}
Wrap you AlertDialog with StatefulBuilder.
here is full code:
import 'package:flutter/material.dart';
class SwitchDemo extends StatefulWidget {
const SwitchDemo({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => new _TabsPageState();
}
class _TabsPageState extends State<SwitchDemo> {
late bool isInstructionView;
#override
void initState() {
super.initState();
isInstructionView = Global.shared.isInstructionView;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("add data"),
),
body: Container(
child: TextButton(
child: Text('Open Alert Box'),
onPressed: () => {
showDialog(
context: context,
builder: (BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width / 20,
vertical: MediaQuery.of(context).size.height / 20,
),
child: StatefulBuilder(builder: (context, setState) {
return AlertDialog(
content: Container(
child: Switch(
value: isInstructionView,
onChanged: (bool isOn) {
print(isInstructionView);
setState(() {
isInstructionView = !isInstructionView;
});
},
activeColor: Colors.blue,
inactiveTrackColor: Colors.grey,
inactiveThumbColor: Colors.grey,
),
),
);
}),
);
},
)
}),
));
}
}
class Global {
static final shared = Global();
bool isInstructionView = false;
}
Does it answer your question?
ref: https://stackoverflow.com/a/57240941/10157127

Flutter - How to implement a Dropdown List using BloC?

I'd like to add a Dropdown list containing "names" in my application instead of a TextField as I have in the code below.
How can I do this using BloC?
class PersonPage extends StatefulWidget {
PersonPage(this.person);
final Person person;
#override
_PersonPageState createState() => _PersonPageState();
}
class Names{
const Item(this.name);
final String name;
}
class _PersonPageState extends State<PersonPage> {
Item selectedUser;
List<Item> names = <Names>[
const Item('Thomas'),
const Item('John'),
const Item('Mary'),
const Item('Lukas'),
];
TextEditingController _nameController;
final _bloc = PersonBloc();
#override
void initState() {
_bloc.setPerson(widget.person);
_nameController = TextEditingController(text: widget.person.name);
super.initState();
}
#override
void dispose() {
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("MyApp"),
),
body: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: <Widget>[
Container(
child: TextField(
decoration: InputDecoration(labelText: "Name"),
controller: _nameController,
onChanged: _bloc.setName,
),
),
Container(
height: 20,
),
RaisedButton(
child: Text("Save"),
onPressed: () {
if (_bloc.insertOrUpdate()) {
Navigator.pop(context);
}
},
)
],
),
),
),
);
}
Thanks.
I'll give to you an example and you try to apply in your code
class _PersonPageState extends State<PersonPage> {
final _bloc = PersonBloc();
#override
void initState() {
_bloc.setPerson(widget.person);
_nameController = TextEditingController(text: widget.person.name);
super.initState();
}
#override
void dispose() {
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("MyApp"),
),
body: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: <Widget>[
StreamBuilder(
stream: bloc.outName,
builder: (context, snapshot) {
if (!snapshot.hasData)
return Container();
return DropdownButton(
hint: Text("Names"),
value: snapshot.data,
items: bloc.names.map((item) {
return DropdownMenuItem(
value: item,
child: Row(children: <Widget>[Text(item),]),
);
}).toList(),
onChanged: bloc.inName,
);
},
),
Container(
height: 20,
),
RaisedButton(
child: Text("Save"),
onPressed: () {
if (_bloc.insertOrUpdate()) {
Navigator.pop(context);
}
},
)
],
),
),
),
);
}
Change your names list to your BLoC, and create the name BehaviorSubject
PersonBloc{
List<String> names = ['Thomas', 'John', 'Mary', 'Lukas'];
final _name = BehaviorSubject<String>.seeded("");
Stream<String> get outName => _name.stream;
Function(String) get inName => _name.sink.add;
//TODO - Don't forget to dispose this _name
}
Now your _name has the selected name, and to get the name, you just need to do
Person.name = _name.stream.value;
This example can be improved, it's just a draft
this is null-safty sample with model:
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const CountryPage(),
);
}
}
class CountryPage extends StatefulWidget {
const CountryPage({Key? key}) : super(key: key);
#override
State<CountryPage> createState() => _CountryPageState();
}
class _CountryPageState extends State<CountryPage> {
final _bloc = CountryBloc();
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("MyApp"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
border: Border.all(width: 1.2, color: Colors.grey)),
child: CustomDropDown(bloc: _bloc),
),
),
);
}
}
class CustomDropDown extends StatelessWidget {
const CustomDropDown({
Key? key,
required CountryBloc bloc,
}) : _bloc = bloc,
super(key: key);
final CountryBloc _bloc;
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _bloc.streamReference,
builder: (context, snapshot) {
if (!snapshot.hasData) return Container();
return DropdownButton(
isExpanded: true,
hint: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("${_bloc.getCountry.name}"),
),
underline: const SizedBox(),
icon: const Icon(Icons.keyboard_arrow_down, size: 32.0),
items: _bloc.countries.map((item) {
return DropdownMenuItem(
value: item,
child: Directionality(
textDirection: Directionality.of(context).index == 0
? TextDirection.rtl
: TextDirection.ltr,
child: Container(
margin: const EdgeInsets.only(bottom: 4.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
border: Border.all(width: 1.2, color: Colors.grey)),
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Text("${item.name}"),
)),
),
),
);
}).toList(),
onChanged: (value) {
_bloc.selectCountry(value as ObjectModel);
},
);
},
);
}
}
class CountryBloc {
List<ObjectModel> countries = [
ObjectModel(id: "0", name: 'USA'),
ObjectModel(id: "1", name: 'CHINA'),
ObjectModel(id: "2", name: 'INDIA'),
ObjectModel(id: "3", name: 'BRAZIL'),
];
final _country = BehaviorSubject<ObjectModel>.seeded(
ObjectModel(id: "-1", name: "Select Country Name.."));
Stream<ObjectModel> get streamReference => _country.stream;
Function(ObjectModel) get selectCountry => _country.sink.add;
ObjectModel get getCountry => _country.value;
void setCountry(ObjectModel country) {
_country.value = country;
}
}
class ObjectModel {
String? id;
String? name;
ObjectModel({this.id, this.name});
ObjectModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['name'] = name;
return data;
}
}
add this dependencies to pubspec.yaml file:
flutter_bloc: ^7.1.0
rxdart: ^0.27.5

Flutter auto-suggest TextField

I am developing a Flutter app, which contains a TextFormField/TextField inside Form. One of the TextFields will be auto suggest/complete.
What is the proper widget to show a list of data when the TextField input changes?
Flutter has an upcoming AutoComplete feature and you can track its progress in this thread. Aside from using the mentioned packages in the comments, you can also try using RawAutocomplete for your Text[Form]Field while waiting for the AutoComplete feature to be released. You need to be on master channel to try this though.
Here's a sample following this demo.
import 'package:flutter/material.dart';
import 'package:flutter/services.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> {
var _textFieldHints = List<String>();
var _textFieldController;
#override
void dispose() {
super.dispose();
_textFieldController.dispose();
}
_addAutoFillText() {
if (_textFieldController.text.isEmpty) {
// Don't add input text if TextFormField is empty
debugPrint('_textFieldController.text is empty');
} else {
debugPrint('${_textFieldController.text} added');
setState(() {
// Add TextFormField String to List
_textFieldHints.add(_textFieldController.text);
// Clear TextFormField after adding
_textFieldController.clear();
});
debugPrint('_textFieldHints contains:');
_textFieldHints.sort();
_textFieldHints.forEach((text) {
debugPrint('$text');
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
padding: EdgeInsets.all(16.0),
child: RawAutocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == null ||
textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return _textFieldHints.where((String option) {
return option
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
},
onSelected: (String selection) {
debugPrint('$selection selected');
},
fieldViewBuilder: (BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted) {
_textFieldController = textEditingController;
return TextFormField(
controller: _textFieldController,
focusNode: focusNode,
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
);
},
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<String> onSelected,
Iterable<String> options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: Container(
height: 200.0,
child: ListView.builder(
padding: EdgeInsets.all(8.0),
itemCount: options.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index >= options.length) {
return TextButton(
child: const Text('clear'),
onPressed: () {
_textFieldController.clear();
},
);
}
final String option = options.elementAt(index);
return GestureDetector(
onTap: () {
onSelected(option);
},
child: ListTile(
title: Text(option),
),
);
},
),
),
),
);
},
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _addAutoFillText(),
child: Icon(Icons.add),
),
);
}
}

Flutter Inherited Widget - Missing some listener

I'm trying to get the following to work. Changes to the app model state are not picked up via the InheritedWidget 'AppStateProvider'. I've manage to get this working with sinks/streams but was hoping to established a simpler structure.
This is just a test application to switch between various app modes.
What's missing?
import 'package:flutter/material.dart';
void main() {
runApp(AppStateProvider(
child: RootPage(),
appState: new AppState(),
));
}
enum AppMode { introduction, login, home }
class AppState {
AppMode appMode;
AppState({
this.appMode = AppMode.introduction,
});
}
class AppStateProvider extends InheritedWidget {
final AppState appState;
AppStateProvider({Key key, Widget child, this.appState})
: super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static AppStateProvider of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(AppStateProvider)
as AppStateProvider);
}
}
class RootPage extends StatelessWidget {
AppMode _mode;
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Inherited Widget Test',
theme: new ThemeData(
primarySwatch: Colors.blueGrey,
),
home: _body(context),
);
}
Widget _body(BuildContext context) {
final provider = AppStateProvider.of(context); //Registers as a listener
final state = provider.appState;
_mode = state.appMode;
return new Stack(
children: <Widget>[
new Offstage(
offstage: _mode != AppMode.introduction,
child: new MaterialApp(
home: ColorsListPage(
color: Colors.red,
targetAppMode: AppMode.login,
title: "Intro",
),
),
),
new Offstage(
offstage: _mode != AppMode.login,
child: new MaterialApp(
home: ColorsListPage(
color: Colors.blue,
targetAppMode: AppMode.home,
title: "Login",
),
),
),
new Offstage(
offstage: _mode != AppMode.home,
child: new MaterialApp(
home: ColorsListPage(
color: Colors.green,
targetAppMode: AppMode.introduction,
title: "Home",
),
),
),
],
);
}
}
class ColorDetailPage extends StatefulWidget {
final String title;
final MaterialColor color;
final int materialIndex;
final AppMode targetAppMode;
ColorDetailPage(
{this.color, this.title, this.targetAppMode, this.materialIndex: 500});
#override
_ColorDetailPageState createState() => new _ColorDetailPageState();
}
class _ColorDetailPageState extends State<ColorDetailPage> {
#override
Widget build(BuildContext context) {
final provider = AppStateProvider.of(context);
return Scaffold(
appBar: AppBar(
backgroundColor: widget.color,
title: Text(
'$widget.title[$widget.materialIndex]',
),
),
body: Container(
color: widget.color[widget.materialIndex],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
provider.appState.appMode = widget.targetAppMode;
});
},
heroTag: null,
),
);
}
}
class ColorsListPage extends StatefulWidget {
final MaterialColor color;
final String title;
final ValueChanged<int> onPush;
final AppMode targetAppMode;
final List<int> materialIndices = [
100,
200,
300,
400,
500,
600,
700,
800,
900,
];
ColorsListPage({this.color, this.targetAppMode, this.title, this.onPush});
#override
_ColorsListPageState createState() => new _ColorsListPageState();
}
class _ColorsListPageState extends State<ColorsListPage> {
#override
Widget build(BuildContext context) {
final provider = AppStateProvider.of(context);
return new Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: widget.color,
),
body: Container(
color: Colors.white,
child: _buildList(context),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
provider.appState.appMode = widget.targetAppMode;
});
},
heroTag: null,
));
}
Widget _buildList(BuildContext context) {
return ListView.builder(
itemCount: widget.materialIndices.length,
itemBuilder: (BuildContext content, int index) {
int materialIndex = widget.materialIndices[index];
return Container(
color: widget.color[materialIndex],
child: ListTile(
title: Text(
"$materialIndex",
style: TextStyle(fontSize: 24.0),
),
trailing: Icon(Icons.chevron_right),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ColorDetailPage(
color: widget.color,
title: widget.title,
targetAppMode: widget.targetAppMode,
materialIndex: materialIndex,
)),
);
}
//onTap: () => onPush(materialIndex),
));
},
);
}
}
You need to wrap your InheritedWidget inside a StatefulWidget
class _AppStateProvider extends InheritedWidget {
final AppStateProviderState data;
_AppStateProvider({Key key, #required Widget child, #required this.data})
: super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
}
class AppStateProvider extends StatefulWidget {
final Widget child;
final AppState appState;
AppStateProvider({
#required this.child,
#required this.appState,
});
static AppStateProviderState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(_AppStateProvider)
as _AppStateProvider)
.data;
}
#override
AppStateProviderState createState() => AppStateProviderState(
appState,
);
}
class AppStateProviderState extends State<AppStateProvider> {
AppState appState;
AppStateProviderState(this.appState);
void updateAppMode(AppMode appMode) {
setState(() {
appState.appMode = appMode;
});
}
#override
Widget build(BuildContext context) {
return _AppStateProvider(
data: this,
child: widget.child,
);
}
}
for more information
pay attention to this method:
void updateAppMode(AppMode appMode) {
setState(() {
appState.appMode = appMode;
});
}
you can use it like this:
floatingActionButton: FloatingActionButton(
onPressed: () {
provider.updateAppMode(widget.targetAppMode);
},
heroTag: null,
),