Why couldn't my onPressed callback call my Bloc using Bloc? - flutter

I'm using the Bloc Library by felangel, specifically Flutter_bloc, but using BlocProvider I'm unable to pass the bloc instance to my onpressed method for when a button is pressed
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/bloc.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bloc Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double temp;
String _formText = "";
final _formKey = GlobalKey<FormState>(debugLabel: "City name should be here");
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Hello, title here"),
),
body: BlocProvider<WeatherBloc>(
builder: (BuildContext context) => WeatherBloc(),
child: BlocBuilder<WeatherBloc, WeatherState>(
builder: (BuildContext context, WeatherState state) {
if (state is InitialWeatherState) {
return buildInitialWeather();
}
else if (state is WeatherStateLoading) {
return buildLoading();
} else if (state is WeatherStateLoaded) {
return buildColumn(state.weather);
} else throw Exception("Something went wrong here");
}
),
),
);
}
Widget buildInitialWeather() {
return Center(
child: Column(children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: Form(
key: _formKey,
child: TextFormField(
decoration: InputDecoration(hintText: "Enter your city"),
onSaved: (value) {
setState(() {
_formText = value;
});
},
)),
),
new RaisedButton(
onPressed: _onPressed,
child: Text("Click Me!"),
splashColor: Colors.pink,
)
],),
);
}
Widget buildLoading() {
return Center(
child: CircularProgressIndicator(),
);
}
Column buildColumn(Weather weather) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Geo Locator for:'+weather.cityName,
style: TextStyle(fontSize: 20.0),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
weather.temp.toString(),
style: TextStyle(fontSize: 20.0),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: Form(
key: _formKey,
child: TextFormField(
decoration: InputDecoration(hintText: "Enter your city"),
onSaved: (value) {
setState(() {
_formText = value;
});
},
)),
),
new RaisedButton(
onPressed: _onPressed,
child: Text("Click Me!"),
splashColor: Colors.pink,
)
],
);
}
void _onPressed() {
_formKey.currentState.save();
print(_formText);
// This line fails. I don't know why.
BlocProvider.of<WeatherBloc>(context).dispatch(GetWeather(_formText));
}
#override
void dispose() {
super.dispose();
}
}
Basically whenever I run the code it throws the following exception: BlocProvider.of() called with a context that does not contain a Bloc of type Bloc.

Try to put BlocProvider in top of your project (Parent of MyHomePage or MaterialApp).

Related

How can I pass data from a TextFieldInput to another Page with Provider

I want to pass the Data (String) from a TextField to a second Page with Provider after I clicked the Button.
Here is my code
Update your name_provider class
class NameProvider extends ChangeNotifier {
String _name = "";
String get getName => _name;
saveName(String name) {
_name = name;
notifyListeners();
}
}
The name variable was made private to avoid getting wrong result while calling the function.
Now this edit was made to main.dart file
class _MyHomePageState extends State<MyHomePage> {
String name = "";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Please enter your name',
),
TextField(
onSubmitted: (value) {
setState(() {
Provider.of<NameProvider>(context).saveName(value);
});
},
onChanged: (value) {
setState(() {
name = value;
});
},
),
Text("Name: $name"),
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondPage(),
),
);
},
child: const Text("To Second Page"),
),
],
),
),
),
);
}
}
Now getting name in SecondPage:
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Second Page"),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text(
"Name:",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
Text(
Provider.of<NameProvider>(context).getName,
style: TextStyle(fontSize: 32),
),
],
),
),
),
);
}
}
If there is some sort of expected string but got... error, replace getName with '${Provider.of<NameProvider>(context).getName}'
Let us know if this solution works
First of all, in the main.dart file at line 70, do this:
Provider.of<NameProvider>(context).saveName(name);
and then on the second page where you want to use this Provider.of<NameProvider>(context).name;
Here is the full working code.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ProviderCheck extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => NameProvider(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Provider Problem'),
),
);
}
}
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> {
String name = "";
//it will store data in typed in textfield
final TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Please enter your name',
),
TextField(
controller: _textEditingController,//texteditingcontroller set to textfield
onSubmitted: (value) {
setState(() {
name = value;
});
},
onChanged: (value) {
setState(() {
name = value;
});
},
),
Text("Name: $name"),
TextButton(
onPressed: () {
//saveName method called using provider and name saved.
Provider.of<NameProvider>(context, listen: false)
.saveName(_textEditingController.text);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondPage(),
),
);
},
child: const Text("To Second Page"),
),
],
),
),
),
);
}
}
class NameProvider extends ChangeNotifier {
String name = "";
String get getName => name;
//name1 value will set to name variable
saveName(String name1) {
name = name1;
notifyListeners();
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Second Page"),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//using provider, we can access the saved name
Text(
"Name:${Provider.of<NameProvider>(context, listen: false).getName}",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
Text(
//TODO: Name should be displayed here
"Name",
style: TextStyle(fontSize: 32),
),
],
),
),
),
);
}
}
if you prefer to use provider for that purpose,
edit your provider saveName Function to :
saveName(String _name) {
name = _name;
notifyListeners();
}
in SecondPage() add
your provider to the build method like this :
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final nameProvider = Provider.of< NameProvider>(context);
then get your string value by calling :
Text(
"Name: ${nameProvider.name}",
style : ....

How to refresh data after returning to a screen in flutter

I have the following cart screen which contains the address fields. When a user wants to update their address, they click on Update button, which takes them to the address screen where they can update the address. Although when the back button is clicked to return back to the cart screen,the print function shows the updated value but the UI still shows the old value.
class CartScreen extends StatefulWidget {
static const routeName = '/cart';
#override
_CartScreenState createState() => _CartScreenState();
}
class _CartScreenState extends State<CartScreen> {
Future _addressFuture;
String value = 'deliver';
var address;
void initState() {
_addressFuture = _obtainAddressFuture();
super.initState();
}
Future _obtainAddressFuture() async {
address = await Provider.of<Addresses>(context, listen: false).fetchMyAddress();
}
void didChangeDependencies() async {
print('inside didchange');
super.didChangeDependencies();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
print('Rebuild cart');
return GestureDetector(
child: Scaffold(
appBar: AppBar(
title: Text('Your Cart'),
),
body: FutureBuilder(
future: _addressFuture,
builder: (ctx, dataSnapshot) {
if (dataSnapshot.error != null) {
print('Data snapshot error is ' +
dataSnapshot.error.toString());
return Center(
child: Text('Something went wrong!'),
);
} else if (dataSnapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: Loading(),
);
} else {
return Container(
child: SingleChildScrollView(
child: Column(
children: [
Card(
margin: EdgeInsets.symmetric(
horizontal: 15,
vertical: 4,
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
new GestureDetector(
onTap: () async{
_awaitReturnValueFromSecondScreen(context);
},
child: Container(
padding: EdgeInsets.all(10),
child: const Text("Update",
style: TextStyle(
color: Colors.blue))),
)
],
),
TextFormField(
initialValue: address.name,
readOnly: true,
decoration: InputDecoration(
border: InputBorder.none,
prefixIcon: Icon(Icons.person,
color: Colors.orangeAccent),
labelText: 'Contact name'),
),
],
)),
],
)));
}
})));
}
void _awaitReturnValueFromSecondScreen(BuildContext context) async {
// start the SecondScreen and wait for it to finish with a result
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddressScreen('cartScreen'),
));
setState(() {
address = result;
});
print(address.name);
}
}
please check the below example as it requires async and await to get the data from 2nd screen
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(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 Location',
theme: ThemeData(
primarySwatch: Colors.amber,
),
home: Provider(
create: (context) => Addresses(),
child: CartScreen(),
),
);
}
}
class CartScreen extends StatefulWidget {
static const routeName = '/cart';
#override
_CartScreenState createState() => _CartScreenState();
}
class _CartScreenState extends State<CartScreen> {
Future? _addressFuture;
String value = 'deliver';
var address;
void initState() {
_addressFuture = _obtainAddressFuture();
super.initState();
}
Future _obtainAddressFuture() async {
address =
await Provider.of<Addresses>(context, listen: false).fetchMyAddress();
}
void didChangeDependencies() async {
super.didChangeDependencies();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Scaffold(
appBar: AppBar(
title: Text('Your Cart'),
),
body: FutureBuilder(
future: _addressFuture,
builder: (ctx, dataSnapshot) {
if (dataSnapshot.error != null) {
print('Data snapshot error is ' + dataSnapshot.error.toString());
return const Center(
child: Text('Something went wrong!'),
);
} else if (dataSnapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
return SingleChildScrollView(
child: Column(
children: [
Card(
margin: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 4,
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () async {
// Here you have to wait for the screen to retrun data so we use asyn await
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
AddressScreen(title: 'cartScreen'),
),
);
print(result);
},
child: Container(
padding: EdgeInsets.all(10),
child: const Text(
"Update",
style: TextStyle(
color: Colors.blue,
),
),
),
)
],
),
TextFormField(
// initialValue: address.name,
readOnly: true,
decoration: InputDecoration(
border: InputBorder.none,
prefixIcon: Icon(Icons.person,
color: Colors.orangeAccent),
labelText: 'Contact name',
),
),
],
),
),
],
),
);
}
},
),
),
);
}
}
class AddressScreen extends StatelessWidget {
const AddressScreen({
Key? key,
required this.title,
}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: TextButton(
onPressed: () {
// Here you have pass the data that you want
Navigator.pop(context, 'Data pass here ');
},
child: Text('Pass data back'),
),
),
);
}
}
class Addresses {
Future fetchMyAddress() async {}
}

How can I get the input of my textfield inside my custom dialog widget?

I am working on a custom dialog called "Alertbox" where the user inserts a name into a textfield and after he pushes the button a function called "addTeam" created a team out of the string.
This is how I created my dialog "Alertbox":
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:trainings_app/config/palette.dart';
class Alertbox extends StatelessWidget {
final Function addTeamFunction;
const Alertbox(this.addTeamFunction);
#override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
insetPadding: EdgeInsets.all(10),
child: Center(
child: Container(
decoration: new BoxDecoration(
borderRadius: new BorderRadius.all(const Radius.circular(20)),
color: Colors.white,
),
width: 350,
height: 200,
child: Row(
children: [
SizedBox(width: 12),
Expanded(
child: TextField(
textAlign: TextAlign.center,
autofocus: true,
),
),
SizedBox(width: 12),
ElevatedButton(
onPressed: () => addTeamFunction(),
child: const Text('✓'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Palette.orange),
),
),
SizedBox(width: 8),
],
),
),
),
);
}
}
And here I am using it:
void newTeam() {
showDialog<AlertDialog>(
context: context,
builder: (BuildContext context) {
return Alertbox(() {
Navigator.of(context).pop();
});
},
);
}
void addTeam(String name) {
setState(() {
teams.add(name);
});
Navigator.of(context).pop();
sharedPreferences.setStringList('teams', teams);
}
But I can't find a way to parse the input from the textfield into the function "addTeam" where it is needed. Can anyone help me please?
You Should try below code hope its helps you:
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 const MaterialApp(
title: 'Testing',
home: MyCustomForm(),
);
}
}
class MyCustomForm extends StatefulWidget {
const MyCustomForm({Key? key}) : super(key: key);
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
final myController = TextEditingController();
#override
void dispose() {
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Retrieve Text Input'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: myController,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text(myController.text),
);
},
);
},
tooltip: 'Show the value!',
child: const Icon(Icons.add),
),
);
}
}
Your Screen like ->
Use a TextFormField instead of a TexiField widget contained in a Form widget that has a GlobalKey, which will be useful to you during validation!
How to get the value which is already entered on the keyboard?
Uses a TextEditingController or the onSaved method of the TextFormField.

Flutter Bloc State Success State Not woking

I want Popup dialog or Card when state.success is NotEmpty
When success state is empty it return Text as i set in code
I don't know, Widget error or Other
I need help
import 'package:bloc_salesearch/src/screen/page_settings.dart';
import 'package:bloc_salesearch/src/search_bloc/bloc.dart';
import 'package:bloc_salesearch/src/util/utils.dart';
import 'package:bloc_salesearch/src/widget/utils_widget.dart';
import 'package:flutter/material.dart';
import 'package:bloc_salesearch/src/widget/widgets.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../src.dart';
class SearchPage extends StatefulWidget {
#override
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
Screen size;
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
size = Screen(MediaQuery.of(context).size);
return Scaffold(
backgroundColor: backgroundColor,
body: AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: backgroundColor,
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.dark,
systemNavigationBarColor: backgroundColor,
systemNavigationBarIconBrightness: Brightness.dark,
),
child: Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
upperPart(),
],
),
),
),
),
);
}
Widget upperPart() {
return Stack(
children: <Widget>[
ClipPath(
clipper: UpperClipper(),
child: Container(
height: size.getWidthPx(240),
decoration: BoxDecoration(
gradient:
LinearGradient(colors: [colorCurve, colorCurveSecondary]),
),
),
),
Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(top: size.getWidthPx(36)),
child: Column(
children: <Widget>[
titleWidget(),
SizedBox(
height: size.getWidthPx(10),
),
upperBoxCard(),
SizedBox(
height: size.getWidthPx(10),
),
SearchResult(),
],
),
),
],
),
],
);
}
Text titleWidget() {
return Text("Search Lottery",
style: TextStyle(
// fontFamily: 'Exo2',
fontSize: 24.0,
fontWeight: FontWeight.w900,
color: Colors.white));
}
Card upperBoxCard() {
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
margin: EdgeInsets.symmetric(
horizontal: size.getWidthPx(20), vertical: size.getWidthPx(16)),
borderOnForeground: true,
child: Container(
height: size.getWidthPx(150),
child: Column(
children: <Widget>[
SearchWidget(),
leftAlignText(
text: "Your Number foo win :",
leftPadding: size.getWidthPx(16),
textColor: textPrimaryColor,
fontSize: 16.0),
rightAlignText(
text: '150000',
rightPadding: size.getWidthPx(16),
textColor: textPrimaryColor,
fontSize: 16.0)
],
),
));
}
//
}
class SearchResult extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<SearchBloc, SearchState>(
builder: (context, state) {
if (state is SearchStateLoading) {
return Container(
height: 40,
child: Center(
child: CircularProgressIndicator(),
),
);
}
if (state is SearchStateError) {
return Text(state.error);
}
if (state is SearchStateSuccess) {
print(state);
return state.items.isEmpty
? Text('No Found!Good Luck Next Month!')
: Navigator.pushReplacement(context, MaterialPageRoute(builder: (context)=> SettingPage()));
}
return Text('Please Type your Lottery Number To begin');
},
);
}
}
class _SearchResult extends StatelessWidget {
final List<Sale> items;
const _SearchResult({Key key, this.items}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return _SearchView(item: items[index]);
},
);
}
}
class _SearchView extends StatelessWidget {
final Sale item;
const _SearchView({Key key, this.item}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(child: Text(''
'You Win'),);
}
}
class SearchWidget extends StatefulWidget {
#override
_SearchWidgetState createState() => _SearchWidgetState();
}
class _SearchWidgetState extends State<SearchWidget> {
final texEdc = TextEditingController();
SearchBloc _searchBloc;
#override
void initState() {
// TODO: implement initState
super.initState();
_searchBloc = BlocProvider.of<SearchBloc>(context);
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
texEdc.dispose();
}
#override
Widget build(BuildContext context) {
return TextField(
controller: texEdc,
autocorrect: false,
onChanged: (text) {
_searchBloc.dispatch(
TextChanged(text: text),
);
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
suffixIcon: GestureDetector(
child: Icon(Icons.clear),
onTap: _onClearTapped,
),
border: InputBorder.none,
hintText: 'Enter your Lottery number',
),
);
}
void _onClearTapped() {
texEdc.text = '';
_searchBloc.dispatch(TextChanged(text: ''));
}
}
You can listen to Bloc state with the BlocListener and execute actions depending on different states.
You should wrap your widget or bloc builder into a BlocListener as this:
BlocListener(
bloc: _searchBloc,
listener: (BuildContext context, SearchState state) {
if(state is SearchStateSuccess){
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context)=> SettingPage()));
}
},
child: your_blocBuilder
)

How to populate a form from a listview on tap in flutter

I have a form widget, a list widget, and a "wrapper" widget or in other words, a parent/container widget. So to give an idea of the widget tree, it is as such.
Parent/Container Widget
Form Widget
Button Widget
List Widget
Notice that the form, buttons and list widget are all siblings, inside of the parent/container widget. What I want to happen, is tap on a list item in the list widget, and populate the form widget with the data that gets passed from the list widget.
Here is my parent widget.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:andplus_flutter_7_gui/services/user_service.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'crud_form.dart';
import 'crud_list.dart';
class Crud extends StatefulWidget {
Crud({Key key, this.title}) : super(key: key);
final String title;
_CrudContainerState createState() => _CrudContainerState();
}
class _CrudContainerState extends State<Crud> {
List<User> users;
User user = User();
UserService userService;
#override
void initState() {
super.initState();
if (userService == null) {
userService = UserService(user);
}
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
userService.dispose();
}
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(widget.title),
),
body: Builder(
builder: (context) => Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 2,
child: StreamBuilder(
builder: (context, AsyncSnapshot<User> snapshot) {
return CrudForm(
user: snapshot.data,
onUserAdded: (user) {
userService.addUser(user);
},
);
},
stream: userService.userObservable,
),
),
Expanded(
child: Text("Future button widget"),
),
Expanded(
flex: 3,
child: StreamBuilder(
builder: (ctx, AsyncSnapshot<List<User>> snap) {
return CrudList(
onUserSelected: userService.userSelected,
users: snap.data,
);
},
stream: userService.usersObservable,
),
),
],
),
),
),
);
}
void onEditUser(User user) {
setState(() {
user = user;
});
}
}
The above widget wraps the three widgets I mentioned.
Here are the children widget:
Form:
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:flutter/material.dart';
class CrudForm extends StatefulWidget {
CrudForm({Key key, this.onUserAdded, this.user}) : super(key: key);
final User user;
final void Function(User user) onUserAdded;
_CrudFormState createState() => _CrudFormState(user: user);
}
class _CrudFormState extends State<CrudForm> {
_CrudFormState({this.user});
User user = User();
var _key = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Container(
child: Builder(
builder: (context) => Container(
color: Colors.blueAccent[100],
child: Form(
key: _key,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Text(
"First Name",
style: TextStyle(fontSize: 20),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
initialValue: widget.user?.firstName == null ||
widget.user.firstName.isEmpty
? user.firstName
: widget.user.firstName,
validator: (value) {
if (value.isEmpty) {
return "First name is required";
}
return null;
},
onSaved: (value) {
setState(() {
user.firstName = value;
});
},
),
),
)
],
),
Row(
children: <Widget>[
Text(
"Last Name",
style: TextStyle(fontSize: 20),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (value) {
if (value.isEmpty) {
return "Last name is required";
}
return null;
},
onSaved: (value) {
setState(() {
user.lastName = value;
});
},
),
),
),
],
),
RaisedButton(
child: Text(
"Save",
),
splashColor: Colors.blueGrey,
onPressed: () {
if (!_key.currentState.validate()) {
return;
}
_key.currentState.save();
widget.onUserAdded(
new User(
firstName: user.firstName,
lastName: user.lastName,
),
);
},
)
],
),
),
),
),
),
);
}
}
Here is my list widget.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:flutter/material.dart';
class CrudList extends StatefulWidget {
CrudList({Key key, this.users, this.onUserSelected}) : super(key: key);
final List<User> users;
final SelectUser onUserSelected;
_CrudListState createState() => _CrudListState();
}
class _CrudListState extends State<CrudList> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: ListView.builder(
itemCount: widget.users?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
var user = widget.users[index];
return ListTile(
key: Key(index.toString()),
title: Center(
child: Text(
"${user.firstName} ${user.lastName}",
style: TextStyle(color: Colors.white),
),
),
onTap: () {
print("${widget.users[index]} $index");
widget.onUserSelected(widget.users[index]);
},
);
},
),
);
}
}
typedef void SelectUser(User user);
And just for further context, here is my user service, responsible for adding the objects to the stream, and using the stream builder within rxdart to notify of state changes.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:rxdart/rxdart.dart';
class UserService {
User _editedUser = User();
List<User> _users = <User>[];
BehaviorSubject<User> _userSubject;
BehaviorSubject<List<User>> _usersSubject;
UserService(this._editedUser) {
_userSubject = BehaviorSubject<User>.seeded(_editedUser);
_usersSubject = BehaviorSubject<List<User>>.seeded(_users);
}
Observable<List<User>> get usersObservable => _usersSubject.stream;
Observable<User> get userObservable => _userSubject.stream;
addUser(User user) {
_users.add(user);
_usersSubject.add(_users);
}
dispose() {
_userSubject.close();
_usersSubject.close();
}
void userSelected(User user) {
_editedUser = user;
_userSubject.add(_editedUser);
}
}
What am I missing? It looks like my widget rebuilds, and tries to set the initial value in the form when I tap the user in the list widget. But the actual field doesn't get updated and I'm not sure why.
I'd appreciate any documentation or articles on how to better approach data and state management between sibling widgets within the flutter framework.
Here's a similar use case that I tried to implement locally. What I'm doing here is I generate TextFormFields dynamically and assign TextEditingController.
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
_selectedField = textEditingController;
},
),
);
n--;
}
return Column(children: listForm);
}
Clicking a ListView item updates the text of the currently selected TextFormField.
InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
_selectedField!.value = TextEditingValue(text: 'Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
Complete sample
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');
_selectedField = textEditingController;
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
if (_selectedField != null) {
_selectedField!.value = TextEditingValue(text: 'Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}
Demo