flutter - Avoiding whole widget rebuild - flutter

Trying a 'like button' within a Future builder with many other widgets as below ,
onPressed: () {
if (aleadyLiked.length > 0) {
unlike(profileId);
} else {
like(profileId);
}
setState(() {});
},
And this is how my future builder starts,
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getProfile(profileId),
builder: (context, snapshot) {
=======Other widgets here======
}
Issue is onPressed of the like icon-button I am doing the setState() which is causing the whole Future builder to reload , Is there a way just to update the Like Button and the Like count , I was thinking to use some client side counter logic which callbacks to actual DB updates .Please help.
Loading Profile part on initState() can be achieved, but how to handle updating and reflecting 'Likes' , can that Like-button region alone be reloaded ?

You should not get this User Profile like this, but what you can do rather, you can get the user profile inside the initState, and before data is not loaded you can show either loader of something.
Something like this...
User _user;
Future<User> getUserProfile(profileId) async{
///Todo : Implementation of get User Profile
}
#override
void initState() {
// TODO: implement initState
super.initState();
getUserProfile("userId").then((user){
setState(() {
_user =user;
});
})
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: this._user == null ? CircularProgressIndicator() : UserWidget(),
);
}

So this is how I finally achieved it ,
void initState() {
getProfile(profileId).then((user){
setState(() {
_profile =user;
_counter =_profile.profilelikes.length;
_isAlreadyLiked=_profile.allreadyliked.length > 0;
});
});
super.initState();
}
And OnPressed() is
onPressed: () {
if (_isAlreadyLiked) {
unlike(profileId);
setState(() {
_counter--;
_isAlreadyLiked=false;
});
} else {
like(profileId);
setState(() {
_counter++;
_isAlreadyLiked=true;
});
}
},
Downside : The likes by other users will reflect on Wiget reload only, which is fine for me , for now.

Related

In flutter I need to hide button after 1 mins

I need to hide button after 1 mins but while reloading and again restarting application it will not show again once it hide it will not repeated
Use shared_preferences (or any persist memory) to persist the state of the button over restarts.
See Store key-value data on disk for details.
You need to use db to handle restarting application case.
You can use shared_preferences to store a flag about visibility.
class _GState extends State<G> {
static const String _buttonVisibilityKey = "DoneWIthButtonX";
bool showButton = false;
#override
void initState() {
super.initState();
_buttonActionChecker().then((value) async {
// return false if button never showed up, activate the timer to hide
print(value);
if (!value) {
showButton = true;
setState(() {});
Future.delayed(Duration(minutes: 1)).then((value) async {
showButton = false;
SharedPreferences.getInstance()
.then((value) => value.setBool(_buttonVisibilityKey, true));
setState(() {});
});
}
});
}
#override
void dispose() {
super.dispose();
}
/// is the button have been shown already return true
Future<bool> _buttonActionChecker() async {
return SharedPreferences.getInstance().then((value) {
return value.getBool(_buttonVisibilityKey) ?? false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: showButton
? FloatingActionButton(
onPressed: () {
setState(() {});
},
)
: null,
);
}
}

StreamBuilder / ChangeNotifierProvider- setState() or markNeedsBuild() called during build

Streambuilder, ChangeNotifier and Consumer cannot figure out how to use correctly. Flutter
I've tried and tried and tried, I've searched a lot but I cannot figure this out:
I'm using a Streambuilder this should update a ChangeNotifier that should trigger rebuild in my Consumer widget. Supposedly...
but even if I call the provider with the (listen: false) option I've got this error
The following assertion was thrown while dispatching notifications for
HealthCheckDataNotifier: setState() or markNeedsBuild() called during
build. the widget which was currently being built when the offending call was made was:
StreamBuilder<List>
Important: I cannot create the stream sooner because I need to collect other informations before reading firebase, see (userMember: userMember)
Widget build(BuildContext context) {
return MultiProvider(
providers: [
/// I have other provider...
ChangeNotifierProvider<HealthCheckDataNotifier>(create: (context) => HealthCheckDataNotifier())
],
child: MaterialApp(...
then my Change notifier look like this
class HealthCheckDataNotifier extends ChangeNotifier {
HealthCheckData healthCheckData = HealthCheckData(
nonCrewMember: false,
dateTime: DateTime.now(),
cleared: false,
);
void upDate(HealthCheckData _healthCheckData) {
healthCheckData = _healthCheckData;
notifyListeners();
}
}
then the Streambuilder
return StreamBuilder<List<HealthCheckData>>(
stream: HeathCheckService(userMember: userMember).healthCheckData,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData) {
if (snapshot.data!.isNotEmpty) {
healthCheckData = snapshot.data?.first;
}
if (healthCheckData != null) {
timeDifference = healthCheckData!.dateTime.difference(DateTime.now()).inHours;
_cleared = healthCheckData!.cleared;
if (timeDifference < -12) {
healthCheckData!.cleared = false;
_cleared = false;
}
///The problem is here but don't know where to put this or how should be done
Provider.of<HealthCheckDataNotifier>(context, listen: false).upDate(healthCheckData!);
}
}
return Builder(builder: (context) {
return Provider<HealthCheckData?>.value(
value: healthCheckData,
builder: (BuildContext context, _) {
return const HealthButton();
},
);
});
} else {
return const Text('checking health'); //Scaffold(body: Loading(message: 'checking...'));
}
});
and finally the Consumer (note: the consumer is on another Route)
return Consumer<HealthCheckDataNotifier>(
builder: (context, hN, _) {
if (hN.healthCheckData.cleared) {
_cleared = true;
return Container(
color: _cleared ? Colors.green : Colors.amber[900],
Hope is enough clear,
Thank you so very much for your time!
it is not possible to setState(or anything that trigger rerender) in the builder callback
just like you don't setState in React render
const A =()=>{
const [state, setState] = useState([])
return (
<div>
{setState([])}
<p>will not work</p>
</div>
)
}
it will not work for obvious reason, render --> setState --> render --> setState --> (infinite loop)
so the solution is similar to how we do it in React, move them to useEffect
(example using firebase onAuthChange)
class _MyAppState extends Stateful<MyApp> {
StreamSubscription<User?>? _userStream;
var _waiting = true;
User? _user;
#override
void initState() {
super.initState();
_userStream = FirebaseAuth.instance.authStateChanges().listen((user) async {
setState(() {
_waiting = false;
_user = user;
});
}, onError: (error) {
setState(() {
_waiting = false;
});
});
}
#override
void dispose() {
super.dispose();
_userStream?.cancel();
}
#override
Widget build(context) {
return Container()
}
}

Data for Flutter Page not loading when routing via MaterialPageRoute, but Hot Reloading loads the data correctly?

I'm building a Flutter app, and have a page with a table that is populated with data. I load the data like so:
class _AccountMenuState extends State<AccountMenu> { {
List<Account> accounts;
Future<List<Account>> getAccounts() async {
final response = await http.get('http://localhost:5000/accounts/' + globals.userId);
return jsonDecode(response);
}
setAccounts() async {
accounts = await getAccounts();
}
#override
void initState() {
setAccounts();
super.initState();
}
}
This works as expected when hot reloading the page, but when I route to this page via MaterialPageRoute,
like so: Navigator.push(context, MaterialPageRoute(builder: (context) => AccountMenu()));
then the data is not there.
What am I missing? I thought initState() gets called whenever a page loads?
You cannot do setState inside initState directly but you can wrap the initialization inside a PostFrameCallback to make sure that the initState lifecycle of the Widget is done.
class _AccountMenuState extends State<AccountMenu> { {
List<Account> accounts;
Future<List<Account>> getAccounts() async {
final response = await http.get('http://localhost:5000/accounts/' + globals.userId);
return jsonDecode(response);
}
setAccounts() async {
accounts = await getAccounts();
setState(() {})
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) => setAccounts());
super.initState();
}
}
initState() will not wait for setAccounts() to finish execution. In the method setAccounts() call setState after loading data.
setAccounts() async {
accounts = await getAccounts();
setState((){});
}
initState does not await. It only loads functions before the widget builder but it does not await.
you need to await loading widgets with data until accounts.length is not empty.
Show loading widget while data still loads or use FutureBuilder
List<Account> accounts;
#override
void initState() {
setAccounts();
super.initState();
}
#override
Widget build(BuildContext context) {
accounts.length > 0 ? SHOW_DATA_HERE : LOADING_WIDGET_HERE
}

Flutter setState not updating child element

I have an InkWell which uses onTap to perform some actions. When the button is tapped, I like an indicator to be shown (in case the action is long-running). However, the setState in the InkWell does not trigger its children to be re-rendered. The code is as follows:
class PrimaryButtonState extends State<PrimaryButton> {
bool _apiCall;
Widget getWidget() {
if(_apiCall) {
return new CircularProgressIndicator();
} else {
return Text(
widget.label,
);
}
}
#override
Widget build(BuildContext context) {
final List<Color> colors = //omitted
return InkWell(
child: Container(
decoration: // omitted
child: getWidget(), // not updated when _apiCall changes !!!!!
),
onTap: () {
setState(() {
_apiCall = true;
});
widget.onTab(context);
setState(() {
_apiCall = false;
});
}
);
}
}
How can I solve this that getWidget returns the correct widget dependent on _apiCall?
EDIT:
The widget.onTap contains the following:
void performLogin(BuildContext context) {
final String userName = _userName.text.trim();
final String password = _password.text.trim();
UserService.get().loginUser(userName, password).then((val) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => MainLayout()));
}).catchError((e) {
// omitted
});
}
it is passed with the widget:
class PrimaryButton extends StatefulWidget {
final bool isPrimary;
final String label;
final Function(BuildContext context) onTab;
PrimaryButton(this.label, this.isPrimary, this.onTab);
#override
State<StatefulWidget> createState() => PrimaryButtonState();
}
My main concern is, that the given onTap method should not know it is "bound" to a UI widget and therefore should not setState. Also, as this is a general button implementation I like it to be interchangeable (therefore, onTap is not hardcoded)
It looks like your problem is because you are calling setState() twice in your onTap() function. Since onTap() is not an async function it will set _apiCall = true in the first setState, then immediately run widget.onTab(context) and then immediately perform the second setState() to set _apiCall = false so you never see the loading widget.
To fix this you will need to make your onTab function an async function and await for a value in your onTap function for your InkWell:
onTap: () async {
setState(() {
_apiCall = true;
});
await widget.onTab(context);
setState(() {
_apiCall = false;
});
}
This will also let you use the results of your onTab function to show errors or other functionality if needed.
If you are unsure how to use async functions and futures here is a good guide on it that goes over this exact kind of use case.

setState() called after dispose()

When I click the raised button, the timepicker is showing up. Now, if I wait 5 seconds, for example, and then confirm the time, this error will occur:
setState() called after dispose()
I literally see in the console how flutter is updating the parent widgets, but why? I don't do anything - I just wait 5 seconds?!
The example below will work in a normal project, however in my project which is quite more complex it won't work because Flutter is updating the states while I am waiting... What am I doing wrong? Does anyone have a guess at what it could be that Flutter is updating randomly in my more complex project and not in a simple project?
[UPDATE]
I took a second look at it and found out it is updating from the level on where my TabBar and TabBarView are.
Could it have to do something with the "with TickerProviderStateMixin" which I need for the TabBarView? Could it be that it causes the app to refresh regularly and randomly?
class DateTimeButton extends State<DateTimeButtonWidget> {
DateTime selectedDate = new DateTime.now();
Future initTimePicker() async {
final TimeOfDay picked = await showTimePicker(
context: context,
initialTime: new TimeOfDay(hour: selectedDate.hour, minute: selectedDate.minute),
);
if (picked != null) {
setState(() {
selectedDate = new DateTime(selectedDate.year, selectedDate.month, selectedDate.day, picked.hour, picked.minute);
});
}
}
#override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text("${selectedDate.hour} ${selectedDate.minute}"),
onPressed: () {
initTimePicker();
}
);
}
}
Just check boolean property mounted of the state class of your widget before calling setState().
if (this.mounted) {
setState(() {
// Your state change code goes here
});
}
Or even more clean approach
Override setState method in your StatelfulWidget class.
class DateTimeButton extends StatefulWidget {
#override
void setState(fn) {
if(mounted) {
super.setState(fn);
}
}
}
If it is an expected behavior that the Future completes when the widget already got disposed you can use
if (mounted) {
setState(() {
selectedDate = new DateTime(selectedDate.year, selectedDate.month, selectedDate.day, picked.hour, picked.minute);
});
}
Just write one line before setState()
if (!mounted) return;
and then
setState(() {
//Your code
});
I had the same problem and i solved changing the super constructor call order on initState():
Wrong code:
#override
void initState() {
foo_bar(); // call setState();
super.initState(); // then foo_bar()
}
Right code:
#override
void initState() {
super.initState();
foo_bar(); // first call super constructor then foo_bar that contains setState() call
}
To prevent the error from occurring, one can make use of the mounted property of the State class to ensure that a widget is mounted before settings its state:
// First Update data
if (!mounted) {
return;
}
setState(() { }
Try this
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text("${selectedDate.hour} ${selectedDate.minute}"),
onPressed: () async {
await initTimePicker();
}
);
}
class MountedState<T extends StatefulWidget> extends State<T> {
#override
Widget build(BuildContext context) {
return null;
}
#override
void setState(VoidCallback fn) {
if (mounted) {
super.setState(fn);
}
}
}
Example
To prevent the error,Instead of using State use MountedState
class ExampleStatefulWidget extends StatefulWidget {
const ExampleStatefulWidget({Key key}) : super(key: key);
#override
_ExampleStatefulWidgetState createState() => _ExampleStatefulWidgetState();
}
class _ExampleStatefulWidgetState extends MountedState<ExampleStatefulWidget> {
....
}
I had this error when I mistakenly called super.initState before the variable. Check this:
#override
void initState() {
super.initState();
bloc = MainBloc();
}
Should be fixed as
#override
void initState() {
bloc = MainBloc();
super.initState();
}
The problem could occur when you have long asynchronous operation in stateful widget that could be closed/disposed before the operation finished.
Futures in Dart are not preemptive, so the only way is to check if a widget mounted before calling setState.
If you have a lot of widgets with asynchrony, adding ton of if (mounted) checks is tedious and an extension method might be useful
extension FlutterStateExt<T extends StatefulWidget> on State<T> {
void setStateIfMounted(VoidCallback fn) {
if (mounted) {
// ignore: invalid_use_of_protected_member
setState(fn);
}
}
}