How to run the setState() after executing insert or delete of the moor in flutter - flutter

if you touch the heart icon, I want to insert or delete the product class in the Moor database and change the heart icon to setState()
Insertion or deletion is well executed, but the heart icon does not seem to change because setState() has already been executed during insertion or deletion.
I'd appreciate it if you could let me know if my method is wrong or if there's a better way than using a "stream builder."
Thank you for reading my question.
Widget setFavorite() {
ProductsDao productsDao = Provider.of<AppDatabase>(context).productsDao;
return StreamBuilder<List<mf.QueryRow>>(
stream: productsDao
.customSelect(
"SELECT * FROM Products WHERE firestoreid LIKE '${widget.product.firestoreid}'")
.watch(),
builder:
(BuildContext context, AsyncSnapshot<List<mf.QueryRow>> snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
return new Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text("");
default:
return Positioned(
right: 0,
bottom: 0,
child: Padding(
padding: EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
snapshot.data.isEmpty
? productsDao.insertProduct(this.widget.product)
: productsDao.deleteProduct(this.widget.product);
});
},
child: Icon(
snapshot.data.isEmpty
? Icons.favorite_border
: Icons.favorite,
color: Colors.pink,
),
),
],
),
),
);
}
},
);
}

You can try to wait for the database to finish using await :
onTap: () async {
snapshot.data.isEmpty
? await productsDao.insertProduct(this.widget.product)
: await productsDao.deleteProduct(this.widget.product);
setState(() {}
});
},

I'm not sure how you have the method of inserting, but if you are using provider you need to use a function that will update a state and uses notifiyListeners().
When you are using notify listeners you don't even need setstate it will update all the values.
class AppDatabase extends ChangeNotifier {
ProductsDao _productsDao;
ProductsDao get productsDao => _productsDao;
void insertProduct(value){
this._productsDao.insertProduct(value);
notifiyListeners();
}
void deleteProduct(value){
this._productsDao.deleteProduct(value);
notifiyListeners();
}
}
Now you should use the provider insertProduct to refresh the state of your value.
Widget setFavorite() {
AppDatabase appState = Provider.of<AppDatabase>(context);
return StreamBuilder<List<mf.QueryRow>>(
stream: appState.productsDao
.customSelect(
"SELECT * FROM Products WHERE firestoreid LIKE '${widget.product.firestoreid}'")
.watch(),
builder:
(BuildContext context, AsyncSnapshot<List<mf.QueryRow>> snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
return new Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text("");
default:
return Positioned(
right: 0,
bottom: 0,
child: Padding(
padding: EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
snapshot.data.isEmpty
? appState.insertProduct(this.widget.product)
: appState.deleteProduct(this.widget.product);
});
},
child: Icon(
snapshot.data.isEmpty
? Icons.favorite_border
: Icons.favorite,
color: Colors.pink,
),
),
],
),
),
);
}
},
);
This will refresh your value in the ui.
Hope it helped.

Related

Flutter, how to return different widget based on future value?

I would like to base on a future bool value, to set different icons pass back to a data card inside a list, I tried .then or FutureBuilder, but still not successful.
Scaffold:
child: ListView.builder(
itemCount: fullList.length,
itemBuilder: (BuildContext context, int index) {
return dataCard(context, fullList, index);
}),
dataCard:
Row(
children: [
Expanded(
flex: 8,
child: Text(dl[i].Name,
style:
TextStyle(color: Colors.blue[400], fontSize: 16)),
),
Expanded(
flex: 1,
child: setFavouriteIcon(dl[i].ID),
),
],
),
setFavouriteIcon:
Widget setFavouriteIcon(_id) {
final marked = markedFavourites(_id).then((value) { //markedFavourites returns Future<bool>
if (value == true) {
return Icon(
size: 24,
Icons.favorite,
color: Colors.red,
);
} else {
return Icon(
size: 24,
Icons.favorite_border_outlined,
color: Colors.red,
);
}
});
return Text(''); //Without this line, Error: A non-null value must be returned
}}
You can include other state as well on FutureBuilder
Widget setFavouriteIcon(_id) {
return FutureBuilder(
future: markedFavourites(_id),// you shouldn't call method directly here on statefulWidget case
builder: (context, snapshot) {
final value = snapshot.hasData && (snapshot.data as bool? ?? false);
if (value == true) {
return Icon(
size: 24,
Icons.favorite,
color: Colors.red,
);
} else {
return Icon(
size: 24,
Icons.favorite_border_outlined,
color: Colors.red,
);
}
},
);
}
you should use FutureBuilder
class FavoriteWidget extends StatelessWidget {
const FavoriteWidget({super.key});
// some future value
Future<bool> markedFavorites() async {
//do smth
return true;
// or return false
}
#override
Widget build(BuildContext context) {
return Center(
child: FutureBuilder<bool>(
future: markedFavorites(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
if (snapshot.data!) {
return const Icon(
Icons.favorite,
color: Colors.red,
);
}
return const Icon(Icons.favorite_border_outlined);
}
},
),
);
}
}

Why StreamBuilder always has no data before hot reload?

I use firestore and streambuilder to read data in a list, when i run the application for the first time i get a message "Unexpected null value" and I realized that "snapshot.hasData" is always false and snapshot.ConnectionState.waiting is always true. But when i restart application with hot reload i can retrieve data.
This is my stream:
Stream<QuerySnapshot> _branchStream = FirebaseFirestore.instance.collection('Companies').doc(company_id).collection("Branch Offices").snapshots();
This is my StreamBuilder
StreamBuilder<QuerySnapshot>(
stream: _branchStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
/* if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}*/
return ListView(
children: snapshot.data!.docs
.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return Padding(
padding: const EdgeInsets.all(22.0),
child: Card(
elevation: 8,
shadowColor: Colors.blueGrey,
shape: cardShape,
child: Row(
children: [
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(22.0),
child: CircleAvatar(
radius: 50,
backgroundImage:
NetworkImage(data['branch_image'],scale: 60),
),
)),
Expanded(
flex: 4,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(22.0),
child: Text(data['branch_name'], style: textBlackTitle, textAlign: TextAlign.center,),
),
Padding(
padding: const EdgeInsets.all(22.0),
child: Text("UbicaciĆ³n: "+data['branch_address'], style: textGraySubTitle, textAlign: TextAlign.center,),
),
],
)),
Expanded(
flex: 2,
child: IconButton(
// focusColor: Color(color1),
// color: Color(color1),
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => Home(branch_id : data['branch_id'], company_id : company_id, branch_name : data['branch_name'], branch_image : data['branch_image'])));
}, icon: Image.asset("assets/enter.png", fit: BoxFit.contain, height: 100,)))
],
),
),
);
})
.toList()
.cast(),
);
},
)
This is data that I want to get
This is what I get at the first time
This is what I get after hot reload (That I should have from the beginning).
Because your data is null at the beginning, it takes some time to load the data.
You actually already included a check, but commented it out again. Undo it and try again.
/* if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}*/
It takes some time to load snapshot data. For better UX return specific widgets for each state of the snapshot.
Make sure you're using StreamBuilder inside StatefulWidget.
StreamBuilder<QuerySnapshot>(
stream: _branchStream,
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return //error widget
} else {
switch (snapshot.connectionState) {
case ConnectionState.none:
return //some widget
case ConnectionState.waiting:
return CircularProgressIndicator(),
case ConnectionState.active:
return ListView()
case ConnectionState.done:
return //some widget
}
}
);

Flutter: Future keeps rebuilding because it is being called inside a stream. How to only make it call again, if possible?

So I wanted to display a list of songs but the future that displays a Uint8List artwork of the songs is called from a future. The code works but the album art looks as if it is glitching because it is constantly being called. I had not idea how to fix this and I have tried many solutions. Please help.
Here is my code:
StreamBuilder<List<SongInfo>>(
stream: widget.songs,
builder: (context, snapshot) {
if (snapshot.hasError)
return Utility.createDefaultInfoWidget(Text("${snapshot.error}"));
if (!snapshot.hasData)
return Utility.createDefaultInfoWidget(
CircularProgressIndicator());
return (snapshot.data.isEmpty)
? NoDataWidget(
title: "There is no Songs",
)
: Column(
children: [
Container(
padding:
EdgeInsets.symmetric(vertical: 10, horizontal: 15),
alignment: Alignment.centerRight,
child: Text("${snapshot.data.length} Songs"),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, songIndex) {
SongInfo song = snapshot.data[songIndex];
return ListItemWidget(
title: Text("${song.title}"),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: <Widget>[
Text("Artist: ${song.artist}"),
Text(
"Duration: ${Utility.parseToMinutesSeconds(int.parse(song.duration))}",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500),
),
],
),
trailing: (widget.addToPlaylistAction == true)
? IconButton(
icon: Icon(Icons.playlist_add),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(_dialogTitle),
content: FutureBuilder<
List<PlaylistInfo>>(
future: model.getPlayList(),
builder:
(context, snapshot) {
if (snapshot.hasError) {
print("has error");
return Utility
.createDefaultInfoWidget(
Text(
"${snapshot.error}"));
}
if (snapshot.hasData) {
if (snapshot
.data.isEmpty) {
print("is Empty");
return NoDataWidget(
title:
"There is no playlists",
);
}
return PlaylistDialogContent(
options: snapshot.data
.map((playlist) =>
playlist.name)
.toList(),
onSelected: (index) {
snapshot.data[index]
.addSong(
song: song);
Navigator.pop(
context);
},
);
}
print("has no data");
return Utility
.createDefaultInfoWidget(
CircularProgressIndicator());
}),
);
});
},
tooltip: "Add to playlist",
)
: Container(
width: .0,
height: .0,
),
leading: song.albumArtwork == null
? FutureBuilder<Uint8List>(
future: model.audioQuery.getArtwork(
type: ResourceType.SONG,
id: song.id,
size: Size(100, 100)),
builder: (context, snapshot) {
SchedulerBinding.instance
.addPostFrameCallback(
(_) => setState(() {
isServiceError = false;
isDataFetched = true;
}));
if (snapshot.data.isEmpty)
return CircleAvatar(
backgroundImage: AssetImage(
"assets/images/title.png"),
);
if (isDataFetched) {
return CircleAvatar(
backgroundColor: Colors.transparent,
backgroundImage: MemoryImage(
snapshot.data,
),
);
} else {
return CircleAvatar(
child: CircularProgressIndicator(),
);
}
})
: CircleAvatar(
backgroundImage: FileImage(
IO.File(song?.albumArtwork)),
),
);
},
),
),
],
);
},
I would prefer not to set state inside future builders or stream builders using post-frame callbacks. The reason being you basically asking flutter to build the widget again in the next frame while building the current one which recursively sets the whole thing in a loop. Maybe you can create a new stateful widget and do the loading task manually inside the initState if you need those isServiceError and isDataFetched flags.
The problem in your current code seems to be related to:
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isServiceError = false;
isDataFetched = true;
}));
Which is called inside the future builder. Everytime you set the state, the same code is called again as the widget is rebuilt thus forming a loop in which the whole thing is built again and again needlessly.
You can avoid it by checking the flags before assigning a post-frame callback like so:
if(!isDataFetched)
{
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isDataFetched = true;
}));
}
So in the next frame, isDataFetched will be true hence no further post-frame callbacks.
This solution however is not really a proper solution because as I mentioned above, it's not a good idea to set the state in future builders using post-frame callbacks. If you don't need those flags outside the future builder, you should simply avoid them and rely on snapshot.hasData and snapshot.hasError inside the builder itself.

How to turn off CircularProgressIndicator flutter

There is a list that displays data from the database that comes to it at the time the data is fetched and until the data appears, the CircularProgressIndicator () appears so that the user knows that there is a process happening in the background.
Excellent but there is a problem with this CircularProgressIndicator () continues to work non-stop if there is no data in the database. Herein lies the problem.
It is supposed to work for a specified time and if there is no data in the database it will stop working and disappear.
Is there a way to do this? So that if there is no data that can be fetched it stops working?
My code:
class MainListView extends StatefulWidget {
MainListViewState createState() => MainListViewState();
}
class MainListViewState extends State {
final String apiURL = 'http://====================/getStudentInfo.php';
Future<List<Studentdata>> fetchStudents() async {
var response = await http.get(apiURL);
if (response.statusCode == 200) {
final items = json.decode(response.body).cast<Map<String, dynamic>>();
List<Studentdata> studentList = items.map<Studentdata>((json) {
return Studentdata.fromJson(json);
}).toList();
return studentList;
}
else {
throw Exception('Failed to load data from Server.');
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Studentdata>>(
future: fetchStudents(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator()
);
return ListView(
children: snapshot.data
.map((data) => Column(children: <Widget>[
GestureDetector(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.fromLTRB(20, 5, 0, 5),
child: Text(data.studentName,
style: TextStyle(fontSize: 21),
textAlign: TextAlign.left))
]),),
Divider(color: Colors.black),
],))
.toList(),
);
},
);
}
}
You can try with the below lines
return FutureBuilder<List<Studentdata>>(
future: fetchStudents(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data
.map((data) =>
Column(children: <Widget>[
GestureDetector(
child: Row(
crossAxisAlignment: CrossAxisAlignment
.start,
children: [
Padding(
padding: EdgeInsets.fromLTRB(
20, 5, 0, 5),
child: Text(data.studentName,
style: TextStyle(fontSize: 21),
textAlign: TextAlign.left))
]),),
Divider(color: Colors.black),
],))
.toList(),
);
}
else if (!snapshot.hasData) {
return Text("No data Available");
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Center(
child: CircularProgressIndicator()
);
},
);
You can also set a value to CircularProgressIndicator() to stop at full circle indicator.
Just do this:
if (!snapshot.hasData && !snapshot.hasError) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(
child: Text("No data Found"),
);
} else {
// do something here
}
It means that if status code is 200 and there is no data then you are going to get an error.

How to prevent white screen in Video Player after quitting App?

I'm trying to play a video in my application from my assets file.
I'm using the video_player package for flutter.
Didn't do anything special just copied their implementation from 'flutter.dev', so i'm not going to bore you with the code.
However, my question here is, I noticed when I quit the app (or switch to another app, basically when the app is in the background) and open it again, the video player turns into a white screen and I have to press the play button (FloatingActionButton) again for it to resume.
Any idea on how to prevent this ? I'm running the app on a Physical Iphone X
This is how my video_player is displayed on the Home_Screen:
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: <Widget>[
FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return AspectRatio(
aspectRatio: 16 / 9,
child: VideoPlayer(_controller),
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
Positioned(
bottom: 20.0,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: FloatingActionButton(
onPressed: () {
setState(() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
),
),
],
),
);
}
Thanks
While working with video player package I came across the same problem. Are you checking the resource is truly loaded? Check _controller.value.isInitialized within your build method.
Something like this:
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: [
FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var videoLoaded = _controller.value.isInitialized;
var widget = videoLoaded
? AspectRatio(
aspectRatio: 16 / 9,
child: VideoPlayer(_controller))
: Container(child:(Text("loading")));
return widget;
} else {
return Center(child: CircularProgressIndicator());
}
},
),
Positioned(
bottom: 20.0,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: FloatingActionButton(
onPressed: () {
setState(() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
),
),
],
),
);
}