Multiple audioplayers keep playing on Flutter - flutter

I have an app on Flutter with different tabs and on each tab basically I have a different audioplayer. By the way I use the "audioplayers.dart" package.
When the user changes tab, I want the audioplayer to stop. So I put a stop() function in the dispose method.
However, sometimes this isn't working because of states issues.
I wonder if there is an easiest way by forbidding maybe an audioplayer to be play when the user is on another page ?
late AudioPlayer audioplayer;
#override
void initState() {
super.initState();
audioplayer = AudioPlayer(playerId: 'liked_musics');
audioplayer.onDurationChanged.listen((Duration d) {
setState(() => duree = d);
});
audioplayer.onAudioPositionChanged.listen((Duration d) {
setState(() => position = d);
});
audioplayer.onPlayerCompletion.listen((event) {
setState(() {
position = duree;
statut = PlayerState.STOPPED;
});
});
}
#override
dispose() {
audioplayer.stop();
}
Thank you for your help
Joffrey

You can use event bus package
Sample pseudo code:
class AudioStopEvent {
int clickedTabPosition;
AudioStopEvent(this.clickedTabPosition);
}
class BusHelper {
static EventBus eventBus = new EventBus();
}
class YourTabPage{
#override
void initState() {
super.initState();
BusHelper.eventBus.on<AudioStopEvent>().listen((event) {
if(event.clickedTabPosition != currentPagePosition)
audio.stop();
});
}
}
class YourTabManagementClass{
whenChangeTabPage(){
BusHelper.eventBus.fire(new AudioStopEvent(clickedPagePosition)); }
}

Related

Using Provider in Widget's initState or initialising life-cycle

So while learning Flutter, it seems that initState() is not a place to use Providers as it does not yet have access to context which must be passed. The way my instructor gets around this is to use the didChangeDependencies() life-cycle hook in conjunction with a flag so that any code inside doesn't run more than once:
bool _isInit = true;
#override
void didChangeDependencies() {
if (_isInit) {
// Some provider code that gets/sets some state
}
_isInit = false;
super.didChangeDependencies();
}
This feels like a poor development experience to me. Is there no other way of running initialisation code within a Flutter Widget that has access to context? Or are there any plans to introduce something more workable?
The only other way I have seen is using Future.delayed which feels a bit "hacky":
#override
void initState() {
Future.delayed(Duration.zero).then(() {
// Some provider code that gets/sets some state
});
super.initState();
}
I have implemented as follows inside didChangeDependencies
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (_isInit) {
setState(() {
_isLoading = true;
});
Provider.of<Products>(context).fetchAndSetProducts().then((_) {
setState(() {
_isLoading = false;
});
});
}
_isInit = false;
}
It's possible to schedule code to run at the end of the current frame. If scheduled within initState(), it seems that the Widget is fully setup by the time the code is running.
To do so, you can use the addPostFrameCallback method of the SchedulerBinding instance:
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) {
// Some provider code that gets/sets some state
})
}
You can also use WidgetsBinding.instance.addPostFrameCallback() for this. They both behave the same for the purpose of running code once after the Widget has been built/loaded, but here is some more detail on the differences.
Note: be sure to import the file needed for SchedulerBinding:
import 'package:flutter/scheduler.dart';
you can have the Provider in a separate function and call that function within the initState()
bool isInit = true;
Future<void> fetch() async {
await Provider.of<someProvider>(context, listen: false).fetch();
}
#override
void initState() {
if (isInit) {
isInit = false;
fetch();
}
isInit = false;
super.initState();
}

Managing multiple setState() methods in single class?

I am creating a timer when it will come to 0. It will navigate to the next Page.(Not created navigation yet). Till the timer is on user can answer the questions. The problem is I want to start the timer as the StartGame() file opens. In main.dart I have created a navigation for this on click on button. But when this page loads it automatically calls both ( in void getNum() and startTime() ) setState methods() one by one. Therefore answers (options) that I created as buttons automatically changes without onPressed by user. But I only want startTime() method to be called once as widget builds / inits. then after the getNum() method on every click of user.
How do i make these two setState() methods to be called individually without affecting each other.
class StartGame extends StatefulWidget {
StartGameState createState() => StartGameState();
}
class StartGameState extends State<StartGame> {
int no1, no2, no3, no4, inp1, inp2;
int pos;
int res;
List<int> answers;
GenerateQuestion g = new GenerateQuestion();
void getNum() {
answers = g.generateNum();
pos = g.answerPosition();
inp1 = g.generateValue();
inp2 = g.generateValue();
res = inp1 + inp2;
answers.add(res);
answers.shuffle();
setState(() {
no1 = answers[0];
no2 = answers[1];
no3 = answers[2];
no4 = answers[3];
print("set state 1 called");
});
} // void get Num ends here
//counter timer starts from herer
int counter = 10;
Timer timer;
void startTime() {
counter = 10;
timer = new Timer.periodic(Duration(seconds: 1), (timer) {
if (counter > 0) {
setState(() {
counter--;
print("set state 2 called");
});
} else {
timer.cancel();
}
});
}
You can achieve this using the initState() method, it gets only called once when the widget is built.
class _StartGameState extends State<StartGame> {
#override
void initState() {
startTime(); //this function only gets called once
super.initState();
}
//your Code...

Need Help updating chart data in flutter at every x amount of seconds

So I'm using the Sparkline library for flutter to create a line chart and it works when I use a static list Eg. ([0, 10, 20, 20, 30]). But I want to make it so that at every say 10 seconds it would add a value, for now that could be anything but later i want to pull that value from firebase.
I've looked at other examples of people trying to run a function multiple time init state but it isnt working. I know I need to redraw the widget but I don't think I'm doing it right or I'm missing something.
class BikeState extends State<BikeScreen> {
Timer timer;
List<double> speedList = new List();
Sparkline speedChart1 = Sparkline(data: [0],);
void updateChart(Timer timer){
speedChart1 = Sparkline(data: speedList,);
speedList.add(10);
speedChart1 = Sparkline(data: speedList,);
}
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 15), updateChart);
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
All that happens when i run it is that I get the just the graph which has the values passed into it when it was declared and nothing was changed.
Flutter will not repaint or rebuild any widgets for you on its own. In your case you already have a stateful widget, which is the correct way, but you also need to tell flutter that your state has changed. This is done by using the setState method like this:
void updateChart(Timer timer) {
setState(() {
speedChart1 = Sparkline(data: speedList,);
speedList.add(10);
speedChart1 = Sparkline(data: speedList,);
});
}
Additionally, depending on how your build method looks, I think you should remove the Sparkline Widget from your state and have it purely during build like this:
class BikeState extends State<BikeScreen> {
Timer timer;
List<double> speedList = new List();
void updateChart(Timer timer){
setState(() {
speedList.add(10);
});
}
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 15), updateChart);
}
#override
void dipose() {
timer?.dipose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Sparkline(data: speedList);
}
}

How to pop screen using Mobx in flutter

I have a Food object that contains properties like name, id, calories, etc. With a series of screens, the user populates the food object properties.
Once done, the user can press the submit button, that will call the addFood method in the store.
The problem is, after uploading the food to the server, i want to pop the screen or show error message in toast based on the response. I just don't know how to do this.
Following is my code (only the important bits):
FoodDetailStore.dart
class FoodDetailStore = _FoodDetailStore with _$FoodDetailStore;
abstract class _FoodDetailStore with Store {
Repository _repository;
Food _food;
#observable
String msg = '';
// ... Other Observables and actions
#action
addFood(bool toAdd) {
if (toAdd) {
_repository.addFood(food).then((docId) {
if (docId != null) {
// need to pop the screen
}
}).catchError((e) {
// show error to the user.
// I tried this, but it didn't work
msg = 'there was an error with message ${e.toString()}. please try again.';
});
}
// .. other helper methods.
}
FoodDetailScreen.dart (Ignore the bloc references, I am currently refactoring code to mobx)
class FoodDataScreen extends StatefulWidget {
final String foodId;
final Serving prevSelectedServing;
final bool fromNewRecipe;
FoodDataScreen({#required this.foodId, this.prevSelectedServing, this.fromNewRecipe});
#override
_FoodDataScreenState createState() => _FoodDataScreenState(
this.foodId,
this.prevSelectedServing,
this.fromNewRecipe,
);
}
class _FoodDataScreenState extends State<FoodDataScreen> {
final String foodId;
final Serving prevSelectedServing;
final bool fromNewRecipe;
FoodDataBloc _foodDataBloc;
_FoodDataScreenState(
this.foodId,
this.prevSelectedServing,
this.fromNewRecipe,
);
FoodDetailStore store;
#override
void initState() {
store = FoodDetailStore();
store.initReactions();
store.initializeFood(foodId);
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
// I know this is silly, but this is what i tried. Didn't worked
Observer(
builder: (_) {
_showMsg(store.msg);
}
);
}
#override
Widget build(BuildContext context) {
return Container(
// ... UI
);
}
_popScreen() {
_showMsg('Food Added');
Majesty.router.pop(context);
}
_showMsg(String msg) {
Fluttertoast.showToast(msg: msg);
}
#override
void dispose() {
store.dispose();
super.dispose();
}
}
Constructing an Observer instance inside the didChangeDependencies() is indeed "silly" as you have rightly noted already :)
Observer is a widget and widget needs to be inserted into the widgets tree in order to do something useful. In our case non-widget Mobx reactions come to the rescue.
I will show how I did it in my code for the case of showing a Snackbar upon observable change so you will get an idea how to transform your code.
First of all, import import 'package:mobx/mobx.dart';.
Then in the didChangeDependencies() create a reaction which will use some of your observables. In my case these observables are _authStore.registrationError and _authStore.loggedIn :
final List<ReactionDisposer> _disposers = [];
#override
void dispose(){
_disposers.forEach((disposer) => disposer());
super.dispose();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_authStore = Provider.of<AuthStore>(context);
_disposers.add(
autorun(
(_) {
if (_authStore.registrationError != null)
_scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text(_authStore.registrationError),
backgroundColor: Colors.redAccent,
duration: Duration(seconds: 4),
),
);
},
),
);
_disposers.add(
reaction(
(_) => _authStore.loggedIn,
(_) => Navigator.of(context).pop(),
),
);
}
I use two types of Mobx reactions here: autorun and reaction. autorun triggers the first time immediately after you crate it and then every time the observable changes its value. reaction does not trigger the first time, only when the observable change.
Also pay attention to dispose the created reactions in the dispose() method to avoid resources leak.
Here is a code of my Mobx store class with used observables to complete the picture:
import 'package:mobx/mobx.dart';
import 'dart:convert';
part "auth_store.g.dart";
class AuthStore = AuthStoreBase with _$AuthStore;
abstract class AuthStoreBase with Store{
#observable
String token;
#observable
String registrationError;
#observable
String loginError;
#action
void setToken(String newValue){
token = newValue;
}
#action
void setRegistrationError(String newValue){
registrationError = newValue;
}
#action
void setLoginError(String newValue){
loginError = newValue;
}
#action
void resetLoginError(){
loginError = null;
}
#computed
bool get loggedIn => token != null && token.length > 0;
#action
Future<void> logOut() async{
setToken(null);
}
}

initialize data once in initState and call the setState when data is ready causes exception

Since flutter calls the build method many times in different condition, to avoid getting the data many times, I initialize the data in initState.
I want to re-build the widget when the data is ready.
Here is my code :
class Test extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<Test> {
Data data;
bool dataReady = false;
#override
void initState() {
super.initState();
getData(context).then((Data data) async {
setState(() {
dataReady= true;
});
});
}
#override
Widget build(BuildContext context) {
if (dataReady) {
return createMainContent(context);
} else {
return new Container();
}
}
}
However, it results in following exception :
inheritFromWidgetOfExactType(_InheritedProvider) or inheritFromElement() was called before _TestState.initState() completed.
May I know am I doing something wrong here?
When I add the following line to implementation of getData(context)
await Future.delayed(new Duration(milliseconds: 300));
the exception does not happen.
For everyone coming here at a later point
It is best to use the #override void didChangeDependencies () method of the State class.
From the docs
This method is also called immediately after initState. It is safe to call BuildContext.inheritFromWidgetOfExactType from this method.
But make sure to check if you have already performed your initialization
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (bloc == null) { // or else you end up creating multiple instances in this case.
bloc = BlocProvider<MyBloc>.of(context);
}
}
Edit: Better answer below.
Apparently, you cannot access getData(context) during initState (more concrete: before it completed).
The reason, so I believe, is that getData tries to look up an InheritedWidget ancestor up in the tree, but the tree is just now being built (your widget is created during the parent widget's build).
The obvious solution would be to delay getData's lookup to a later point in time. There are several ways to achieve that:
Delay the lookup to a later time. scheduleMicrotask should work fine.
Look it up during the first build call. You could have an isInitialized field set to false and in you build, something like:
if (!isInitialized) {
isInitialized = true;
// TODO: do the getData(...) stuff
}
an alternative is to put it inside PostFrameCallback which is between initState and Build.
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) => getData());
super.initState();
}
getData() async {
}
I moved my code to my build method from initState and it worked
class _TestState extends State<Test> {
Data data;
bool dataReady = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
getData(context).then((Data data) async {
setState(() {
dataReady= true;
});
});
if (dataReady) {
return createMainContent(context);
} else {
return new Container();
}
}
}