I'm still new to DART but I thought this would be easy to accomplish.
I want to check if a List from a future contains an integer. My return type is list dynamic but when I do the check it's telling me it's a Future.
The function that returns a List dynamic
I want to change the color of an icon
Icon(
Icons.favorite,
color: checkIfLiked(), // Change color here using a future.
size: buttonSize,
);
Future<List> userLikes() async {
GetUserLikes userLikesClicks = GetUserLikes();
Future<List> userLikes = userLikesClicks.getLikes();
List userList = await userLikes;
return userList; // returns List<dynamic>
}
I want to check of the Array contains an integer and if it does to return a color.
Future<Color> checkIfLiked() async{
Color color;
List resultUsersLikes = await userLikes();
if (resultUsersLikes.contains(widget.id)) {
color = Colors.blue;
} else {
color = Colors.grey;
}
return color;
}
I have to return a Color, and I am already using a FutureBuilder on the widget.
I need the color to change the color of an icon.
This is the icon
Icon(
Icons.favorite,
color: checkIfLiked(),
size: buttonSize,
);
How can I add an async here to await the future?
as I see the userLikes() is a Future method, which means when you do this:
if (userLikes().contains(widget.id)) {
// ....
you're trying to call contains() which belongs to the List type on a Future which didn't resolve yet to return the userList from it.
what you need to do is to wait until it gets it and resolves, then use its result ( which will be a List ), like this:
Future<Color> checkIfLiked() async { // add async
List resultUsersLikes = await userLikes(); // then await for this
Color color;
if (resultUsersLikes.contains(widget.id)) { // then use it here
color = Colors.blue;
} else {
color = Colors.grey;
}
return color;
}
Now when you call checkIfLiked(), first it will get the response from the userLikes() then store it in resultUsersLikes, then use it in the rest of your code :
and when you wanna use checkIfLiked() in your other codes as the onPressed of some button, you will need to await for it to get its final response, like this:
onPressed: () async {
Color colorResult = await checkIfLiked();
}
You can set that color to the Icon, by calling this method on initState, then once it completes, update the state with the Color, first declare a Color variable in your State class:
Color? resultColor;
then set it to your Icon:
Icon(
Icons.favorite,
color: resultColor,
size: buttonSize,
);
then in your initState:
#override
void initState() {
checkIfLiked().then((colorResponse) {
setState(() {
resultColor = colorResponse
});
});
}
now once the widget will be set inside the widget tree, the checkIfLiked() will get called, when it finishes, the then will be executed, which updates the state of resultColor with the color got from the method.
Like your error message states, The method 'contains' isn't defined for the type 'Future'. You should first await the future to resolve, and then you can use the List it returns. First of all, your checkIfLiked() function must return Future and inside it you should await userLikes() function.
Future<Color> checkIfLiked() async {
Color color;
if ((await userLikes()).contains(widget.id)) {
color = Colors.blue;
} else {
color = Colors.grey;
}
return color;
}
Since you don't want checkIfLiked() to return a Future<Color>, you can try this
First declare a variable color and give it a default value.
Color color = Colors.grey;
Then call the below method checkIfLiked() when you need to.
It will fetch the list of likes and finds the required color based on your logic.
Then it uses setState to change the variable color's value from default value grey to required value.
Color change will happen automatically in all your widgets, where color is referenced.
Future<void> checkIfLiked() async {
List userLikesList = await userLikes();
setState(() {
if (userLikesList.contains(widget.id)) {
color = Colors.blue;
} else {
color = Colors.grey;
}
});
}
Related
I'm using Shared preferences to save the user's name and login state even after closing the app. the Shared Preference I used in main.dart is fine because I used it in the main function and made it async, but when I'm trying to use it in other classes, I see a dark red screen for less than a second before loading the page and it makes my app so ugly. what can I do to fix it?
Here's my code:
late bool _isEditingText;
TextEditingController _editingController = TextEditingController();
late String initialText ;
SharedPreferences? _prefs;
#override
void initState(){
super.initState();
initializePreference().whenComplete((){
setState(() {});
});
}
Future<void> initializePreference() async{
_prefs = await SharedPreferences.getInstance();
String? name = _prefs?.getString('name');
if (name == null) {
_isEditingText = true;
initialText = 'Enter ur name';
} else {
_isEditingText = false;
initialText = name;
}
}
Update:
sorry for not including my exact error... here it is :
LateInitializationError: Field '_isEditingText#37486951' has not been initialized.
I think you are performing setState before all widgets are get initialised. So for that you can update initState as below:
void initState(){
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) async {
initializePreference().whenComplete((){
setState(() {});
});
});
}
If it's not causing issue, than you have to show loading indicator. Like initially when there is no data indicator will be there and once you get data from SharedPreference in setState - you have to remove indicator and load your data.
You can use CircularProgressIndicator for that.
initialise your boolean variable,
var isDataLoad = false;
once you get data in whenComplete(), set it as true and based on this variable you can declare your widgets.
isDataLoad ? Container( // Your widgets where you use "initialText" ) : CircularProgressIndicator();
I have a method in my cubit that captures a widget as an image and I am testing this method. Long story short this method calls _captureFromWidget (see below, the code is copied from package screenshot), which accepts the Widget and returns a Uint8List. (I am not testing that the package works correctly, I am testing that my method and its parameters work correctly).
The problem is, that in real app the widget is captured correctly, but in the test, the font of all the Text widgets is not rendered correctly, and boxes are shown instead of letters.
I know the reason of this, see here, and here.
I tried loading the font as they suggested:
in pubspec.yaml:
assets:
- assets/fonts/
and I have in the assets/fonts folder my font: AbrilFatface-Regular.ttf
in my test:
final Future<ByteData> abrilFatFaceFontData = rootBundle.load('assets/fonts/AbrilFatface-Regular.ttf');
final FontLoader fontLoader = FontLoader('AbrilFatFace-Regular')..addFont(abrilFatFaceFontData);
await fontLoader.load();
Text text = ...; // text that uses custom font from above
cubit.captureWidget(...); // will invoke _captureFromWidget
but still, boxes are shown instead of letters in the captured image:
this is the result of calling the same methods with the same arguments from the real app:
So how to provide the font to the test correctly?
Here is the code that captures the widget:
/// [context] parameter is used to Inherit App Theme and MediaQuery data.
Future<Uint8List> _captureFromWidget(
widgets.Widget widget, {
required Duration delay,
double? pixelRatio,
widgets.BuildContext? context,
}) async {
// Retry counter
int retryCounter = 3;
bool isDirty = false;
widgets.Widget child = widget;
if (context != null) {
// Inherit Theme and MediaQuery of app
child = widgets.InheritedTheme.captureAll(
context,
widgets.MediaQuery(data: widgets.MediaQuery.of(context), child: child),
);
}
final RenderRepaintBoundary repaintBoundary = RenderRepaintBoundary();
Size logicalSize = ui.window.physicalSize / ui.window.devicePixelRatio;
Size imageSize = ui.window.physicalSize;
assert(logicalSize.aspectRatio.toPrecision(5) == imageSize.aspectRatio.toPrecision(5));
final RenderView renderView = RenderView(
window: ui.window,
child: RenderPositionedBox(alignment: Alignment.center, child: repaintBoundary),
configuration: ViewConfiguration(
size: logicalSize,
devicePixelRatio: pixelRatio ?? 1.0,
),
);
final PipelineOwner pipelineOwner = PipelineOwner();
final widgets.BuildOwner buildOwner = widgets.BuildOwner(
focusManager: widgets.FocusManager(),
onBuildScheduled: () {
///
///current render is dirty, mark it.
///
isDirty = true;
});
pipelineOwner.rootNode = renderView;
renderView.prepareInitialFrame();
final widgets.RenderObjectToWidgetElement<RenderBox> rootElement = widgets.RenderObjectToWidgetAdapter<RenderBox>(
container: repaintBoundary,
child: widgets.Directionality(
textDirection: TextDirection.ltr,
child: child,
)).attachToRenderTree(
buildOwner,
);
// Render Widget
buildOwner.buildScope(
rootElement,
);
buildOwner.finalizeTree();
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
ui.Image? image;
do {
// Reset the dirty flag
isDirty = false;
image = await repaintBoundary.toImage(pixelRatio: pixelRatio ?? (imageSize.width / logicalSize.width));
// This delay should increase with Widget tree Size
await Future.delayed(delay);
// Check does this require rebuild
if (isDirty) {
// Previous capture has been updated, re-render again.
buildOwner.buildScope(
rootElement,
);
buildOwner.finalizeTree();
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
}
retryCounter--;
//retry until capture is successful
} while (isDirty && retryCounter >= 0);
final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
return byteData!.buffer.asUint8List();
}
Turns out I was doing everything correctly except one thing.
The line final FontLoader fontLoader = FontLoader('AbrilFatFace-Regular')..addFont(abrilFatFaceFontData); will determine the fontFamily name that must be used in the TextStyle of the Text element.
and since I was using AbrilFatFace as fontFamily, the test was not working.
So the solution is to use in the fontFamily, the same name passed to the FontLoader, since thats the name the font will be identified with.
I am trying to update a Boolean value declared in build method using a function which is being called on onPressed event.
Widget build(BuildContext context) {
DatabaseFunctions databaseFunctions = new DatabaseFunctions();
bool isLiked = false;
updateLikes() async{
if(!isLiked){
isLiked = true;
await databaseFunctions.updateLikes(postId, likeCount+1);
}else{
await databaseFunctions.updateLikes(postId, likeCount-1);
isLiked = false;
}
}
return IconButton(
onPressed: (){
updateLikes();
print("like status: $isLiked");
},
icon: Icon(FontAwesomeIcons.heart),),
}
the function updateLikes is updating a collection in the database and trying to switch the isLiked value.
The database function:
databaseFunctions.updateLikes(String postId, int likeCount) async{
return await firestore.collection('posts')
.doc(postId).update({'likes': {'likeCount' : likeCount}});
}
The problem is the value of isLiked is not changing and the number of likes in the database keeps increasing on clicking the button.
Try moving these outside of the "build()" method:
the "updateLikes()" method
the " isLiked" variable
Also add "await" before calling the " updateLikes" method.
Let me know if these workout for you
defining variable inside build function is incorrect, you should move it outside the build method. this is due to everytime flutter rerender your widget this parameter will reinitialize in the build method. Also, use setState to change the state of the widget as follow:
bool isLiked = false;
updateLikes() async{
if(!isLiked){
isLiked = true;
await databaseFunctions.updateLikes(postId, likeCount+1);
}else{
await databaseFunctions.updateLikes(postId, likeCount-1);
isLiked = false;
}
setState((){});
}
Widget build(BuildContext context) {
DatabaseFunctions databaseFunctions = new DatabaseFunctions();
return IconButton(
onPressed: (){
updateLikes();
print("like status: $isLiked");
},
icon: Icon(FontAwesomeIcons.heart),),
);
I have a Card() widget which contains a ListTile() widget.
One of the ListTile() widget's properties is enabled. I would like to dynamically set the value of this enabled property by using the outcome of a Future<bool> which uses async and await. Is this possible?
Here is the Card() widget with the ListTile() in it
Card myCard = Card(
child: ListTile(
title: Text('This is my list tile in a card'),
enabled: needsToBeEnabled(1),
),
);
Here is my Future
Future<bool> cardNeedsToBeEnabled(int index) async {
bool thisWidgetIsRequired = await getAsynchronousData(index);
if (thisWidgetIsRequired == true) {
return true;
} else {
return false;
}
}
Other attempts
I have tried to use a Future Builder. This works well when I'm building a widget, but in this case I'm trying to set the widget's property; not build a widget itself.
You cannot do that for two reason:
enable does not accept Future<bool> but bool
you need to update the state after result is received (you need a StatefullWidget)
There are 1 million way to do what you want to do and one of this is FutureBuilder but if you want to not rebuild all widget you can use this flow (your main widget need to be Statefull):
create a local variable that contains your bool value, something like bool _enabled
on initState() method override you can launch the call that get asynchronous data and using the then() extension method you can provide the new state to your widget when the call will be completed.
Something like:
#override
void initState() {
super.initState();
getAsynchronousData(index).then((result) {
if (result == true) {
_enabled = true;
} else {
_enabled = false;
}
setState(() {});
});
}
assign the boolean var to the ListTile widget
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.