FutureBuilder doesn't update when updating the database - flutter

I was working in a simple notes app in flutter, this is my code.
i just made this example to preview the problem
This is the home page that navigate to a create page, and uses a futureBuilder to display the data from the database
import 'package:flutter/material.dart';
import 'package:futurebuilder_test/db.dart';
import 'create.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final DatabaseHelper db = DatabaseHelper.instance;
Future futureData;
#override
void initState() {
futureData = _getData();
super.initState();
}
Future _getData() async{
return await db.queryAllRows('notes');
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => _navigateToCreatePage(context),
),
body: FutureBuilder(
future: _getData(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, index) {
return ListTile(
title: Text(snapshot.data[index]['title']),
);
},
);
}
return Text('Empty');
},
),
);
}
}
void _navigateToCreatePage(context) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => CreatePage())
);
}
and this is the create page
import 'package:flutter/material.dart';
import 'package:futurebuilder_test/db.dart';
class CreatePage extends StatelessWidget {
final DatabaseHelper db = DatabaseHelper.instance;
final TextEditingController title = TextEditingController();
final TextEditingController description = TextEditingController();
void save(context) async {
await db.insert(
'notes', {'title': title.text, 'description': description.text}
);
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextField(
decoration: InputDecoration(hintText: 'Title'),
controller: title,
),
TextField(
decoration: InputDecoration(hintText: 'Title'),
controller: description,
),
RaisedButton(
child: Text('Save'),
onPressed: () => save(context),
)
],
),
),
);
}
}
when the user click the save button it adds a new note to the database and pop the page back to home screen It's supposed that the future builder update it's data after submitting new data to the database
but this is not happening. could someone help me.

You should dispose the previous page using this or handle everything with state management.

Related

Flutter - drawer doesn't pop out when performing logout

I have an app that presents an AuthScreen and then works with subsequent screens based on the Auth provider class result.
In fact, in the main.dart file I present different pages based on the provider class Auth.
If the user performs the login (and he is Supplier), he will be directed to SupplierOverviewScreen, where he can see all his events.
If he clicks on an event, he will be directed to the EditEventScreen class, where he can modify the event.
Both SupplierOverviewScreen and EditEventScreen use the same drawer (SupplierDrawer), which allows to perform the logout operation.
When peforming a logout, Auth info will be deleted and so the main.dart file (consuming Auth provider class) will present againt the AuthScreen page.
If I'm on the SupplierOverviewScreen and I open the drawer to logout, everything works.
The problem is that if I'm on the EditEventScreen and I try to logout, The screen remains stuck and the drawer doesn't pop out.
I see that under the hood everything works, and the main.dart file returns the AuthScreen exactly as it does in SupplierOverviewScreen (where it works), but nothing changes on the screen.
If I return to previous screen, it doesn't direct me to SupplierOverview screen, but to AuthScreen.
I've found two possible hacks, but I'm not satisfied with them:
Remove drawer from EditEventScreen
Change the drawer such that after the logout it performs Navigator.pushReplacementNamed(context, "/"); It works, but since main.dart file consumes Auth provider class, I have that the home is called twice (one when Auth provider class notify listeners and one when drawer calls Navigator.pushReplacementNamed(context, "/")
Do you have any idea to suggest? Thanks a lot
This is the main.dart file:
Future<void> main() async {
await dotenv.load(fileName: Environment.fileName);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
//Init state operations
}
void _configureAmplify() async {
// Amplify initial configurations
}
List<Event> _mockEvents() {
// mocked list of event objects
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => Auth()),
ChangeNotifierProxyProvider<Auth, Supplier>(
create: (ctx) => Supplier(userId: null, username: null),
update: (ctx, authData, previous) => Supplier(
userId: authData.getUserId, username: previous?.username),
),
ChangeNotifierProxyProvider<Auth, SupplierEvents>(
create: (ctx) => SupplierEvents(
userId: null,
events:
_mockEvents()),
update: (ctx, authData, previous) => SupplierEvents(
userId: authData.getUserId, events: previous?.events ?? []),
),
],
child: Consumer<Auth>(
builder: (context, authData, child) => MaterialApp(
title: 'Apperò',
theme: ThemeData(
colorScheme: Theme.of(context).colorScheme.copyWith(),
),
home: !authData.isAuth()
? FutureBuilder(
future: authData.tryAutoLogin(),
builder: (ctx, authResultSnapshot) {
print(authResultSnapshot.connectionState.name);
if (authResultSnapshot.connectionState ==
ConnectionState.waiting) {
return LoadingScreen();
} else
return AuthScreen();
})
: (authData.getUserType == UserType.supplier
? SupplierOverviewScreen()
: CustomerOverviewScreen()),
routes: {
SupplierOverviewScreen.ROUTE_NAME: (ctx) =>
SupplierOverviewScreen(),
CustomerOverviewScreen.ROUTE_NAME: (ctx) =>
CustomerOverviewScreen(),
EditEventScreen.ROUTE_NAME: (ctx) => EditEventScreen(),
}),
),
);
}
}
This is the SupplierOverviewScreen class:
class SupplierOverviewScreen extends StatefulWidget {
static const String ROUTE_NAME = '/supplier-overview-screen';
const SupplierOverviewScreen({Key? key}) : super(key: key);
#override
State<SupplierOverviewScreen> createState() => _SupplierOverviewScreenState();
}
class _SupplierOverviewScreenState extends State<SupplierOverviewScreen> {
late Future _obtainedInfo;
Future<void> _fetchInfo() async {
await Provider.of<Supplier>(context, listen: false).fetchSupplierByUserId();
await Provider.of<SupplierEvents>(context, listen: false)
.fetchEventsByUserId();
}
#override
void initState() {
_obtainedInfo = _fetchInfo();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Your page'), actions: []),
drawer: SupplierDrawer(),
body: FutureBuilder(
future: _obtainedInfo,
builder: (context, snapshot) => snapshot.connectionState ==
ConnectionState.waiting
? const Center(
child: CircularProgressIndicator(),
)
: RefreshIndicator(
onRefresh: () => _fetchInfo(),
child: Column(
children: [
Consumer<Supplier>(
builder: (context, supplierData, _) => Padding(
padding: EdgeInsets.all(8),
child: Text('Hello ${supplierData.username}'),
),
),
const SizedBox(
height: 10,
),
Consumer<SupplierEvents>(
builder: (context, supplierEventsData, _) => Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: supplierEventsData.events.length,
itemBuilder: (context, index) => Column(children: [
SupplierEventsItem(
event: supplierEventsData.events[index],
),
const Divider(),
]),
),
),
),
const SizedBox(
height: 10,
),
Padding(
padding: EdgeInsets.all(8),
child: Text('Bottom element'),
),
],
),
),
),
floatingActionButton: FloatingActionButton(
child: const Text('Add Event', textAlign: TextAlign.center,),
onPressed: () {
Navigator.of(context)
.pushNamed(EditEventScreen.ROUTE_NAME);
},
));
}
}
This is the EditEventScreen class:
class EditEventScreen extends StatefulWidget {
static const String ROUTE_NAME = '/edit-event-screen';
const EditEventScreen({Key? key}) : super(key: key);
#override
State<EditEventScreen> createState() => _EditEventScreenState();
}
class _EditEventScreenState extends State<EditEventScreen> {
late Future _obtainedInfo;
Event? inputEvent;
var _isLoading = false;
var _form = GlobalKey<FormState>();
var _titleFocusNode = FocusNode();
var _descriptionFocusNode = FocusNode();
Map<String, dynamic?> _initialValuesMap = {
//....
};
Event event = Event(// ...);
#override
void didChangeDependencies() {
_obtainedInfo = _fetchInfo();
super.didChangeDependencies();
}
#override
void dispose() {
// ...
}
Future<void> _fetchInfo() async {
try {
var eventId = ModalRoute.of(context)!.settings.arguments as int?;
print('eventId: $eventId');
if (eventId != null) {
inputEvent = await Provider.of<SupplierEvents>(context, listen: false)
.findById(eventId);
_initFormFields();
}
} catch (error) {
print(error);
}
}
void _initFormFields() {
// init form fields logic
}
Future<void> _saveForm() async {
//Form saving logic
}
#override
Widget build(BuildContext context) {
print('Building EditEventScreen');
return Scaffold(
appBar: AppBar(title: Text('Manage event'), actions: [
IconButton(
onPressed: () {
_saveForm();
},
icon: Icon(Icons.check))
]),
drawer: SupplierDrawer(),
body: FutureBuilder(
future: _obtainedInfo,
builder: (context, snapshot) => snapshot.connectionState ==
ConnectionState.waiting
? const Center(
child: CircularProgressIndicator(),
)
: _isLoading
? const Center(
child: CircularProgressIndicator(),
)
: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _form,
child: SingleChildScrollView(
child: Column(
children: [
// TextFormFields ...
],
),
),
),
),
),
);
}
}
This is the drawer class:
class SupplierDrawer extends StatelessWidget {
Future<void> logout(BuildContext context) async {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Logout'),
content: Container(
alignment: Alignment.center,
height: 300,
width: 300,
child: Column(
children: const [
Text('Logging out...'),
Center(
child: CircularProgressIndicator(),
)
],
),
),
);
},
);
await Provider.of<Auth>(context, listen: false).signOutCurrentUser(false);
Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: [
AppBar(
title: Text('Your options'),
automaticallyImplyLeading: false,
),
Divider(),
ListTile(
leading: Icon(Icons.logout),
title: Text('Logout'),
onTap: () async {
await logout(context);
},
),
],
),
);
}
}

pass value between bottomNavigationBar views

How am I supposed to pass a value in this big mess called Flutter?
30 years old php global $var wasn't good?
All these years were to come up with setState, passed in a controller which get redeclared as a key inside a stateful widget that receive the value from a Navigator?
By the way, I tried using Navigator.push but it seems to open a completely new window, the value is there but I'd need it to show in the tab body not in a new window, below is my code:
main.dart
import 'dart:core';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeView(),
);
}
}
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
final tabs = [QRViewExample(), SecondView(res: '')];
int _currentIndex = 0;
#override
void initState() {
setState(() {});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 40.0,
elevation: 0,
centerTitle: true,
title: Text('Flutter App'),
),
body: tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.red,
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white.withOpacity(0.5),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.qr_code),
label: 'Scan',
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'List',
),
],
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
// SECOND TAB WIDGET (custom)
class SecondView extends StatelessWidget {
const SecondView({Key? key, required this.res}) : super(key: key);
final String? res;
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text(res!),
),
);
}
}
// FIRST TAB WIDGET (qrcode)
class QRViewExample extends StatefulWidget {
const QRViewExample({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _QRViewExampleState();
}
class _QRViewExampleState extends State<QRViewExample> {
Barcode? result;
QRViewController? controller;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
#override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller!.pauseCamera();
}
controller!.resumeCamera();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 500,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Expanded(flex: 4, child: _buildQrView(context)),
Expanded(
flex: 1,
child: FittedBox(
fit: BoxFit.contain,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
if (result != null)
Text(
'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}')
else
const Text('Scan a code'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.toggleFlash();
setState(() {});
},
child: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return Text('Flash: ${snapshot.data}');
},
)),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.flipCamera();
setState(() {});
},
child: FutureBuilder(
future: controller?.getCameraInfo(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return Text(
'Camera facing ${describeEnum(snapshot.data!)}');
} else {
return const Text('loading');
}
},
)),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.pauseCamera();
},
child: const Text('pause',
style: TextStyle(fontSize: 20)),
),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.resumeCamera();
},
child: const Text('resume',
style: TextStyle(fontSize: 20)),
),
)
],
),
],
),
),
)
],
),
),
),
);
}
Widget _buildQrView(BuildContext context) {
var scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
? 150.0
: 300.0;
return QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.cyanAccent,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) {
controller.pauseCamera();
setState(() {
result = scanData;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondView(res: result!.code)))
.then((value) => controller.resumeCamera());
});
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
log('${DateTime.now().toIso8601String()}_onPermissionSet $p');
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('no Permission')),
);
}
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
}
How am I supposed to pass a value in this big mess called Flutter?
With state management tools like InheritedWidget, InheritedModel, Provider, BloC and many more.
30 years old php global $var wasn't good? All these years were to come up with setState, passed in a controller which get redeclared as a key inside a stateful widget that receive the value from a Navigator?
Well, you shouldn't do that and it's not meant to be done like that. We can use several methods to propagate data down the widget tree. Let me explain this with InheritedWidget. But sometimes you want to go for Provider which is a wrapper class for InheritedWidget.
First we create a class named QRListModel which extends InheritedModel:
class QRListModel extends InheritedWidget {
final List<Barcode> qrList = []; // <- This holds our data
QRListModel({required super.child});
#override
bool updateShouldNotify(QRListModel oldWidget) {
return !listEquals(oldWidget.qrList, qrList);
}
static QRListModel of(BuildContext context) {
final QRListModel? result = context.dependOnInheritedWidgetOfExactType<QRListModel>();
assert(result != null, 'No QRListModel found in context');
return result!;
}
}
updateShouldNotify is a method we have to override to tell Flutter, when we want the widgets to rebuild. We want this to happen when the list changes. The of method is just a handy way to access the QRListModel.
Now wrap a parent widget of both the scan tab view and the list tab view inside QRListModel. We go for HomeView:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: QRListModel(child: HomeView()), // <- here!
);
}
}
We can take any parent widget but it should be a class where we don't call setState. Otherwise our QRListModel also gets rebuilt and our list is gone.
Now we can access QRListModel from anywhere inside the subtree. We need it here:
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
this.controller!.resumeCamera();
});
controller.scannedDataStream.listen((scanData) async {
controller.pauseCamera();
QRListModel.of(context).qrList.add(scanData); // <- Here we access the list
await showDialog(
context: context,
builder: (context) => SimpleDialog(
title: Text("Barcode was added!"),
children: [
Text(scanData.code!)
],
)
);
});
}
And here we read the list:
class SecondView extends StatelessWidget {
const SecondView({Key? key, required this.res}) : super(key: key);
final String? res;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: QRListModel.of(context).qrList.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(QRListModel.of(context).qrList[index].code ?? "NO"),
),
);
}
);
}
}
Now both pages have access to the qr list. Please do mind that a InheritedWidget can only have final fields. So if you need mutable fields, you need an additional wrapper class. We don't need it as we don't change the list but only its elements.
By the way: You shouldn't call setState inside initState. You did this here:
class _HomeViewState extends State<HomeView> {
final tabs = [QRViewExample(), SecondView(res: '')];
int _currentIndex = 0;
#override
void initState() {
setState(() {}); // <- Don't call setState inside initState!
super.initState();
}

I was able to display the data in CloudFirestore, but I want to transfer the data to the other side of the screen

I have managed to acquire and display data using CloudFireStore, but I would like to send the acquired data to the destination screen when the screen is transferred.
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NextPage('test')),
));
but I don't know which function to put in.
↓This code is the one that sends the data.
class Rigakubu extends StatefulWidget {
const Rigakubu({Key? key}) : super(key: key);
#override
State<Rigakubu> createState() => _RigakubuState();
}
class _RigakubuState extends State<Rigakubu> {
final _firestore = FirebaseFirestore.instance;
List<DocumentSnapshot> documentList = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('理学部'),),
body: SafeArea(
child: StreamBuilder(
stream: _firestore.collection('理学部').snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Center(
child:Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.do_disturb_on_outlined,size: 150,),
Text(
'校外のメールアドレスでログインしているため\nこの機能は利用できません。',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
],
)
);
}
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator()
);
}
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index){
return ListTile(
title: Text(snapshot.data!.docs[index].get('zyugyoumei')),
subtitle: Text(snapshot.data!.docs[index].get('kousimei')),
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => View(post: snapshot.data!.docs)),
);
}
);
}
);
},
)
),
);
}
}
↓This code is the code on the receiving end of the data.
import 'package:flutter/material.dart';
import 'package:http/http.dart';
class View extends StatefulWidget {
final int post;
View(this.post);
const View({Key? key, required this.post}) : super(key: key);
#override
State<View> createState() => _ViewState();
}
class _ViewState extends State<View> {
late int state;
#override
void initState() {
super.initState();
// 受け取ったデータを状態を管理する変数に格納
state = widget.post;
}
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.post.bumon),
);
}
}
You could probably try this inside the onTap function before calling the Navigator
documentList = snapshot.data!.docs;
But generally avoid passing data through constructor and try to work with state management.

Can't add the data to list using Provider in flat button on flutter

I'm using provider 4.3.2 in this flutter code, this is a simple flutter app that has a text filed, flat button, and a list view builder that contain the text widget. I created a class ListData that has the list and is shown in the list view builder using provider. Here is the problem, I created a addData method in the ListData class. I used this method to add data to list using provider in the onPressed method of flat button add it is throwing error, unable to find. the solution for this problem. Also this is a short form of my main app
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
String data;
return ChangeNotifierProvider(
create: (context) => ListData(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("list"),
),
body: Column(
children: [
TextField(
onChanged: (value) => data = value,
),
FlatButton(
child: Text("Add"),
color: Colors.blue,
onPressed: () {
Provider.of<ListData>(context).addData(data);
},
),
Expanded(
child: MyListView(),
),
],
),
),
),
);
}
}
class MyListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Text(Provider.of<ListData>(context).listData[index]);
},
itemCount: Provider.of<ListData>(context).listCount,
);
}
}
class ListData extends ChangeNotifier {
List _listData = [
'Hello',
"hi",
];
UnmodifiableListView get listData {
return UnmodifiableListView(_listData);
}
int get listCount {
return _listData.length;
}
void addData(String data) {
_listData.add(data);
notifyListeners();
}
}
You can copy paste run full code below
You need Builder and listen: false
code snippet
Builder(builder: (BuildContext context) {
return FlatButton(
child: Text("Add"),
color: Colors.blue,
onPressed: () {
Provider.of<ListData>(context, listen: false).addData(data);
},
);
}),
working demo
full code
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
String data;
return ChangeNotifierProvider(
create: (context) => ListData(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("list"),
),
body: Column(
children: [
TextField(
onChanged: (value) => data = value,
),
Builder(builder: (BuildContext context) {
return FlatButton(
child: Text("Add"),
color: Colors.blue,
onPressed: () {
Provider.of<ListData>(context, listen: false).addData(data);
},
);
}),
Expanded(
child: MyListView(),
),
],
),
),
),
);
}
}
class MyListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Text(Provider.of<ListData>(context).listData[index]);
},
itemCount: Provider.of<ListData>(context).listCount,
);
}
}
class ListData extends ChangeNotifier {
List _listData = [
'Hello',
"hi",
];
UnmodifiableListView get listData {
return UnmodifiableListView(_listData);
}
int get listCount {
return _listData.length;
}
void addData(String data) {
_listData.add(data);
notifyListeners();
}
}
You need to wrap your FlatButton in a Consumer widget because Provider.of is called with a BuildContext that is an ancestor of the provider.
return ChangeNotifierProvider(
create: (_) => ListData(),
child: Consumer<ListData>(
builder: (_, listData, __) => FlatButton(onPressed: () => listData.addData(data)),
},
);
Check out this to learn more with simple examples to help you understand why you get the error and how to use it.
https://pub.dev/documentation/provider/latest/provider/Consumer-class.html

Flutter Bloc does not change TextFormField initialValue

I'm using Bloc library and noticed after yielding a new state my TextFormField initialValue does not change.
My app is more complicated than this but I did a minimal example. Also tracking the state it is changing after pushing the events.
Bloc is supposed to rebuild the entire widget right. Am I missing something?
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer' as developer;
void main() {
runApp(MyApp());
}
enum Event { first }
class ExampleBloc extends Bloc<Event, int> {
ExampleBloc() : super(0);
#override
Stream<int> mapEventToState(Event event) async* {
yield state + 1;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (_) => ExampleBloc(),
child: Builder(
builder: (contex) => SafeArea(
child: BlocConsumer<ExampleBloc, int>(
listener: (context, state) {},
builder: (context, int state) {
developer.log(state.toString());
return Scaffold(
body: Form(
child: Column(
children: [
TextFormField(
autocorrect: false,
initialValue: state.toString(),
),
RaisedButton(
child: Text('Press'),
onPressed: () {
context.bloc<ExampleBloc>().add(Event.first);
},
)
],
),
),
);
}),
),
),
),
);
}
}
pubspec.yaml
name: form
description: A new Flutter project.
version: 1.0.0+1
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
bloc: ^6.0.0
flutter_bloc: ^6.0.0
Edit
As #chunhunghan noted adding a UniqueKey solves this. I should have also mentioned that my case. the app emits events from the onChanged method of two TextFormField. This causes the Form to reset and remove the keyboard. autofocus does not work because there are two TextFormField wgich emit events.
You can copy paste run full code 1 and 2 below
You can provide UniqueKey() to Scaffold or TextFormField to force recreate
You can referecne https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d for detail
if the key of the Element doesn’t match the key of the corresponding Widget. This causes Flutter to deactivate those elements and remove the references to the Elements in the Element Tree
Solution 1:
return Scaffold(
key: UniqueKey(),
body: Form(
Solution 2:
TextFormField(
key: UniqueKey(),
working demo
full code 1 Scaffold with UniqueKey
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer' as developer;
void main() {
runApp(MyApp());
}
enum Event { first }
class ExampleBloc extends Bloc<Event, int> {
ExampleBloc() : super(0);
#override
Stream<int> mapEventToState(Event event) async* {
yield state + 1;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print("build");
return MaterialApp(
home: BlocProvider(
create: (_) => ExampleBloc(),
child: Builder(
builder: (contex) => SafeArea(
child: BlocConsumer<ExampleBloc, int>(
listener: (context, state) {},
builder: (context, int state) {
print("state ${state.toString()}");
developer.log(state.toString());
return Scaffold(
key: UniqueKey(),
body: Form(
child: Column(
children: [
TextFormField(
autocorrect: false,
initialValue: state.toString(),
),
RaisedButton(
child: Text('Press'),
onPressed: () {
context.bloc<ExampleBloc>().add(Event.first);
},
)
],
),
),
);
}),
),
),
),
);
}
}
full code 2 TextFormField with UniqueKey
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer' as developer;
void main() {
runApp(MyApp());
}
enum Event { first }
class ExampleBloc extends Bloc<Event, int> {
ExampleBloc() : super(0);
#override
Stream<int> mapEventToState(Event event) async* {
yield state + 1;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print("build");
return MaterialApp(
home: BlocProvider(
create: (_) => ExampleBloc(),
child: Builder(
builder: (contex) => SafeArea(
child: BlocConsumer<ExampleBloc, int>(
listener: (context, state) {},
builder: (context, int state) {
print("state ${state.toString()}");
developer.log(state.toString());
return Scaffold(
body: Form(
child: Column(
children: [
TextFormField(
key: UniqueKey(),
autocorrect: false,
initialValue: state.toString(),
),
RaisedButton(
child: Text('Press'),
onPressed: () {
context.bloc<ExampleBloc>().add(Event.first);
},
)
],
),
),
);
}),
),
),
),
);
}
}
You should not be rebuilding the entire Form just because you want to update the value of the TextFormField, try using a TextEditingController and update the value on the listener.
TextEditingController _controller = TextEditingController();
BlocProvider(
create: (_) => ExampleBloc(),
child: Builder(
builder: (contex) => SafeArea(
child: BlocListener<ExampleBloc, int>(
listener: (context, state) {
_controller.text = state.toString();
},
child: Scaffold(
body: Form(
child: Column(
children: [
TextFormField(
controller: _controller,
autocorrect: false,
),
RaisedButton(
child: Text('Press'),
onPressed: () {
context.bloc<ExampleBloc>().add(Event.first);
},
)
],
),
),
);
}),
I also had the exact same problem. While adding the Unique Key the flutter keeps building the widget and my keyboard unfocus each time. The way I solved it is to add a debounce in onChanged Event of the TextField.
class InputTextWidget extends StatelessWidget {
final Function(String) onChanged;
Timer _debounce;
void _onSearchChanged(String value) {
if (_debounce?.isActive ?? false) _debounce.cancel();
_debounce = Timer(const Duration(milliseconds: 2000), () {
onChanged(value);
});
}
#override
Widget build(BuildContext context) {
return TextFormField(
controller: TextEditingController(text: value)
..selection = TextSelection.fromPosition(
TextPosition(offset: value.length),
),
onChanged: _onSearchChanged,
onEditingComplete: onEditingCompleted,
);
}
}
Hope if this help for someone, working with form, bloc and and has too update the form.
Edit: Although adding a debounce help show what. I have changed the code to be more robust. Here is the change.
InputTextWidget (Changed)
class InputTextWidget extends StatelessWidget {
final Function(String) onChanged;
final TextEditingController controller;
void _onSearchChanged(String value) {
if (_debounce?.isActive ?? false) _debounce.cancel();
_debounce = Timer(const Duration(milliseconds: 2000), () {
onChanged(value);
});
}
#override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
onChanged: _onSearchChanged,
onEditingComplete: onEditingCompleted,
);
}
}
And on my presentation end
class _NameField extends StatelessWidget {
const _NameField({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final TextEditingController _controller = TextEditingController();
return BlocConsumer<SomeBloc,
SomeState>(
listenWhen: (previous, current) =>
previous.name != current.name,
listener: (context, state) {
final TextSelection previousSelection = _controller.selection;
_controller.text = state.name;
_controller.selection = previousSelection;
},
buildWhen: (previous, current) =>
previous.name != current.name,
builder: (context, state) => FormFieldDecoration(
title: "Name",
child: InputTextWidget(
hintText: "AWS Certification",
textInputType: TextInputType.name,
controller: _controller,
onChanged: (value) => context
.read< SomeBloc >()
.add(SomeEvent(
value)),
),
),
);
}
}
This edit is working perfectly.
Final Edit:
I added a key? key on my bloc state and pass this key to the widget. If I needed to redraw the form again, I changed the key to UniqueKey from the event. This is the by far easiest way I have implemented bloc and form together. If you needed explanation, please comment here, I will add it later.