how to change the state inside FutureBuilder - flutter

i just need when the user open the screen the notification icon button change when he click, it's value is coming from shared preferences. the problem is the icon is never changed!
the initState code:
#override
void initState() {
super.initState();
_isActiveNotification = _notificationGetState();
}
_notificationGetState function is:
//getting notification on/off
Future<bool> _notificationGetState() async {
final SharedPreferences _prefs = await SharedPreferences.getInstance();
return _prefs.getBool('notification') ?? true;
}
_isActiveNotification variable is:
late Future<bool> _isActiveNotification;
the class of the notification icon button is:
class _NoificationActivationButton extends StatefulWidget {
_NoificationActivationButton();
#override
_NoificationActivationButtonState createState() =>
_NoificationActivationButtonState();
}
class _NoificationActivationButtonState
extends State<_NoificationActivationButton> {
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
//function haveing the return value
future: _isActiveNotification,
builder: (context, snapshot) {
bool data = snapshot.data!;
return IconButton(
icon: Icon(
data
? Icons.notifications_active_outlined
: Icons.notifications_off_outlined,
color: Colors.white,
size: 40,
),
onPressed: () {
setState(() {
data = !data;
});
},
);
});
}

just call setstate
onPressed: () {
data = !data;
// just call setstate((){});
},

Make data a global state.
NOTE: I'm only assuming that you will only call _notificationGetState once (in initState).
Sample...
class _NoificationActivationButtonState
extends State<_NoificationActivationButton> {
final bool _isOtherVersion = true;
late Future<bool> _isActiveNotification;
bool? _data;
#override
void initState() {
super.initState();
_isActiveNotification = _notificationGetState();
}
//getting notification on/off
Future<bool> _notificationGetState() async {
final SharedPreferences _prefs = await SharedPreferences.getInstance();
return _isOtherVersion
? _prefs.getBool('notification') ?? true
: _data = _prefs.getBool('notification') ?? true;
}
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
//function haveing the return value
future: _isActiveNotification,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const CircularProgressIndicator();
}
if (_isOtherVersion && _data == null) {
_data = snapshot.data;
}
return IconButton(
icon: Icon(
_data!
? Icons.notifications_active_outlined
: Icons.notifications_off_outlined,
color: Colors.white,
size: 40,
),
onPressed: () => setState(() => _data = !_data!),
);
},
);
}
}

Related

Can not update variable value inside a void method in flutter

I'm trying to update a variable value inside a void method in flutter, tried using StatefulBuilder but the value does not get changed.
Here is my code:
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final user = FirebaseAuth.instance.currentUser;
String type = "";
void checkUser() async {
await FirebaseFirestore.instance
.collection('users')
.doc(user!.uid)
.get()
.then(
(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
var data = documentSnapshot.data();
var res = data as Map<String, dynamic>;
if (res["type"] == "Salarié") {
print('Salarié');
} else if (res["type"] == "Auto entrepreneur") {
print('Auto entrrepreneu');
} else {
showDialog<String>(
context: context,
builder: (_) => StatefulBuilder(
builder: (modalContext, modalSetState) => AlertDialog(
title: const Text('Choissisez votre type'),
content: const Text('Choisir votre type de user'),
actions: <Widget>[
TextButton(
onPressed: () {
setState(() {
type = "Salarié";
});
Navigator.pop(context, 'Cancel');
},
child: const Text('Salarié'),
),
TextButton(
onPressed: () {
setState(() {
type = "Auto entrepreneur";
});
Navigator.pop(context, 'OK');
},
child: const Text('Auto-entrepreneur'),
),
],
),
));
return FirebaseFirestore.instance
.collection("users")
.doc(user!.uid)
.update({
"type": type,
});
}
} else {}
},
);
}
#override
void initState() {
super.initState();
checkUser();
}
#override
Widget build(BuildContext context) {
return Scaffold(...);
It's all inside a StatefulWidget though, I'm not sure whether this is the correct way to do this because the value does not get changed! I appreciate your help.
edit: I'm calling this method inside the initState()
showDialog is not a synchronous operation and the code basically sends the dialog and immediately continues. to fix that, I had to add async and await, and it worked like a charm.

Flutter #3: I have some async problem in flutter

I have a piece of code to scan and read device information. I have printed the elements in the list in onScan function, however I don't know how to get that information and put it in a listview.
Can someone help me?
List<Data> listDevice = [];
Future<void> getData() async {
var apiEndpoint = TTAPI.shared;
await apiEndpoint.devideScan(((data) => onScan(data)));
}
Future<void> onScan(dynamic data) async {
var dataResponse = DataResponse.fromJson(data);
print(dataResponse.toJson());
List<dynamic> dt = jsonDecode(jsonEncode(dataResponse.data).toString());
dt.forEach((element) {
var item = Data.fromJson(element);
print(item.modelName);
listDevice.add(item);
});
var connectRequest = {
'serialNumber': 'DEVICE_SERIAL',
'modelName': 'DEVICE_MODEL',
'ipAddr': 'DEVICE_IP'
};
var apiEndpoint = TTAPI.shared;
await apiEndpoint.connectDevice(connectRequest);
}
Future<List<Data>> getList() async {
return listDevice;
}
You can see more of my code here: https://docs.google.com/document/d/1ntxaDpyNCLD1MyzJOTmZsrh7-Jfim8cm0Va86IQZGww/edit?usp=sharing
As for the current code structure, listDevice is populated inside Future. So you can call setState to update the UI after getting the list at the end of onScan.
Future<void> getData() async {
var apiEndpoint = TTAPI.shared;
await apiEndpoint.devideScan(((data) => onScan(data)));
setState((){});
}
But it would be great to use FutureBuilder and return list from getData.
Current question pattern example
class TextFW extends StatefulWidget {
const TextFW({super.key});
#override
State<TextFW> createState() => _TextFWState();
}
class _TextFWState extends State<TextFW> {
//for current question way
List<int> listDevice = [];
Future<void> getData() async {
await Future.delayed(Duration(seconds: 2));
/// others async method
listDevice = List.generate(10, (index) => index);
setState(() {}); //here or `getData().then()`
}
#override
void initState() {
super.initState();
getData();
// or this getData().then((value) => setState((){}));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: listDevice.length,
itemBuilder: (context, index) => ListTile(
title: Text("${listDevice[index]}"),
),
),
);
}
}
Using FutureBuilder
class TextFW extends StatefulWidget {
const TextFW({super.key});
#override
State<TextFW> createState() => _TextFWState();
}
class _TextFWState extends State<TextFW> {
/// method will provide data by scanning
Future<List<int>> getData() async {
await Future.delayed(Duration(seconds: 2));
return List.generate(10, (index) => index);
}
late final fututre = getData();
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<int>>(
future: fututre,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("${snapshot.error}");
}
if (snapshot.hasData) {
final listDevice = snapshot.data;
return ListView.builder(
itemCount: listDevice?.length,
itemBuilder: (context, index) => ListTile(
title: Text("${listDevice![index]}"),
),
);
}
return CircularProgressIndicator();
},
),
);
}
}

removeWhere() method does not remove the data

I am building a food recipe app where user can browse various recipes.
The functionality is that, when user hit delete button, the item will not be shown in listing. I navigated the the mealId to the previous screen, i.e. Listing screen through
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).pop(mealId);
},
child: const Icon(Icons.delete),
),
I receive the pop() value in backward widget like:
void selectMeal(BuildContext context) {
Navigator.of(context)
.pushNamed(MealsDetailsScreen.routeName, arguments: id)
.then((result) {
if (result != null) {
removeItem(result);
print(result); // it prints the expected id
}
});
}
And in the code attached fully, I wanted to remove the item details via mealId
void _removeMeal(String mealId) {
setState(() {
print("$mealId from didChangedDependancies"); //it also prints the expected id
displayedMeals.removeWhere((meal) => meal.id == mealId);
});
}
The code where I set the function to remove:
import 'package:flutter/material.dart';
import '../models/meals.dart';
import '../models/dummy_data.dart';
import '../widgets/meal_item.dart';
class CategoryMealaScreen extends StatefulWidget {
static const routeName = '/category-meals';
#override
State<CategoryMealaScreen> createState() => _CategoryMealaScreenState();
}
class _CategoryMealaScreenState extends State<CategoryMealaScreen> {
late String categoryTitle;
late List<Meal> displayedMeals;
var _loadedInitData = false;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
if (!_loadedInitData) {
final routeArgs =
ModalRoute.of(context)!.settings.arguments as Map<String, String>;
categoryTitle = routeArgs['title'].toString();
final categoryId = routeArgs['id'];
displayedMeals = dummyMeals.where((meal) {
return meal.categories.contains(categoryId);
}).toList();
_loadedInitData = true;
}
super.didChangeDependencies();
}
void _removeMeal(String mealId) {
setState(() {
print("$mealId from didChangedDependancies");
displayedMeals.removeWhere((meal) => meal.id == mealId);
});
}
#override
Widget build(BuildContext context) {
final routeArgs = // received data from widget CategoryItems()
ModalRoute.of(context)!.settings.arguments as Map<String, String>;
final categoryTitle = routeArgs['title'];
final categoryId = routeArgs['id'];
final displayedMeals = dummyMeals.where((meal) {
return meal.categories.contains(categoryId);
}).toList();
return Scaffold(
appBar: AppBar(
title: Text(categoryTitle.toString()),
),
body: ListView.builder(
itemCount: displayedMeals.length,
itemBuilder: (ctx, index) {
return MealItem(
id: displayedMeals[index].id,
title: displayedMeals[index].title,
imageUrl: displayedMeals[index].imageUrl,
complexity: displayedMeals[index].complexity,
affordability: displayedMeals[index].affordability,
duration: displayedMeals[index].duration,
removeItem: _removeMeal,
);
}),
);
}
}
No error shows on console.
I'll be vary happy if you guys help me out! Thanks a lot😎
Remove final displayedMeals inside your build method.
Use the displayedMeals variable outside your build method instead.

Flutter display pop-up once based on value

I am trying to display a pop-up dialog like (+20) based on the value coming from server. I have a variable name ageRestriction which is getting the value from server. And I want to display pop-up based on the value of this variable. (Eg: If ageRestriction has the value of "18" the pop-up will be displayed only once then later on if the value will change to "20" the pop-up will be displayed once again, so these values will be stored somewhere and the pop-up will not be displayed if the same value comes again)
I have tried to do it with shared preferences unfortunately it did not work:
// initializing shared pref
#override
void initState() async{
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
widget.ageRestriction = await prefs.getString("ageRestriction");
await prefs.setString("ageRestriction", widget.ageRestriction);
}
// displaying pop-up dialog
widget.ageRestriction.toString() == widget.ageRestriction ? null :
AwesomeDialog(
popContext: false,
context: context,
dialogType: DialogType.WARNING,
animType: AnimType.TOPSLIDE,
title: "${widget.ageRestriction} Warning",
desc: "We only sell this product to persons who are ${widget.ageRestriction} years old. Age will be verified upon delivery.",
btnOkText: "Continue",
btnOkOnPress: () async{
widget.onPressed();
Navigator.of(context).pop();
},
btnCancelOnPress: () {
Navigator.of(context).pop();
},
btnCancelText: S.current.cancel,
btnOkColor: Theme.of(context).accentColor,
btnCancelColor: Color(0xFF084457).withOpacity(0.9),
).show();
ageRestrict();
}
Store widget.ageRestriction in a variable in the state of the widget and then check in didUpdateWidget whether the value changed and if it did show the popup
Simple Demo App to show age restriction :
Future<String> getAgeRestrictionFromServer() async {
// write your own logic
await Future.delayed(Duration(seconds: 2));
return "22";
}
enum RequestState { LOADING, SUCCESS, ERROR }
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>{
// request status
RequestState requestState = RequestState.LOADING;
// disable multiple clickes
bool retryButtonEnabled = true;
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
getData();
});
}
showAgeRestrictionDialog(String age) {
return showDialog(
context: context,
child: AlertDialog(
title: Text('Age policy changed!'),
content: Text('New Age: $age'),
),
);
}
void changeRequestState(RequestState newRequestState) {
if (mounted) {
setState(() {
requestState = newRequestState;
});
}
}
Future<void> getData() async {
changeRequestState(RequestState.LOADING);
try {
// get age from server
var newAgeRestriction = await getAgeRestrictionFromServer();
// get age stored locally
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
var previousAgeRestriction =
sharedPreferences.getString('ageRestriction');
print('$previousAgeRestriction, $newAgeRestriction');
int.parse(newAgeRestriction);
if (mounted) {
// compare previous and new age
if (previousAgeRestriction != newAgeRestriction) {
// save new age
await sharedPreferences.setString(
'ageRestriction', newAgeRestriction);
// show dialog because age changed
showAgeRestrictionDialog(newAgeRestriction);
}
}
retryButtonEnabled = true;
changeRequestState(RequestState.SUCCESS);
} catch (e) {
print(e);
retryButtonEnabled = true;
changeRequestState(RequestState.ERROR);
}
}
#override
Widget build(BuildContext context) {
var child;
if (requestState == RequestState.LOADING) {
child = Center(
child: CircularProgressIndicator(),
);
} else if (requestState == RequestState.SUCCESS) {
child = Center(
child: Text('Got data from server!'),
);
} else {
child = Center(
child: FlatButton(
color: Colors.blue,
onPressed: retryButtonEnabled
? () {
setState(() {
retryButtonEnabled = false;
});
getData();
}
: null,
child: Text('Retry')),
);
}
return Scaffold(
appBar: AppBar(
title: Text('Demo App'),
),
body: child,
);
}
}
More info about state management here

Flutter : Prevent FutureBuilder always refresh every change screen

I have FutureBuilder to fetch User profil from API and code to fetch user like this :
Future<List<UserModel>> getUserByUsername({#required String username}) async {
try {
final response =
await _client.get("$_baseUrl/getUserByUsername?username=$username");
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson["status"] == "ok") {
List userList = responseJson['data'];
final result = userList
.map<UserModel>((json) => UserModel.fromJson(json))
.toList();
return result;
} else {
throw CustomError(responseJson['message']);
}
} catch (e) {
return Future.error(e.toString());
}
}
If you can see in above GIF, My FutureBuilder are inside BottomNavigationBar. Every i change the screen/page from BottomNavigationBar and come back to my FutureBuilder is always refresh !
How can i fixed it to only once to refresh ?
Home Screen
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
final username = Provider.of<SharedPreferencesFunction>(context).username;
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CardTime(),
FutureBuilder(
future: userApi.getUserByUsername(username: username),
builder: (BuildContext context,
AsyncSnapshot<List<UserModel>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(
child: Text(
snapshot.error.toString(),
),
);
} else {
final user = snapshot.data[0];
return CardProfil(
imageUrl: "${userApi.baseImageUrl}/${user.fotoUser}",
detailProfil: [
Text(
user.namaUser,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(user.idDevice),
],
);
}
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
],
),
);
}
}
Shared Preferences Function
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesFunction extends ChangeNotifier {
SharedPreferencesFunction() {
initialSharedPreferences();
getUsername();
}
String _username;
String get username => _username;
void initialSharedPreferences() {
getUsername();
}
Future updateUsername(String username) async {
SharedPreferences pref = await SharedPreferences.getInstance();
await pref.setString("username", username);
//! It's Important !!! After update / remove sharedpreferences , must called getUsername() to updated the value.
getUsername();
notifyListeners();
}
Future removeUsername() async {
SharedPreferences pref = await SharedPreferences.getInstance();
final result = await pref.remove("username");
//! It's Important !!! After update / remove sharedpreferences , must called getUsername() to updated the value.
getUsername();
print(result);
notifyListeners();
}
Future getUsername() async {
SharedPreferences pref = await SharedPreferences.getInstance();
final result = pref.getString("username");
_username = result;
notifyListeners();
}
}
final sharedpref = SharedPreferencesFunction();
Update Question
I already try Initialize FutureBuilder and use initState and didChangeDependencies . But new problem is , if i initialize inside initState my profil not rebuild because Provider listen=false.
If i using didChangeDependencies my FutureBuilder still refresh every i change screen.
Something wrong ?
Using initState
Using didChangeDependencies
Initialize the Future during initState or didChangeDependencies instead.
class _HomeScreenState extends State<HomeScreen> {
Future<List<UserModel>> user;
#override
void initState() {
super.initState();
// must use listen false here
final username = Provider.of<SharedPreferencesFunction>(context, listen: false).username;
user = userApi.getUserByUsername(username: username);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
final username = Provider.of<SharedPreferencesFunction>(context).username;
user = userApi.getUserByUsername(username: username);
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FutureBuilder(
future: user,
builder: (context, snapshot) {
// ...
},
),
],
),
);
}
}
I faced a similar case and use AutomaticKeepAliveClientMixin on each view / page / tab bar view / widget / child to keep the page not refreshing every time I go back and forth through the tab bar.
class YourClass extends StatefulWidget {
YourClass({
Key key
}): super(key key);
#override
_YourClassState createState() => _YourClassState();
}
// Must include AutomaticKeepAliveClientMixin
class _YourClassState extends State<YourClass> with AutomaticKeepAliveClientMixin {
Future resultGetData;
void getData() {
setState(() {
resultGetData = getDataFromAPI();
});
}
// Must include
#override
bool get wantKeepAlive => true;
#override
void initState() {
getData();
super.initState();
}
#override
Widget build(BuildContext context) {
super.build(context); // Must include
return FutureBuilder(
future: resultGetAllByUserIdMCId,
builder: (context, snapshot) {
// ...
// Some Code
// ...
}
);
}
}
If you want to refresh the data you could use RefreshIndicator that runs the getData() function. Put this code inside FutureBuilder. The key: PageStorageKey(widget.key) will keep the scroll in the exact same place where you left of.
return RefreshIndicator(
onRefresh: () async {
getData();
},
child: ListView.separated(
key: PageStorageKey(widget.key),
itemCount: data.length,
separatorBuilder: (BuildContext context, int index) {
return Divider(height: 0);
},
itemBuilder: (context, index) {
return ...;
},
),
);
Use IndexedStack as the parent of tabbar.
You have to put your Future Builder in a Stateful Widget then define a
late final Future myFuture;
then you have to initialize it in the initstate so the future will be executed only one time.