Boolean value getting changed for all - Flutter Provider - flutter

I'm building a music player, there are two screens - At first screen, list of songs are present in ListView builder and second screen plays the respective music (player screen)
When I play music from the player screen by clicking the play button, the icon changed to pause but the problem is that for other player screen, it also get changed
I'm trying to achieve this using Flutter provider package, I think what happens is that isPlaying boolean is acting up as a global variable in provider class and all of them are consuming the same value, how to tackle this?
//Provider class
class MusicPlayerProvider extends ChangeNotifier {
final AudioPlayer audioPlayer = AudioPlayer();
bool isPlaying = false;
void playMusic(Map<String, dynamic> songMap) async {
await audioPlayer.play(UrlSource(songMap['link']));
isPlaying = true;
notifyListeners();
}
void stopMusic() async {
await audioPlayer.pause();
isPlaying = false;
notifyListeners();
}
}
// Music Listing Screen
class _MusicListScreenState extends State<MusicListScreen> {
List<Map<String, dynamic>> songList = [{'name': 'Helix', 'link': 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3', 'singer': 'Alan Walker'}, {'name': 'Lucifer', 'link': 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3', 'singer': 'John Smith'}];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: songList.length,
itemBuilder: (BuildContext context, int index){
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.grey
),
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => MusicPlayerScreen(songMap: songList[index])));
},
child: Text('Go to Player Screen', style: GoogleFonts.oswald(color: Colors.white))
)
},
);
}
}
// Music Player Screen
class MusicPlayerScreen extends StatefulWidget {
final Map<String, dynamic> songMap;
const MusicPlayerScreen({Key? key, required this.songMap}) : super(key: key);
#override
State<MusicPlayerScreen> createState() => _MusicPlayerScreenState();
}
class _MusicPlayerScreenState extends State<MusicPlayerScreen> {
#override
Widget build(BuildContext context) {
return Consumer<MusicPlayerProvider>(
builder: (BuildContext context, value, Widget? widgetValue){
return InkWell(
onTap: (){
if(value.isPlaying){
value.stopMusic();
} else {
value.playMusic(widget.songMap);
}
},
child: Container(
height: 50.0,
width: 50.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[300]
),
// Icon changed for other music player screen to - problem
child: Icon(value.isPlaying ? Icons.pause_circle_filled : Icons.play_circle_fill, color: Colors.white, size: 35.0),
),
),
},
);
}
}

In your provider you can track the song that is playing instead of just isPlaying bool. This way you can tell which song is playing (if any) and show play/pause button accordingly.
UPD: also I would recommend moving song list to provider as well. So screens would just display a state and provider would handle the data.

Related

How to play one audio file at a time in a series of audio files in Flutter?

I have several audio files to be played, so I used ListView to represent every audio file as an item of ListView, each one with its own controllers (play/pause button and duration slider). The code is as follows (I have used one audio file for all of the items for simplicity sake):
import 'package:audioplayers/audioplayers.dart';
class AudioTestScreen extends StatelessWidget {
const AudioTestScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Songs")),
body: ListView.builder(
itemCount: 10,
itemBuilder: (ctx, index) => const AudioItem(),
),
);
}
}
class AudioItem extends StatefulWidget {
const AudioItem({Key? key}) : super(key: key);
#override
State<AudioItem> createState() => _AudioItemState();
}
class _AudioItemState extends State<AudioItem> {
final audioPlayer = AudioPlayer();
bool isPlaying = false;
Duration duration = Duration.zero; // For total duration
Duration position = Duration.zero; // For the current position
#override
void initState() {
super.initState();
setAudioPlayer();
audioPlayer.onDurationChanged.listen((newDuration) {
setState(() {
duration = newDuration;
});
});
audioPlayer.onPositionChanged.listen((newPosition) {
if (mounted) {
setState(() {
position = newPosition;
});
}
});
audioPlayer.onPlayerStateChanged.listen((state) {
if (mounted) {
setState(() {
isPlaying = state == PlayerState.playing;
});
}
});
}
Future<void> setAudioPlayer() async {
final player = AudioCache(prefix: "assets/audios/");
final url = await player.load("song.mp3");
audioPlayer.setSourceUrl(url.path);
audioPlayer.setReleaseMode(ReleaseMode.stop);
}
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
decoration: BoxDecoration(
color: const Color(0xFFF4F2FF),
borderRadius: BorderRadius.circular(12),
border: Border.all(width: 1, color: Colors.grey)
),
child: Column(
children: [
Slider(
value: position.inMilliseconds.toDouble(),
max: duration.inMilliseconds.toDouble(),
onChanged: (value) {
setState(() {
position = Duration(milliseconds: value.toInt());
});
audioPlayer.seek(position);
},
),
GestureDetector(
onTap: () async {
isPlaying
? await audioPlayer.pause()
: await audioPlayer.resume();
},
child: CircleAvatar(
child: Icon(isPlaying ? Icons.pause : Icons.play_arrow),
),
)
],
),
);
}
}
And here is how it looks like:
Now when I play a music file, and later tap on another item to play it, both of them plays at the same time, but I want the previous one to pause and only the current one to play.
How can I achieve this behavior? Thanks in advance.
create the audio player in the parent class and pass it to the children. Then before you play stop the player and then play it with new url
widget.player.stop()
Use this to stop the player
EDIT
class AudioItem extends StatefulWidget {
final AudioPlayer audioPlayer;
final int currentIndex;
final int index;
final VoidCallback setIndex;
const AudioItem({Key? key, required this.audioPlayer, required required this.currentIndex, required this.index, required this.setIndex}) : super(key: key);
Add these 3 variables to the Audio item. When you add these Widgets in the tree pass the values
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Songs")),
body: ListView.builder(
itemCount: 10,
itemBuilder: (ctx, index) => const AudioItem(
audioPlayer: audioPlayer,
currentIndex:currentIndex, <--this is the variable in which we know which item is playing.
index: index,
setIndex: (){
currentIndex = index;
setState((){});
}
),
),
);
}
Now when the play button is clicked call this setIndex method that will update the parent.

How to save a boolean state in main.dart so it is retained throughout app

I have a challenge in retaining a boolean value state in my dashboard screen after I close or reload app.
On the dashboard screen, there is a ListTile where I can mark a card as verified by tapping on that card. Once the card is tapped, I set the bool verified state from false to true which works fine as long as I haven't closed or reloaded the app. Once the app is closed or reloaded, the boolean state is set back to false.
How can I initialize the boolean state in main.dart so that the verified status is always retained once it is set from the dashboard screen and can be used anywhere (more screens) within the app
here is the code:
Dashboard screen
class Dashboard extends StatefulWidget {
Dashboard({Key? key}) : super(key: key);
#override
_DashboardState createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
bool _verified = false;
//Retrieving card info from database
bool isFetching = false;
late String cardInfo = retrieveData; //url to php script for retrieving from database
List cardData = [];
getCardData() async {
setState(() => isFetching = true);
var response = await http.get(Uri.parse(cardInfo));
if (response.statusCode == 200) {
setState(() {
cardData = json.decode(response.body);
});
}
setState(() => isFetching = false);
return cardData;
}
#override
void initState() {
super.initState();
getCardData();
_verified;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Approve Card'),
centerTitle: true,
),
body: Container(
child: Card(
child: ListView.builder(
physics: const ClampingScrollPhysics(),
shrinkWrap: true,
primary: false,
itemCount: cardData.length, //coming from mysql database
itemBuilder: (context, index) {
return ListTile(
leading: Container(
padding: const EdgeInsets.only(left: 15.0),
alignment: Alignment.center,
height: 50,
width: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.0),
image: DecorationImage(
image: NetworkImage(
'http://url/uploads/${cardData[index]['logo']}'),
fit: BoxFit.cover,
),
),
),
title: Text(
cardData[index]['name'],
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
subtitle: Text(
cardData[index]['email'],
),
trailing: Bounce(
duration: const Duration(milliseconds: 100),
onPressed: () { //onPressed set verified state to true
//After app is reloaded, it is set back to false
setState(() {
col = iconTip;
_verified = true;
});
var url = Uri.parse(verifiedCards), //http url to php script
response = http.post(url, body: {
"card": cardData[index]['card'],
});
getCardData();
},
child: Container(
padding: const EdgeInsets.all(15.0),
color: col,
child: Icon(Icons.check_sharp),
),
),
);
}),
),
),
);
}
}
}
Main.dart screen
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: My Flutter App,
theme: ThemeData(
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.green,
backgroundColor: white,
),
),
initialRoute: '/',
routes: {
'/': (context) => const SplashScreen(),
'/dashboard': (context) => const Dashboard(),
},
);
}
}
Use the shared_preferences package to save data that needs to persist between app reloads.
https://pub.dev/packages/shared_preferences
// Save value
await prefs.setBool('verified', true);
// Read value
final bool? verified= prefs.getBool('verified');
When you want to change your application state entire application then you can use Provider Package.
First create Model Class
class MyChangeNotifier extends ChangeNotifier{
bool _verified;
void setVarified(bool verified){
_verified = verified;
notifyListeners();
}
bool get verified => _verified;
}
Attach Notifier with main.dart file.
runApp(MultiProvider(
providers: [
Provider<MyChangeNotifier>(create: (_) => MyChangeNotifier()),
],
child: MyApp(),
));
Use State in your application
// For change State from widgets
ElevatedButton(onPressed: (){
context.read<MyChangeNotifier>().setVarified([true / false]);
}, child: Text('Event Setter'));
// Access State for widget
bool check = context.read<MyChangeNotifier>().verified;
Also, you can check Flutter State Management in Hindi video tutorial for more help.
A complete example with Shared Preference :
Writing boolean value:
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('isfirstRun', false);
Reading boolean value:
SharedPreferences prefs = await SharedPreferences.getInstance();
bool myboolean = prefs.getBool(key) ?? true; // Or false depending of what you want for default value. This is required for Null safety
And don't forget to import share preference package:
import 'package:shared_preferences/shared_preferences.dart';
Thanks guys for your answers, I appreciate. I figured it out with the shared preferences I onced used. I was thinking there was a way to do so without using shared preferences but since it does the work and it's not too complicated, I decided to use it

Updating a Slider based on video position

Summarize the problem
I need help finding a Flutter construct which will allow me to repeatedly check the return value of an API function call for getting the current time of a playing video (position). My goal is to update the Slider() to the position of the current video. I am using plugin flutter_vlc_player for playing back videos.
The most important part of the code below is the videoPlayerController.getPosition() function call. I need a way to repeatedly call this function and get the latest value. This is what I am struggling with.
The SeekBar class instantiated is the Slider() I am updating.
I think I am close to a solution as StreamBuilder is meant to update based on events. Also if I perform hot refresh of app after playing a video, the Slider updates once.
What I am seeing is the stream function is called twice but returns null each time because the video isn't playing yet. I need the stream function to be called while the video is playing.
I/flutter (29465): snapshot: null
I/flutter (29465): snapshot: null
One last thing: videoPlayerController.getPosition() is a Future.
Describe what you've tried:
I tried using StreamBuilder() and FutureBuilder() but I got the same results. The current position is only fetched twice when I need it to be continuously fetched during video playback. I checked the Flutter documentation on StreamBuilder but their example only shows when there is one item to be grabbed and not multiple. I need to rebuild the Slider() widget based on the value returned from function repeatedly.
Show some code:
VlcPlayerController videoPlayerController = VlcPlayerController.network(
'rtsp://ip_addr:8554/test',
hwAcc: HwAcc.FULL,
autoPlay: true,
options: VlcPlayerOptions(
video: VlcVideoOptions(),
rtp: VlcRtpOptions(['--rtsp-tcp'],),
extras: ['--h264-fps=30']
),
);
await Navigator.push(context,
MaterialPageRoute(builder: (context) =>
Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.blueAccent,
title: const Text("Playback")),
body: Center(
child: Column(
children: [
VlcPlayer(
controller: videoPlayerController,
aspectRatio: 16/9,
placeholder: const Center(child: CircularProgressIndicator()),
),
StatefulBuilder(builder: (context, setState) {
return Row(
children: [
TextButton(
child: Icon(isPlaying ? Icons.play_arrow : Icons.pause),
style: ButtonStyle(backgroundColor: MaterialStateProperty.all<Color>(Colors.blueAccent),
foregroundColor: MaterialStateProperty.all<Color>(Colors.white)),
onPressed: () {
setState(() {
if(videoPlayerController.value.isPlaying)
{
isPlaying = true;
videoPlayerController.pause();
}
else {
isPlaying = false;
videoPlayerController.play();
}
});
}
),
Text("${videoPlayerController.value.position.inMinutes}:${videoPlayerController.value.position.inSeconds}",
style: const TextStyle(color: Colors.white)),
],
);
}),
StreamBuilder<Duration>(
stream: Stream.fromFuture(videoPlayerController.getPosition()),
builder: (BuildContext context, AsyncSnapshot <Duration> snapshot) {
Duration position = snapshot.data ?? const Duration();
print('snapshot: ${snapshot.data?.inSeconds.toDouble()}');
return Column(
children: [
SeekBar(
duration: const Duration(seconds: 5),
position: position,
onChangeEnd: (newPosition)
{
videoPlayerController.seekTo(newPosition);
},
)
],
);
}
),
]
)
),
)
)
);
Thank you for reading and help. I am still learning Flutter/Dart so any references to helpful classes will be great.
Is there a reason you want to use StreamBuilder in this case? You can use a StatefulWidget and add a listener to the controller. Then update the position inside that listener.
Use this to add listener:
videoPlayerController.addListener(updateSeeker);
Also make sure to remove the listener in dispose method:
videoPlayerController.removeListener(updateSeeker);
Here is the updateSeeker method:
Future<void> updateSeeker() async {
final newPosition = await videoPlayerController.getPosition();
setState(() {
position = newPosition;
});
}
Here is an example of a widget that plays the video and shows its position in a Text widget:
class VideoPlayer extends StatefulWidget {
const VideoPlayer({Key? key}) : super(key: key);
#override
_VideoPlayerState createState() => _VideoPlayerState();
}
class _VideoPlayerState extends State<VideoPlayer> {
final videoPlayerController = VlcPlayerController.network(url);
var position = Duration.zero;
#override
void initState() {
super.initState();
videoPlayerController.addListener(updateSeeker);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
VlcPlayer(
aspectRatio: 16 / 9,
controller: videoPlayerController,
),
Text(position.toString()),
],
),
);
}
Future<void> updateSeeker() async {
final newPosition = await videoPlayerController.getPosition();
setState(() {
position = newPosition;
});
}
#override
void dispose() {
videoPlayerController.removeListener(updateSeeker);
super.dispose();
}
}

How to set the updated text value of button throughout the app

I want to set the updated text value of button throughout the app, when i click on button its text changes to current time, but when I navigate to other screen, and then come back to the screen where I created a button, it is not showing the updated text.
here is my button widget
String getTime;
//from here i get the current time
void _getTime() {
final String formattedDateTime =
DateFormat('kk:mm:ss a').format(DateTime.now()).toString();
setState(() {
getTime = formattedDateTime;
print("time");
print(getTime);
});
}
String timeInText = "Time in";
Widget _timein() {
//enable- initial case
bool firstCaseFlag = true;
if (getTimeInStatus == false && timeInButtonPressed == true) {
print("i1");
return FlatButton(
color: timeInButtonPressed ? Colors.blue[500] : Colors.blue[200],
textColor: Colors.white,
padding: EdgeInsets.all(15.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(buttonRoundRadius)),
child: Row(children: <Widget>[
Icon(
Icons.timer,
),
Expanded(
child: Text(
timeInText,
textAlign: TextAlign.center,
style: TextStyle(fontSize: textFontSize),
),
),
]),
onPressed: () {
_getTime();
setState(() {
if (firstCaseFlag == true) {
timeInText = getTime; //here i set the button text to current time
timeIn = timeInText;
firstCaseFlag = false;
} else {
}
});
calltimeInApi();
});
Conditions:
There are certain conditions where button will change there state, like i have 2 button namely timein and timeout, initially timein button will be enable to click and timeout will be disable, so if user click on timein button its text change to current time and timeout button will be enable (this is all happening), and if user moved to other screen and come to home screen (where i created timein and timeout buttons) then timein button text should display that time when user click on it.
Problem:
My problem is when I moved to other screen and come to home screen timein button is enabled and not showing the time when i click on it.
please help how i can fix it.
I prefer using statemanagement StateProvider. here is an example just using global variable.
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:stack_overflow/exports.dart';
String buttonText = "Click to set";
///for riverpod
///final buttonState = StateProvider((ref) => "Click to set");
class BaseWidget extends StatefulWidget {
const BaseWidget({Key? key}) : super(key: key);
#override
_BaseWidgetState createState() => _BaseWidgetState();
}
class _BaseWidgetState extends State<BaseWidget> {
void _getTime() {
final String formattedDateTime =
DateFormat('kk:mm:ss a').format(DateTime.now()).toString();
setState(() {
buttonText = formattedDateTime;
print("time");
print(buttonText);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () {
_getTime();
},
child: Text(buttonText),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => NextWidget(),
));
},
child: Text("next"),
),
],
),
);
}
}
class NextWidget extends StatelessWidget {
const NextWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Back"),
),
);
}
}
Use state management like Provider to keep the values and then access anywhere.
Package link: https://pub.dev/packages/provider
Helpful reference: https://flutter.dev/docs/development/data-and-backend/state-mgmt/intro

How to rebuild a screen if confirm is pressed on bottom sheet?

I have built a home screen with is rendering cards using ListView.builder. These cards have a confirm button which fetches the confirmation status from firestore. When I tap on the confirm button, a bottom sheet appears asking whether I am sure. Once I tap Yes on the bottom sheet, I want the card on homepage to be rebuilt and change the button from confirm to confirm.
I used the setState to change the value at the onPressed event and it is successfully changing it but, the confirm button is not changing to confirmed.
Any leads on how to solve this issue would be really appreciated.
Homepage cards layout
class HomepageCards extends StatefulWidget {
final FirebaseUser user;
final Map cardDetails;
HomepageCards({#required this.user, this.cardDetails});
#override
_HomepageCardsState createState() => _HomepageCardsState();
}
class _HomepageCardsState extends State<HomepageCards> {
#override
Widget build(BuildContext context) {
// Confirmation status from firebase about the captain
bool isConfirmed = widget.cardDetails['c'];
final screenHeight = MediaQuery.of(context).size.height;
final screenWidth = MediaQuery.of(context).size.width;
return SingleChildScrollView(
padding: EdgeInsets.fromLTRB(screenHeight / 60, screenHeight / 90,
// UI Code here......
Container(
height: screenHeight / 80,
),
// Confirm Button checking condition and building UI accordingly
isConfirmed == true
? captainConfirmed(context, isConfirmed) // if confirmed then different button style widget
: confirmAndCancelButton(context, isConfirmed), //if not confirmed then show confirm and cancel button in the card
],
),
// Some UI
);
}
}
Once clicking on cancel, the bottom sheet:
Widget confirmCaptainBookingBottomSheet(
BuildContext context, bool isConfirmed) {
final screenHeight = MediaQuery.of(context).size.height;
final screenWidth = MediaQuery.of(context).size.width;
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Container(
// UI Code
Text(
'Do you want to confirm?',
style: TextStyle(
color: black.color,
fontSize: headSize.fontSize,
),
),
child: FlatButton(
child: Text(
'YES',
style: TextStyle(
color: cyan.color,
fontSize: headSize.fontSize),
),
onPressed: () {
print(isConfirmed);
setState(() {
// change the value of is confirmed which is used to build different buttons in the UI as shown in the above code
isConfirmed = true;
});
print(isConfirmed);
Navigator.pop(context);
}),
),
child: FlatButton(
child: Text(
'NO',
style: TextStyle(
color: cyan.color,
fontSize: headSize.fontSize),
),
onPressed: () {
Navigator.pop(context);
}),
),
});
}
You can create a function in _HomepageCardsState class which change state of isConfirmed and pass that function to widget where you want change the state. Then on onPressed of yes just give that function. it will change state of isConfirmed in _HomepageCardsState widget so you can see captainConfirmed widget.
I am leaving small demo which simulates how you can do that in your case.
I hope following code clear your idea.
class DeleteWidget extends StatefulWidget {
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
bool isConfirmed = false;
changeconfirmed() {
setState(() {
isConfirmed = !isConfirmed;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: isConfirmed
? Home1()
: Home2(
function: changeconfirmed,
),
)),
);
}
}
class Home1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text("confirmed Widget"),
);
}
}
class Home2 extends StatelessWidget {
final Function function;
Home2({this.function});
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("press"),
onPressed: function,
);
}
}