Flutter: Accessing Bloc data from outside a build method - flutter

Can I access the data in Bloc streams from outside a build method?
For example, in the build method, I am able to access the data using snapShot.data. Like this:
return StreamBuilder(
//initialData: Colors.blue,
stream: colorBloc.colorStream,
builder: (BuildContext context, snapShot) => Container(
width: menuButtonSize,
height: menuButtonSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: snapShot.data, //This returns the correct colour previously stored in this stream.
boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 15,
),
],
),
),
);
But for troubleshooting another Bloc instance that I am trying to get working, I would like to be able to print out the current value of snapShot.data somehow, so I can see what it is doing and if it is updating properly, because currently it isn't working.
Current snippet of non-working Bloc:
Widget customTheme() {
return StreamBuilder(
initialData: true,
stream: customToggleBloc.customToggleStream,
builder: (BuildContext context, snapShot) => GestureDetector(
onTap: (){
snapShot.data == true ? widget?._callback('custom') : widget?._callback('clearcustom'); //Section A
},
child: Container(
width: menuButtonSize + 8,
height: menuButtonSize + 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppState.brownTheme,
boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 25,
),
],
),
child: new IconTheme(
data: new IconThemeData(
color: Colors.white,
size: 35,
),
child: snapShot.data == true ? new Icon(Icons.add_photo_alternate) : new Icon(Icons.cancel), // Section B
),
),
),
);
}
So what I would like it to do is have a button that is in one of two states at all times. 1) It displays Icons.add_photo_alternate and allows you to pick an image from the gallery; 2) It displays Icons.cancel and removes the previously selected image. Section A handles the onPress event options, and Section B handles the displayed icon.
What I actually get is Icons.add_photo_alternate at all times, and on press actually triggers BOTH of the alternative code blocks.
So I would really like to be able to access this data to see where I might be going wrong!
I am trying variations of things similar to:
print(customToggleBloc.customToggleSink.toString());
Which returns:
Instance of '_StreamSinkWrapper<bool>'
And not the value inside. Is it even possible to access this information?

I think this behavior is caused by your stream not returning any data, and thus relying on the initialData you provide to build its children.To be sure, you can remove the initialDate: true from your StreamBuilder and wrap the builder body with if-else to check for data existence: if data is available your GestureDetector will be built, if not a CircularProgressIndicator will be displayed in the middle. This way you will know what is going on with your stream:
Widget customTheme() {
return StreamBuilder(
//initialData: true,
stream: customToggleBloc.customToggleStream,
builder: (BuildContext context, snapShot) {
if(snapShot.hasData){
print(snapShot.data.toString());
return GestureDetector(
onTap: (){
widget?._callback(snapShot.data == true ? 'custom' : 'clearcustom') ; //Section A
},
child: Container(
width: menuButtonSize + 8,
height: menuButtonSize + 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppState.brownTheme,
boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 25,
),
],
),
child: new IconTheme(
data: new IconThemeData(
color: Colors.white,
size: 35,
),
child: Icon(snapShot.data == true ? Icons.add_photo_alternate : Icons.cancel), // Section B
),
),
);
}else{
return Center(
child: CircularProgressIndicator(),
);
}
}
);
}

Related

isempty function is printing the text even if the firestore has some data

new to flutter and working on this error.
this is the code.
PROBLEM- I want to to show the text(your cart is empty!) when the firestore is empty. The text is displaying when the firestore document is empty but the problem is when firestore has some data to display, the text(your cart is empty!) will display and after a moment(0.5 sec) the data will display.
I don't want to display the text (your cart is empty) even for a milliseconds when the firestore has data. I appreciate if you guys can light the bulb for me.
that happened because you didn't get response yet for the database so to make sure that you received respond you will use snapshot.hasData
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('User-Cart-Item')
.doc(FirebaseAuth.instance.currentUser!.email)
.collection("items")
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
if (snapshot.data == null || snapshot.data!.docs.isEmpty) {
return const Center(
child: Text(
'YOUR CART IS EMPTY!',
style: TextStyle(
color: Colors.purple,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
);
} else {
return ListView.builder(
itemCount:
snapshot.data == null ? 0 : snapshot.data!.docs.length,
itemBuilder: (_, index) {
DocumentSnapshot documentSnapshot =
snapshot.data!.docs[index];
return Card(
color: const Color.fromARGB(255, 255, 248, 250),
margin: const EdgeInsets.all(20),
child: Container(
height: 120,
padding: const EdgeInsets.all(0),
child: Row(
children: [
Expanded(
flex: 6,
child: Container(decoration: const BoxDecoration()),
),
],
),
),
);
},
);
}
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
I hope this work for you
With FutureBuilder, the app is listening to changes to the database. What you are noticing is the latency between a change being made, and your app receiving it. This can never be absolute 0 because a reading device only sees a change after the change has been made. You can decrease this number with optic internet (fast internet connection) and by moving the database closer to where you are. There are multiple regions that you can choose from in order to get you as close as possible.

mapping values to widgets in children property, flutter

this is portion of my code:
return BlocBuilder<receivedValuesBloc, receivedValuesState>(
bloc: _valueBloc,
builder: (context, state) {
if (state is ReceivedvaluesState) {
return GridView.count(
crossAxisCount: 2,
children: // code to put List of widgets based on values in state which in my code is **state.values**
, );
}
if (state is LoadingvaluesState) {
return Positioned(
child: Container(
color: Color.fromARGB(214, 229, 231, 219),
child: Center(
child: CircularProgressIndicator(
color: Color.fromARGB(255, 71, 47, 157),
),
),
),
);
}
return GridView.count(crossAxisCount: 8, children: [
Card(
child: Container(
width: 50,
color: unavailableData,
))
]);
});
}
What I am trying to do in the children of the widget GridView.count. (commented line) is to produce the children with different colours based on the values in state. I could't get the syntax right to map each value in the state.values list to a widget list that I can put in the children to build my GridView.count.
any ideas on how to do that?

Flutter Button Icon not changing with audio player

I'm trying to make a radio app, using the assets_audio_player library, it does fetch the radio stream from the link, and all is working, but when I try to make the Play/Pause button change icon accordingly, it just doesn't work, I tried two approaches:
1- Using the library builder:
assetsAudioPlayer.builderCurrent(
builder: (context, isPlaying){
return PlayerBuilder.isPlaying(
player: assetsAudioPlayer,
builder: (context, isPlaying) {
return RawMaterialButton(
constraints: BoxConstraints.expand(width: 70, height: 70),
fillColor: Constants.WHITE,
shape: CircleBorder(),
child: RadiantGradientMask(
child: Icon(isPlaying ? Icons.pause : Icons.play_arrow ,
size: 42,
color: Constants.WHITE,
),
gradient: gradient,
),
onPressed: (){
assetsAudioPlayer.playOrPause();
}
);
});
}
),
2- Using a normal button, and a bool value and setState:
RawMaterialButton(
constraints: BoxConstraints.expand(width: 70, height: 70),
fillColor: Constants.WHITE,
shape: CircleBorder(),
child: RadiantGradientMask(
child: Icon(isPlaying ? Icons.pause : Icons.play_arrow ,
size: 42,
color: Constants.WHITE,
),
gradient: gradient,
),
onPressed: (){
assetsAudioPlayer.playOrPause();
setState(() {
isPlaying = assetsAudioPlayer.isPlaying.value;
});
},
In the 1st method, the library starts streaming, but the icon doesn't change at all (pause/play), in the 2nd method, it does change, but after pressing the button several times, it glitches, and the 'play' becomes 'pause', and 'pause' becomes 'play' (reversed).
Any idea about how to get this to work properly?
The method call assetsAudioPlayer.playOrPause(); is Async, and the setState({}); is Sync.
You are checking if the status changed synchronously, but changing it async, you need to await for the the playOrPause() method.

Flutter async method keeps running even after the corresponding widget is removed

I have a list of image paths and I am using the List.generate method to display images and a cross icon to remove image from list. Upload method is called on each image and when I remove the image from the list the method still keeps running until the image is uploaded. The behavior I am expecting is when I remove the image from the list the method should also stop running. I am using a future builder to display the circular progress bar and error icons while uploading an image.
What should I be doing to make sure the future method associated to the current widget also stops when I remove the widget from the list?
This is the code where I am creating a list
List.generate(
files.length,
(index) {
var image = files[index];
return Container(
height: itemSize,
width: itemSize,
child: Stack(
children: <Widget>[
Container(
getImagePreview(image, itemSize)
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
uploadHandler(image, field),
InkWell(
onTap: () => removeFileAtIndex(index, field),
child: Container(
margin: EdgeInsets.all(3),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(.7),
shape: BoxShape.circle,
),
alignment: Alignment.center,
height: 22,
width: 22,
child: Icon(Icons.close, size: 18, color: Colors.white),
),
),
],
),
],
),
);
},
)
This is Upload Handler method.
Widget uploadHandler(file, field) {
return FutureBuilder(
future: upload(file),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data.statusCode == 201) {
return doneUpload();
} else {
logger.d(snapshot.error);
return error();
}
} else {
return uploading();
}
},
);
}
The lifecycle of the widget isn't attached to the async functions invoked by the widget.
You can check the mounted variable to check if the widget still mounted from your async function.

How to reload specific widgets in flutter without its parent?

I am building an app in Flutter,that supposed to be a social network app-similar to faceboook.
I have implemented a like button-when pressed is sending the server the request,and then depending on the status code it sets the state.My problem begins when the setState() is rendering again the avatar picture,or creating it again from scratch(the avatar is stored in a 64base String).
the likePress is a future that sends the request and then sets the boolean isLiked accordingly.
this is the creating of the like button:
buildLikeButton(int ownerId, int postId) {
return RepaintBoundary(
child: FutureBuilder<bool>(
future: getLike(ownerId, postId),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
IconButton likeButton;
if (snapshot.hasData) {
isLiked = snapshot.data;
likeButton = createLikeButton(ownerId, postId);
} else if (snapshot.hasError) {
isLiked = false;
likeButton = createLikeButton(ownerId, postId);
print('the snapshot has an error ${snapshot.error}');
} else {
isLiked = false;
likeButton = createLikeButton(ownerId, postId);
}
return likeButton;
}));
}
createLikeButton(int ownerId, int postId) {
return IconButton(
icon: returnLikeIcon(isLiked),
color: Theme.of(context).accentColor,
onPressed: () async {
if (this.mounted) {
setState(() {
Future lol = likePress(ownerId, postId).then((onValue) {});
});
}
},
);
}
and this is the creation of the avatar:
createAvatar(BuildContext context, avatar_base64, int ownerId) {
Uint8List bytes = base64Decode(avatar_base64.split(',').last);
return RepaintBoundary(
child: CircleAvatar(
radius: 25.0,
backgroundImage: MemoryImage(bytes),
backgroundColor: Colors.transparent,
));
}
The widget that displays them together is the Post widget which i have created for this project,and this is it's build function:
Widget build(BuildContext context) {
return InkWell(
borderRadius: BorderRadius.circular(0.2),
child: Container(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Theme.of(context).primaryColor,
blurRadius: 1.0,
spreadRadius: 1.0, // has the effect of extending the shadow
offset: Offset(
5.0, // horizontal, move right 10
5.0, // vertical, move down 10
),
),
]),
child: Card(
elevation: 10.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
fit: FlexFit.loose,
child: postInfo(context, time, ownerId)),
Divider(
thickness: 1.0,
height: 10.0,
indent: 10.0,
endIndent: 10.0,
),
postContent(content),
Divider(
thickness: 1.0,
height: 10.0,
indent: 10.0,
endIndent: 10.0,
),
createButtonBar(ownerId, postId),
],
)),
));
}
postInfo is just a FutureBuilder that builds the ListTile that adds up the avatar and the name, and createButtonBar creates the like button and 2 more buttons.
I would like to change the icon when the user presses the like button,but only if the server has responded with the right status code and without rendering and creating the whole Post widget over again.Thank you for the trouble!
This means that the avatar is beneath the point where you are calling setState(() {}). In your case, the method is probably inside that particular widget and the widget is being rebuilt.
I suggest for you to solve the problem to move the creation of the avatar above. In this way, if you need to rebuild the object the avatar will not be created anew but simply placed within the new widget. Place some debugPrints around to speed up the process and try to refactor the code to see if you missed something.
After taking a better look at my code,I decided to create a different Widget for each part of the post,so I can initialize everything that will not be built again outside of the build method.
so if you want to exclude a widget from the setState() method, you need to move it outside the current widget(by creating a widget for it) and just create an instance of it as a parameter in the constructor.
In more detail,I created a class named PostHeader and there i created the avatar and the ListTile containing it,then i created an instance of it inside the Post class,so it is not created inside the build method of the Post class.