Reputation: 700
Currently, I am working on the update profile portion of the app but there is seems to be an issue. The issue is when we want to go to the page we want to see the text field loaded up with the uses of previous data or old data. To do that we use a future builder to get the data and then set it to the field. But when we use the keyboard on mobile devices to tap and edit the text fields what happens is the future builder gets rebuilt again just because a build is invoked due to this changing of the size of the screen, the keyboard coming up and down. In our to fix his what I had to do was create a temporary future variable and it only updates if it's null. On top of that, the text field only updates if the count is equal to a certain value IE, in this case, one but it's not working. We want to place text in the text field every time we go to the page AKA refresh or click on the submit button. This is where I'm kind of having trouble. I have tried using states and I get the same result, maybe I'm setting it up wrong.
Goal: Load data, set text fields with data that I just got, edit them as I need them to (data stays as is with the keyboard going up or down). Then update that data via the update button.
old:
class Test extends StatelessWidget {
Future<void> getFuture() async {
_profile = await HTTP.getProfile();
}
Widget build(BuildContext context) {
return FutureBuilder(
future: getFuture(); // generate every time
builder: () {
return nameFeild();
}
)
}
Widget nameFeild(){
_changeNameController.text = _profile.name;
return TextField(controller: _nameController);
}
}
new:
class Test extends StatelessWidget {
Future testFuture;
static int count = 0;
Future<void> getFuture() async {
_profile = await HTTP.getProfile();
}
Widget build(BuildContext context) {
testFuture ??= getFuture();
count++;
return FutureBuilder(
future: testFuture; // generate every time
builder: () {
return nameFeild();
}
)
}
Widget nameFeild(){
if(count == 1){
_changeNameController.text = _profile.name;
}
return TextField(controller: _nameController);
}
}
Actual code
// imports
class UserSettingPage extends StatelessWidget {
BuildContext context;
GetSizes _sizes;
double _w;
double _h;
NavbarWidget _navBar = NavbarWidget();
FooterWidget _footer = FooterWidget.autoPosition();
ImageProvider _userPicture =
AssetImage('assets/images/team/zain.png'); //Defaultpic.png');
Cookies _cookies = Cookies();
final _oldPasswordControllerOne = TextEditingController();
final _resetPasswordControllerOne = TextEditingController();
final _resetPasswordControllerTwo = TextEditingController();
final _changeNameController = TextEditingController();
final _changeBioController = TextEditingController();
final _changeNumberController = TextEditingController();
final _changeFacebookController = TextEditingController();
final _changeTwitterController = TextEditingController();
final _changeInstagramController = TextEditingController();
CheckBoxValueNotifier email = new CheckBoxValueNotifier(false);
CheckBoxValueNotifier phone = new CheckBoxValueNotifier(false);
bool _isDesktop;
Authentication _authentication = Authentication();
Encryption _encryption = Encryption();
DialogBox _dialog = DialogBox();
passwordValidNotifier passwordNotifier = new passwordValidNotifier();
ProfileModel _user;
Uint8List _base64;
bool phoneCheckedValue = false;
bool emailCheckedValue = false;
String userID;
FilePickerWidget imagePicker = FilePickerWidget();
Future _getUser;
static int count = 0;
Future<String> retrieveUser() async {
userID = await Cookies.getCookieValue("userID");
await DioCalls.getProfile(context, userID).then((value) => _user = value);
}
@override
Widget build(BuildContext context) {
this.context = context;
_sizes = GetSizes(context);
_w = _sizes.getPageWidth();
_h = _sizes.getPageHeight();
_isDesktop = _sizes.isDesktop();
print('Update Data: ' + (_getUser == null).toString() + ' count: ' + count.toString());
_getUser ??= retrieveUser();
count++;
return LayoutBuilder(builder: (context, constraints) {
if (_sizes.isDesktop()) {
return _bigDisplay();
} else {
return _smallDisplay();
}
});
}
Widget _bigDisplay() {
// code
}
Widget _smallDisplay() {
return FutureBuilder(
future: _getUser,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: Text(''));
} else {
if (snapshot.hasError) {
return Center(child: Text('Errors: ${snapshot.error}'));
} else {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
_navBar,
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [userPictureAndBorder()],
),
userInfo(),
line(),
changeNameTile(),
line(),
changeBioTile(),
line(),
changeSocialMediaTile(),
line(),
changeNotificationTile(),
line(),
changePasswordTile(),
line(),
Container(
margin: EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [logoutButton(), updateButton(context)],
),
)
],
),
),
)
],
),
);
}
}
},
);
}
// OTHER WIDGETS CODE WERE REMOVED, I dont think you need them
Widget userInfo() {
return Container(
margin: EdgeInsets.fromLTRB(0, 5, 0, 10),
alignment: Alignment.center,
child: Column(
children: [
SelectableText(
_user.userID,
style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 14,
color: Colors.black,
// height: 21,
),
),
SelectableText(
_user.name,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14,
color: Colors.black,
// height: 21,
),
),
SelectableText(
'732-318-5555 (change)',
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14,
color: Colors.black,
// height: 21,
),
),
],
),
);
}
Widget logoutButton() {
return ElevatedButton.icon(
onPressed: () {
print('Logging out');
},
icon: Icon(Icons.logout),
label: Text("Logout"),
style: ElevatedButton.styleFrom(
primary: Colors.black, // background
onPrimary: Colors.white, // foreground
),
);
}
Widget updateButton(BuildContext context) {
return ElevatedButton.icon(
onPressed: () async {
_user.name = _changeNameController.text;
_user.bio = _changeBioController.text;
_user.socialMedia.Facebook = _changeFacebookController.text;
_user.socialMedia.Instagram = _changeInstagramController.text;
_user.socialMedia.Twitter = _changeTwitterController.text;
DioCalls.updateProfile(context, _user, imagePicker.selectedFile,
await Cookies.getCookieValue("jwt"));
Navigator.pop(context); // pop current page
Navigator.pushNamed(context, "/test1"); // push it back in
_oldPasswordControllerOne.clear();
_changeBioController.clear();
count = 0;
_getUser = null;
},
icon: Icon(Icons.update),
label: Text("Update"),
style: ElevatedButton.styleFrom(
primary: Colors.black, // background
onPrimary: Colors.white, // foreground
),
);
}
Widget nameTextField() {
return SelectableText(
"Edit Name",
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 16,
fontStyle: FontStyle.normal,
fontFamily: 'Poppins-Black',
fontWeight: FontWeight.w500,
),
);
}
Widget bioTextField() {
return SelectableText(
"Edit Bio (Max: 100)",
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 16,
fontStyle: FontStyle.normal,
fontFamily: 'Poppins-Black',
fontWeight: FontWeight.w500,
),
);
}
Widget changeNameTile() {
if (count == 1) {
print('updating text');
_changeNameController.text = _user.name;
}
return ExpansionTile(
title: Text(
'Name',
style: TextStyle(
fontSize: (_sizes.isDesktop()) ? 16 : 12,
fontWeight: FontWeight.w500,
color: Colors.black),
textAlign: TextAlign.left,
),
children: [
nameTextField(),
Container(
margin: EdgeInsets.all(20),
child: Theme(
data: ThemeData(
primaryColor: Colors.transparent,
hintColor: Colors.transparent),
child: TextField(
maxLength: 50,
controller: _changeNameController,
decoration: InputDecoration(
counterText: '',
contentPadding: EdgeInsets.all(20.0),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Theme.of(context).errorColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Theme.of(context).primaryColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
border: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
),
),
),
)
],
);
}
Widget changeBioTile() {
if (count == 1) {
_changeBioController.text = _user.bio;
}
return ExpansionTile(
title: Text(
'Bio',
style: TextStyle(
fontSize: (_sizes.isDesktop()) ? 16 : 12,
fontWeight: FontWeight.w500,
color: Colors.black),
textAlign: TextAlign.left,
),
children: [
bioTextField(),
Container(
margin: EdgeInsets.all(20),
child: Theme(
data: ThemeData(
primaryColor: Colors.transparent,
hintColor: Colors.transparent),
child: TextField(
maxLength: 100,
controller: _changeBioController,
decoration: InputDecoration(
counterText: '',
contentPadding: EdgeInsets.all(20.0),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Theme.of(context).errorColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Theme.of(context).primaryColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
border: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
),
),
),
)
],
);
}
Widget changeSocialMediaTile() {
if (count == 1) {
_changeFacebookController.text = _user.socialMedia.Facebook;
_changeTwitterController.text = _user.socialMedia.Twitter;
_changeInstagramController.text = _user.socialMedia.Instagram;
}
return ExpansionTile(
title: Text(
'Social Media',
style: TextStyle(
fontSize: (_sizes.isDesktop()) ? 16 : 12,
fontWeight: FontWeight.w500,
color: Colors.black),
textAlign: TextAlign.left,
),
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
MyFlutterApp.facebook,
color: Colors.blue,
size: 30,
),
SelectableText("www.Facebook.com/"),
Container(
margin: EdgeInsets.all(5),
width: 200,
child: Theme(
data: ThemeData(
primaryColor: Colors.transparent,
hintColor: Colors.transparent),
child: TextField(
maxLength: 100,
controller: _changeFacebookController,
decoration: InputDecoration(
counterText: '',
contentPadding: EdgeInsets.all(5.0),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
errorBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).errorColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).primaryColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
border: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
),
),
),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
MyFlutterApp.twitter,
color: Colors.lightBlue,
size: 30,
),
SelectableText("www.Twitter.com/ "),
Container(
margin: EdgeInsets.all(5),
width: 200,
child: Theme(
data: ThemeData(
primaryColor: Colors.transparent,
hintColor: Colors.transparent),
child: TextField(
maxLength: 100,
controller: _changeTwitterController,
decoration: InputDecoration(
counterText: '',
contentPadding: EdgeInsets.all(5.0),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
errorBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).errorColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).primaryColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
border: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
),
),
),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(MyFlutterApp.instagram, color: Colors.black, size: 30.0),
SelectableText("www.Instagram.com/"),
Container(
margin: EdgeInsets.all(5),
width: 200,
child: Theme(
data: ThemeData(
primaryColor: Colors.transparent,
hintColor: Colors.transparent),
child: TextField(
maxLength: 100,
controller: _changeInstagramController,
decoration: InputDecoration(
counterText: '',
contentPadding: EdgeInsets.all(5.0),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
errorBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).errorColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).primaryColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
border: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
),
),
),
)
],
)
],
);
}
Widget changeNotificationTile() {
return ExpansionTile(
title: Text(
'Notifications',
style: TextStyle(
fontSize: (_sizes.isDesktop()) ? 16 : 12,
fontWeight: FontWeight.w500,
color: Colors.black),
textAlign: TextAlign.left,
),
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.phone_iphone),
Container(
margin: EdgeInsets.all(5),
width: 200,
child: Theme(
data: ThemeData(
primaryColor: Colors.transparent,
hintColor: Colors.transparent),
child: TextField(
maxLength: 100,
controller: _changeNumberController,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
decoration: InputDecoration(
counterText: '',
contentPadding: EdgeInsets.all(5.0),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
errorBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).errorColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).primaryColor),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
border: OutlineInputBorder(
borderSide: BorderSide(),
borderRadius: BorderRadius.all(Radius.circular(
10) // <--- border radius here
),
),
),
),
),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 275,
child: emailTile(
checkBoxValueNotifier: email,
),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 275,
child: phoneTile(
checkBoxValueNotifier: phone,
),
)
],
)
],
);
}
}
class emailTile extends StatefulWidget {
@override
final CheckBoxValueNotifier checkBoxValueNotifier;
const emailTile({Key key, this.checkBoxValueNotifier}) : super(key: key);
_emailTileState createState() => _emailTileState();
}
class _emailTileState extends State<emailTile> {
@override
Widget build(BuildContext context) {
return CheckboxListTile(
value: widget.checkBoxValueNotifier.check.value,
secondary: Icon(
Icons.email_rounded,
color: Colors.black,
size: 30,
),
title: SelectableText("Email Notifications"),
onChanged: (newValue) {
setState(() {
widget.checkBoxValueNotifier.check.value =
!widget.checkBoxValueNotifier.check.value;
});
},
controlAffinity:
ListTileControlAffinity.trailing, // <-- leading Checkbox
);
}
}
class phoneTile extends StatefulWidget {
@override
final CheckBoxValueNotifier checkBoxValueNotifier;
const phoneTile({Key key, this.checkBoxValueNotifier}) : super(key: key);
_phoneTileState createState() => _phoneTileState();
}
class _phoneTileState extends State<phoneTile> {
@override
Widget build(BuildContext context) {
return CheckboxListTile(
value: widget.checkBoxValueNotifier.check.value,
secondary: Icon(
Icons.phone_iphone,
color: Colors.black,
size: 30,
),
title: SelectableText("Phone Notifications"),
onChanged: (newValue) {
setState(() {
widget.checkBoxValueNotifier.check.value =
!widget.checkBoxValueNotifier.check.value;
});
},
controlAffinity:
ListTileControlAffinity.trailing, // <-- leading Checkbox
);
}
}
class CheckBoxValueNotifier {
ValueNotifier check;
CheckBoxValueNotifier(bool init) {
check = ValueNotifier(init);
}
void toggleNotifier() {
check.value = !check.value;
}
}
Upvotes: 4
Views: 5189
Reputation: 1
This may not be to everyone's taste, but after trying several other ways, I decided to keep it simple. I have a small file of Global variables, for things like 'today's date' etc. I added a new variable :-
bool dataUpdated = false;
I set this variable to true whenever the data is updated, then in my display page I added :-
@override
Widget build(BuildContext context) {
if (dataUpdated == true) { // check for new data
dataUpdated = false;
controller.getData(); // data has been updated, go get it
}
return Scaffold(
This works fine now. By the way I am using Getx for my state management.
Upvotes: 0
Reputation: 28010
In both the old:
and new:
examples, the calls to getFuture()
are done inside the build()
method.
This is not the correct place for any type of long-running call. build()
should generally only contain UI display code since the build
method of widgets can theoretically happen 60 times per second (say if you were using an animation within the widget).
I'm guessing that each time your widget rebuilds (due to keyboard showing or other changes requiring a rebuild), your future data call happens again, which triggers a rebuild of your FutureBuilder
.
I'd suggest switching from a Stateless
widget to a StatefulWidget
and place the getFuture()
call into the initState()
method of the StatefulWidget
where it will run only once when the StatefulWidget
is instantiated, but will not run again on its rebuilds (which may happen when your keyboard shows/hides, etc.).
Here's some example code of how this could work:
*(btw, I didn't see rebuilds of either the Stateful
nor Stateless
versions of the below code when the keyboard was shown/hid. Not sure why.)
import 'package:flutter/material.dart';
class StatefulRebuildPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('Base Stateless Page widget re/built');
return Scaffold(
appBar: AppBar(
title: Text('Stateful Rebuild Page'),
),
body: FormPageStateful(), // ← swap between these two to test
//body: FormPageStateless(), // ← swap between these two to test
);
}
}
/// StateLESS version of FormPage
/// ////////////////////////////////////
class FormPageStateless extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('FormPageStateless re/built');
return MyFutureBuilderWidget(FakeFutureDataSource.getData());
}
}
/// StateFUL version of FormPage
/// ////////////////////////////////////
class FormPageStateful extends StatefulWidget {
@override
_FormPageStatefulState createState() => _FormPageStatefulState();
}
class _FormPageStatefulState extends State<FormPageStateful> {
Future<String> _nameData = Future.value('loading data...');
@override
void initState() {
super.initState();
_nameData = FakeFutureDataSource.getData();
/// Long data call happens ↑ here ↑ only once
}
@override
Widget build(BuildContext context) {
print('FormPage re/built');
return MyFutureBuilderWidget(_nameData); // ← supply the future
}
}
class MyFutureBuilderWidget extends StatelessWidget {
final Future<String> _nameData;
MyFutureBuilderWidget(this._nameData);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 25),
child: FutureBuilder<String>(
future: _nameData, // ← when future returns, ↓ will get rebuilt with future data
builder: (context, snapshot) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_nameField(snapshot.hasData ? snapshot.data : 'loading...')
],
);
},
),
);
}
Widget _nameField(String name) {
return TextFormField( // ← is a stateful widget underneath
key: ValueKey(name), // ← key used for framework to know this widget has changed & needs rebuilding
decoration: InputDecoration(
labelText: 'Name',
),
initialValue: name,
);
}
}
class FakeFutureDataSource {
static Future<String> getData() async {
return await Future.delayed(Duration(seconds: 2), () {
print('_fakeGetFuture() complete. Returning data...');
return 'Billy';
});
}
}
Upvotes: 3