Flutter popUntil throws Bad state: Future already completed [duplicate] - dart

This question already has answers here:
Using Navigator.popUntil and route without fixed name
(2 answers)
Closed 3 years ago.
I have screens A->B->C->D
In B, C, D screens there is a button that should take you to screen A keeping it's state (thus pushNamedAndRemoveUntil isn't appropriate here).
I want to use popUntil, that's how I do it, based on docs:
Navigator.popUntil(context, ModalRoute.withName(ScreenName.mainScreen));
I get an error:
Bad state: Future already completed
Here is my main:
void main() {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
final pages = {
ScreenName.mainScreen: (settings) => MaterialPageRoute(
builder: (context) => MainScreen(), settings: settings),
};
var configureApp = AppConfig(
appName: 'TaskerMate',
flavorName: FLAVOR_NAME.PROD,
child: AppModelProvider(
model: AppModel(),
child: MaterialApp(
theme: TMTheme().get(),
home: SplashScreen(),
onGenerateRoute: (settings) {
pages[settings.name](settings);
},
routes: {ScreenName.mainScreen: (context) => MainScreen()},
),
),
);
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((LogRecord rec) {
print('${rec.level.name}: ${rec.time}: ${rec.message}');
});
runApp(configureApp);
}
ScreenName.mainScreen -> static final String mainScreen = '/main';

Took me ages to find the answer, but if anyone finds themselves stuck with this problem, RĂ©mi Rousselet's answer to another question is what solved it for me:
Navigator.pushReplacement(
context,
MaterialPageRoute(settings: RouteSettings(name: "Foo")),
);
Add settings to MaterialPageRoute with name, then call popUntil like so:
Navigator.popUntil(context, ModalRoute.withName("Foo"))

Related

iOS Web App won't play delayed audio but directly playing works. Only on iOS devices running as web app

I'm encountering this very odd behavior on my Flutter Web app when running on iOS device (simulator or real device), where audio is not being played when playing it delayed.
In the following code you can see I'm playing a sound effect after delaying it by a few seconds with Future.delayed. This is to implement a countdown timer that plays a tick sound in the last three seconds.
This works everywhere except as a web app when running on iOS devices, it works even as an iOS app. You can test it on an iOS simulator, just start the app with flutter run -d web-server, then open the localhost in the safari app on the simulator. You will see that no sound is being played.
I have tried different audio packages, they all have the same behavior. It works when I remove the initial await Future.delayed(Duration(seconds: 7)); but I really need the delay to work.
Any kind of help or explanation for this behavior is very appreciated.
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:just_audio/just_audio.dart' as justAudio;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final player = AudioPlayer();
AssetSource tick5 = AssetSource("sounds/tick5.mp3");
void playAudio() async {
await Future.delayed(Duration(seconds: 7));
await player.play(tick5, volume: 1);
await Future.delayed(Duration(seconds: 1));
await player.play(tick5, volume: 1);
await Future.delayed(Duration(seconds: 1));
await player.play(tick5, volume: 1);
}
void playAudioJust() async {
justAudio.AudioPlayer player = justAudio.AudioPlayer();
await Future.delayed(Duration(seconds: 7));
player.setAsset("sounds/tick5.mp3");
player.play();
await Future.delayed(Duration(seconds: 1));
player.setAsset("sounds/tick5.mp3");
player.play();
await Future.delayed(Duration(seconds: 1));
player.setAsset("sounds/tick5.mp3");
player.play();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
TextButton(onPressed: playAudio, child: Text("Audioplayers")),
TextButton(onPressed: playAudioJust, child: Text("JustAudio")),
],
),
);
}
}
My pubspec.yaml
name: empty_flutter_project
description: A new Flutter project.
version: 1.0.0+1
environment:
sdk: '>=2.18.4 <3.0.0'
dependencies:
flutter:
sdk: flutter
audioplayers:
just_audio:
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
assets:
- assets/sounds/
iOS, both with Chrome and Safari don't allow sounds unless initiated by a user action. The delay kills the sound.
You need to start the sound on a user action, then create a listener, and pause as soon as it starts. Then you can resume the sound later. It's asynchronous, so you need the listener.
player.onPlayerStateChanged.listen((PlayerState s) => {
//check for state playing, pause if so.
});
when time expires: resume.

how to send parametrs in signal r with several requests?

I have several separate requests to my websockets and I wanted to get data from all of these using parametrs. AnimalID in getImageAnimal - parametrs for getting image of fixed animals. here is requests I am trying to make
final hubConnection1 = HubConnectionBuilder()
.withUrl(
'http://10.10:8000/animalHub',
options: httpConnectionOptions,
)
.build();
await hubConnection1.start();
List fixationAnimals = [];
if (hubConnection1.state == HubConnectionState.Connected) {
await hubConnection1
.invoke('GetAnimalFixations')
.then((value) => fixation = value as List)
.then((value1) => hubConnection1.invoke('GetImageAnimal',
args: [imageId ?? '']).then((value2) => imageId == value2));
}
hubConnection1.onclose(({error}) {
logger?.finer(error);
});
print(imageId);
print(fixation);
return fixation;
in GetAnimalFixations I have such field:
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"animalId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string",
"fixedTimeAnimal": "2022-08-23T07:25:29.898Z",
"imageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
]
I found out how to do it. Everything i post here is just for testing pupose, just to make sure it is really works. Please do not write like below (as I know it is a bad decision to manipulate FutureBuilder as I did below, but it is ONLY for testing purpose)
I have several requests and one of them are depended on the other one.
GetAnimalFixations (the main request)-> GetImageAnimal (depended one, where I should put arguments imageId from the first request)
STEP 1
Create two separate requests with methods above like that:
Future<List> fetchAnimalFixation() async {
final hubConnection1 = HubConnectionBuilder()
.withUrl(
'http://10.10:8000/animalHub',
options: httpConnectionOptions,
)
.build();
await hubConnection1.start();
List fixationAnimals = [];
if (hubConnection1.state == HubConnectionState.Connected) {
await hubConnection1
.invoke('GetAnimalFixations')
.then((value) => fixation = value as List);
}
hubConnection1.onclose(({error}) {
logger?.finer(error);
});
print(fixation);
return fixation;
}
Second request:
Future<String> fetchAnimalImage(String imageId) async {
final hubConnection1 = HubConnectionBuilder()
.withUrl(
'http://10.10:8000/animalHub',
options: httpConnectionOptions,
)
.build();
await hubConnection1.start();
String image = [];
if (hubConnection1.state == HubConnectionState.Connected) {
await hubConnection1
.invoke('GetImageAnimal', args[imageId])
.then((value) => image= value as String);
}
hubConnection1.onclose(({error}) {
logger?.finer(error);
});
print(image);
return image;
}
STEP 2
First preparation is over, now we a go into the second steps to achive our goal:
You need to create the screen where all data will be displayed. For my testing purpose (again: ONLY for testing and small piece of data) I use future builder:
body: FutureBuilder<List>(
future: AllFixation().fetchAnimalFixation(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: 2,
itemBuilder: (context, index) {
return Card(
child: Column(
children: [
Text(snapshot.data?[index]['name'].toString() ?? ''),
FutureBuilder<String?>(
future: AllFixation().fetchAnimalImage(
snapshot.data?[index]['imageId'].toString() ??
''),
builder: (context, snapshotImage) {
Uint8List bytes =
base64.decode(snapshotImage.data ?? '');
return Image.memory(bytes);
}),
Voila! All is ready. You can use and enjoy the picture and the name of the animal. All this via websocket and using signalR package. I higly recommend to use some state management and think that you are going to do next, because it is the solid piece of code and if someday it will need to change, it turn out to be the biggest pain.

Flutter google mobile ads only shows test ads on IOS

I have the following ad Repository
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:my_app/data/api/constants.dart';
class AdMobRepository {
late String liveBannerAdId;
late String liveInterstitualAdId;
late String liveRewardedAdId;
AdMobRepository() {
if (Platform.isAndroid) {
liveBannerAdId = Constants.androidBannedAdId;
liveInterstitualAdId = Constants.androidInterstitualAdId;
liveRewardedAdId = Constants.androidRewardedAdId;
} else if (Platform.isIOS) {
liveBannerAdId = Constants.iosBannerAdId;
liveInterstitualAdId = Constants.iosInterstitualAdId;
liveRewardedAdId = Constants.iosRewardedAdId;
} else {
liveBannerAdId = "";
liveInterstitualAdId = "";
liveRewardedAdId = "";
}
}
BannerAd getBannerAd({
required AdSize size,
void Function(Ad, LoadAdError)? onFailedLoad,
void Function(Ad)? onLoad,
void Function(Ad)? onAdOpened,
void Function(Ad)? onAdImpression,
}) {
return BannerAd(
adUnitId: kReleaseMode ? liveBannerAdId : BannerAd.testAdUnitId,
request: AdRequest(),
size: size,
listener: BannerAdListener(
onAdFailedToLoad: onFailedLoad ?? onFailedLoadFallback,
onAdLoaded: onLoad,
onAdImpression: onAdImpression,
onAdOpened: onAdOpened,
),
);
}
void onFailedLoadFallback(Ad ad, LoadAdError error) {
ad.dispose();
}
void getInterstitualAd({required void Function(LoadAdError) onFailedLoad, void Function(InterstitialAd)? onLoad}) {
InterstitialAd.load(
adUnitId: kReleaseMode ? liveInterstitualAdId : InterstitialAd.testAdUnitId,
request: AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: onLoad ?? onInterstitialAdLoadedFallback,
onAdFailedToLoad: onFailedLoad,
),
);
}
void onInterstitialAdLoadedFallback(InterstitialAd ad) {
ad.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) => ad.dispose(), onAdFailedToShowFullScreenContent: (ad, error) => ad.dispose());
}
void getRewardAd({required String userId, required void Function(LoadAdError) onFailedLoad, void Function(RewardedAd)? onLoad}) {
RewardedAd.load(
adUnitId: kReleaseMode ? liveRewardedAdId : RewardedAd.testAdUnitId,
request: AdRequest(),
rewardedAdLoadCallback: RewardedAdLoadCallback(
onAdLoaded: onLoad ?? onRewardedAdLoadedFallback,
onAdFailedToLoad: onFailedLoad,
),
serverSideVerificationOptions: ServerSideVerificationOptions(userId: userId),
);
}
void onRewardedAdLoadedFallback(RewardedAd ad) {
ad.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) => ad.dispose(), onAdFailedToShowFullScreenContent: (ad, error) => ad.dispose());
}
}
And I have the following widget for banner ads
class MyBannerAd extends StatefulWidget {
const MyBannerAd();
#override
_MyBannerAdState createState() => _MyBannerAdState();
}
class _MyBannerAdState extends State<MyBannerAd> {
late AdSize adSize;
late AdMobRepository adRepository;
late AnalyticsRepository analyticsRepository;
bool adLoaded = false;
BannerAd? anchoredBanner;
#override
void initState() {
super.initState();
adRepository = context.read<AdMobRepository>();
analyticsRepository = context.read<AnalyticsRepository>();
if (SizerUtil.deviceType != DeviceType.mobile && SizerUtil.orientation == Orientation.portrait) {
adSize = AdSize.leaderboard;
} else {
adSize = AdSize.largeBanner;
}
final bannerAd = adRepository.getBannerAd(
size: adSize,
onFailedLoad: (ad, error) {
print('banner ad failed to load: $error');
ad.dispose();
},
onLoad: (ad) {
setState(() {
adLoaded = true;
anchoredBanner = ad as BannerAd?;
});
},
onAdImpression: (_) {
analyticsRepository.sendBannerAdShownEvent();
},
onAdOpened: (_) {
analyticsRepository.sendBannerAdClickEvent();
},
);
bannerAd.load();
}
#override
void dispose() {
super.dispose();
anchoredBanner?.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<SubscriptionBloc, SubscriptionState>(
builder: (context, state) {
final isLoaded = !adLoaded;
if (isLoaded || state.hasSubscribed || anchoredBanner == null) return SizedBox.shrink();
return Container(
color: Colors.transparent,
width: anchoredBanner!.size.width.toDouble(),
height: anchoredBanner!.size.height.toDouble(),
child: Center(
child: Container(
color: Colors.white,
child: AdWidget(
ad: anchoredBanner!,
),
),
),
);
},
);
}
}
But on IOS it is always showing test ads. How can this be when the app is built with flutter release mode with flutter build ios --release? The app is currently in review and I was thinking that these ads would stop being test ads whenever it is live on the app store.
But Apple sent us the following message
We noticed that your app or its screenshots include test
advertisements. Apps or metadata items that include features that are
for test or demonstration purposes are not appropriate for the App
Store.
Next Steps
To resolve this issue, please revise your app to complete, remove, or
fully configure any partially implemented features. Please ensure your
screenshots do not include any images of demo, test, or other
incomplete content
So how do I get rid of the test ads? Did I miss some XCode setting or?
I am using
flutter: 2.5.3
google_mobile_ads: ^0.13.4
And I also added the GADApplicationIdentifier to my info.plist
<key>GADApplicationIdentifier</key>
<string>{here I have the app Id}</string>
And I am testing on a real device with the testflight build
Side Note:
In admob setting I have added the following test IDFA
00000000-0000-0000-0000-000000000000
which seems to work for Test ads on all IOS devices.
Turns out I needed to remove the 00000000-0000-0000-0000-000000000000 from the test settings on admob. I no longer recieve test ads after that, but I do recieve ads in the release build now.
You don't need to make any code changes.
Next Steps
To resolve this issue, please revise your app to complete, remove, or
fully configure any partially implemented features. Please ensure
your screenshots do not include any images of demo, test, or other
incomplete content
To resolve above rejection, all you need to do is remove banner advertisement from your screenshots and submit for approval again.

How to display version number instantly

I want to have the app version number displayed in my settings when the settings page is opened. My issue is that I learned that has to be done asynchronously. How do I get the version number and display it in a Text once it gets it?
My code that returns a future:
Future<String> getVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
return packageInfo.version;
}
I want to display the version here:
ListTile(
title: Text("Version"),
subtitle: Text("1.0.0"), //replace with getVersion()
),
Use package_info to get the version of the app.
make state variable with dummy initialization and in initState make a function call to get the version value and update the state variable which is initialized with the dummy value
My working code here
//initialize dummy value
PackageInfo _packageInfo = new PackageInfo(
appName: 'Unknown',
packageName: 'Unknown',
version: 'Unknown',
buildNumber: 'Unknown',
);
#override
void initState() {
super.initState();
//get package details
_initPackageInfo();
}
Future<Null> _initPackageInfo() async {
final PackageInfo info = await PackageInfo.fromPlatform();
setState(() {
_packageInfo = info;
});
}
Render list tile view as
new ListTile(
title: new Text('${_packageInfo.version}'),
leading: const Icon(
FontAwesomeIcons.codeBranch,
size: 20.0,
),
),
you view will show somthing like icon 1.0.0 which is specified as version in your package.
Hope it helps you. let me know if not

Flutter SharedPreference do not persist

I want to save user preferences using Flutter's SharedPreference. But the registered preferences are ALL null at new start (when app have been closed, not unistalled).
settings.dart :
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingsPage extends StatefulWidget{
#override
_SettingsPageState createState() => new _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage>{
bool _param1IsON;
bool _param2IsON;
bool _param3IsON;
#override
void initState() {
super.initState();
_param1IsON = false;
_param2IsON = false;
_param3IsON = false;
loadPreferences();
}
Future<Null> loadPreferences() async {
SharedPreferences _preferences = await SharedPreferences.getInstance();
if(_preferences.getBool('setted') == null || !_preferences.getBool('setted'))
SharedPreferences.setMockInitialValues({}); // Library fix line
bool param1IsON = _preferences.getBool('param1IsON');
bool param2IsON = _preferences.getBool('param2IsON');
bool param3IsON = _preferences.getBool('param3IsON');
setState(() {
_param1IsON = (param1IsON != null)? param1IsON : _param1IsON;
_param2IsON = (param2IsON != null)? param2IsON : _param2IsON;
_param3IsON = (param3IsON != null)? param3IsON : _param3IsON;
});
_preferences.setBool('setted', true);
}
Future<Null> setBoolSettings(String key, bool value) async {
switch(key){
case 'param1IsON':
setState(() {
_param1IsON = value;
});
break;
case 'param2IsON':
setState(() {
_param2IsON = value;
});
break;
case 'param3IsON':
setState(() {
_param3IsON = value;
});
break;
default:
print("Unknown settings '$key'");
}
SharedPreferences _preferences = await SharedPreferences.getInstance();
await _preferences.setBool(key, value);
}
#override
Widget build(BuildContext context) {
return new ListView(
children: <Widget>[
new ListTile(
title: new Text(Param 1'),
trailing: new Switch(value: _param1IsON,
onChanged: (bool newValue) {
setBoolSettings('param1IsON', newValue);
}),
),
new ListTile(
title: new Text('Param 2'),
trailing: new Switch(value: _param2IsON,
onChanged: (bool newValue) {
setBoolSettings('param2IsON', newValue);
}),
),
new ListTile(
title: new Text('Param 3'),
trailing: new Switch(value: _param3IsON,
onChanged:
(bool newValue) {
setBoolSettings('param3IsON', newValue);
}),
),
]
);
}
}
What I get:
At lunch 3 parameters are false. If I turn 'ON' one of them, wait 2s (it is not an async problem), then close the app and Start again... All of my parameters are false.
What I want:
At lunch 3 parameters are false. I turn 'ON' one of them. I close the app. Start again. The previous param I turned 'ON' is still true.
I had the same issue and fixed it in Android/../MainActivity.java by adding at the top:
import io.flutter.plugins.GeneratedPluginRegistrant;
As well as under super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
The problem comes from using
SharedPreferences.setMockInitialValues({});
I got it from Flutter Test MissingPluginException but it seems to clear all the shared preferences.
However if you remove SharedPreferences.setMockInitialValues({}); and you don't have the two lines above in MainActivity.java, you'll get:
MissingPluginException(No implementation found for method getAll on
channel flutter: plugins.flutter.io/shared_preferences)
I hope it helps!
Hi I also faced the same issue. Did so many things. nothing helped .This may help someone.First thing ,followed this url and did the changes
1.https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects
2.Run the command
flutter upgrade
3.Changed the shared preferences plugin in pubspec.yaml file
shared_preferences: ">=0.5.10 <2.0.0"
4.Deleted the pub cache from installed flutter location
C:\Users\myuser\AppData\Local\Pub\Cache\hosted\pub.dartlang.org
5.flutter build apk --debug
6.flutter build apk --profile
7.flutter run --release (if I run directly this command its throwing error like debug.jar not found , so I ran command 5 and 6 )
Command 7 is for - To Verify whether its working perfectly in release mode.
Finally I tried to generate app build without shrinking the code. then it worked
flutter build apk --no-shrink
flutter build appbundle --no-shrink

Resources