it seems like I can't pause, stop nor dispose the player in web (chrome,firefox,safari).
It loads and plays allright.
It just keeps playing. Another play adds a layer of sound...
Only a reload of the page stops the cacaphony.
Any Ideas?
final AudioPlayer player = AudioPlayer();
Future preload () async {
await player.setAsset('asset/audio/mysound.mp3');
}
Future<void> playSound() async {
return player.play();
}
Future<void> pauseSound() async {
return player.pause();
}
Future<void> stopSound() async {
return player.stop();
}
Future<void> disposeSound() async {
return player.dispose();
}
Related
I am creating a game where, after a user signs in, I want to send their playerID to my backend. Since this is in SwiftUI, I have the following (btw I know we're not supposed to be using playerID anymore but this is just a minimal reproducible example):
import SwiftUI
import GameKit
struct SampleView: View {
let localPlayer = GKLocalPlayer.local
func authenticateUser() async {
localPlayer.authenticateHandler = { vc, error in
guard error == nil else {
print(error?.localizedDescription ?? "")
return
}
if localPlayer.isAuthenticated {
let playerID = localPlayer.playerID
GKAccessPoint.shared.isActive = localPlayer.isAuthenticated
// here is where I would like to make an async call
}
}
}
var body: some View {
VStack {
Text("Sample View")
}
.task {
await authenticateUser()
}
}
}
struct SampleView_Previews: PreviewProvider {
static var previews: some View {
SampleView()
}
}
In the comment indicating where I'd like to place an async call, I have tried something like
await myBackendCall(playerID)
but this throws the error
Invalid conversion from 'async' function of type '(UIViewController?, (any Error)?) async -> Void' to synchronous function type '(UIViewController?, (any Error)?) -> Void'
which makes sense given that the authenticateHandler function isn't an async function.
What is the best approach here? I'd like to wait until I have the value for PlayerID, and then call await myBackendCall(playerID). Any advice here would be much appreciated, thank you!
To make a completion handler async use a continuation, it returns true if the user is authenticated, otherwise false.
func authenticateUser() async -> Bool {
return await withCheckedContinuation { continuation in
localPlayer.authenticateHandler = { vc, error in
if let error {
print(error.localizedDescription)
continuation.resume(returning: false)
} else {
continuation.resume(returning: localPlayer.isAuthenticated)
}
}
}
}
and in the task scope write
.task {
let isAuthenticated = await authenticateUser()
if isAuthenticated {
let playerID = localPlayer.playerID
GKAccessPoint.shared.isActive = localPlayer.isAuthenticated
// here is where I would like to make an async call
}
}
When you have a callback closure (like authenticateHandler), it invariably means that the closure may possibly be called multiple times. The appropriate async-await pattern would be an AsyncSequence (e.g., an AsyncStream or an AsyncThrowingStream).
So, you might wrap authenticateHandler in an asynchronous sequence, like so:
func viewControllers() -> AsyncThrowingStream<UIViewController, Error> {
AsyncThrowingStream<UIViewController, Error> { continuation in
GKLocalPlayer.local.authenticateHandler = { viewController, error in
if let viewController {
continuation.yield(viewController)
} else {
continuation.finish(throwing: error ?? GKError(.unknown))
}
}
}
}
Then you could do things like:
.task {
do {
for try await _ in viewControllers() {
GKAccessPoint.shared.isActive = GKLocalPlayer.local.isAuthenticated
// do your subsequent `async` call here
}
} catch {
GKAccessPoint.shared.isActive = false
print(error.localizedDescription)
}
}
For more information, see WWDC 2021 video Meet AsyncSequence. But the idea is that withCheckedContinuation (or withThrowingCheckedContinuation) is designed for completion handler patterns, where it must be called once, and only once. If you use a checked continuation and the closure is called again, it will be “logging correctness violations”, because “You must call a resume method exactly once on every execution path throughout the program.”
Instead, in cases where it may be called multiple times, consider handling it as an asynchronous sequence.
Hi the plugin i am using are firebase_messaging: ^14.1.2 and flutter_local_notifications: ^12.0.4
Firstly it works fine with android however in ios i have no notification,
I set up my AppDelegate.swift as follows
import UIKit
import Flutter
import background_locator_2
import flutter_local_notifications
func registerPlugins(registry: FlutterPluginRegistry) -> () {
if (!registry.hasPlugin("BackgroundLocatorPlugin")) {
GeneratedPluginRegistrant.register(with: registry)
}
}
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
BackgroundLocatorPlugin.setPluginRegistrantCallback(registerPlugins)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
nextly in my main.dart
final FlutterLocalNotificationsPlugin notificationsPlugin =
FlutterLocalNotificationsPlugin();
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Handling a background message ${message.messageId}');
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
AndroidInitializationSettings androidSettings =
AndroidInitializationSettings("#mipmap/ic_launcher");
DarwinInitializationSettings iosSettings = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestCriticalPermission: true,
requestSoundPermission: true);
InitializationSettings initializationSettings =
InitializationSettings(android: androidSettings, iOS: iosSettings);
bool? initialized = await notificationsPlugin.initialize(
initializationSettings, onDidReceiveNotificationResponse: (response) {
log(response.payload.toString());
});
log("Notifications: $initialized");
await Firebase.initializeApp();
currentFirebaseUser = FirebaseAuth.instance.currentUser;
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
await UserSimplePrefences.init();
//await FirebaseAuth.instance.currentUser;
runApp(MyApp());
}
This is where I handle the messages and move to other window and do stuffs
class PushNotificationService {
FirebaseMessaging firebasemessaging = FirebaseMessaging.instance;
Future initialize(context) async {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification? notification = message
.notification; //assign two variables for remotenotification and android notification
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null) {
retrieveRideRequestInfo(getRideRequestId(message.data)!, context);
notificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
iOS: DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
),
android: AndroidNotificationDetails("Driver", "You have a new ride",
priority: Priority.max, importance: Importance.max),
),
);
print('AndroidNotification after clicking ' + notification.title!);
print('AndroidNotification after clicking ' + notification.body!);
print("This is message map");
//getRideRequestId(message);
}
});
//on message open
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
// same as above
RemoteNotification? notification = message
.notification; //assign two variables for remotenotification and android notification
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null) {
// if (doublenotification.isOdd)
retrieveRideRequestInfo(getRideRequestId(message.data)!, context);
print('AndroidNotification after clicking ' + notification.title!);
print('AndroidNotification after clicking ' + notification.body!);
//getRideRequestId();
}
//}
});
}
}
In android it works fine however in IOS, there is absolutely nothing happening, no logs ,notifications or error, what am I missing here ?
// FirstScreen
final result = await Get.to(SecondScreen());
//SecondScreen
//First button onTap
Get.back(result: 10)
//second buton onTap
Get.back(result: true);
How can I identify the result is bool or int type when back to the second screen to the first screen.
You can check it with is like this:
if (result is int) {
print("result is int");
}
else if (result is bool) {
print("result is bool");
}
I want to keep firing a function 5 seconds after it completes.
Previously I would use this at the end of the function:
Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { self.function() }
But I am wanting to use Swift 5.5's async/await.
If I use something like this:
func loadInfo() async {
async let info = someOtherAsyncFunc()
self.info = try? await info
await Task.sleep(5_000_000_000)
await loadInfo()
}
I get a warning that the Function call causes an infinite recursion and it's not really cancellable.
This compiles fine:
func loadInfo() async {
Task {
async let info = someOtherAsyncFunc()
self.info = try? await info
await Task.sleep(5_000_000_000)
if Task.isCancelled {
print("Cancelled")
}
else
{
print("Not cancelled")
await loadInfo()
}
}
}
and although it does fire every 5 seconds, it keeps running when my SwiftUI view is dismissed.
I start it using:
.onAppear {
loadInfo()
}
As it's all running on the same Task and not detached should it not all cancel when the view is removed?
What is the modern way to achieve this with async/await?
You can save the task in a #State variable, and then cancel it when the view disappears with onDisappear(perform:).
Working example:
struct ContentView: View {
#State private var info: String?
#State private var currentTask: Task<Void, Never>?
var body: some View {
NavigationView {
VStack {
Text(info ?? "None (yet)")
.onAppear(perform: loadInfo)
.onDisappear(perform: cancelTask)
NavigationLink("Other view") {
Text("Some other view")
}
}
.navigationTitle("Task Test")
}
.navigationViewStyle(.stack)
}
private func loadInfo() {
currentTask = Task {
async let info = someOtherAsyncFunc()
self.info = try? await info
await Task.sleep(5_000_000_000)
guard !Task.isCancelled else { return }
loadInfo()
}
}
private func cancelTask() {
print("Disappear")
currentTask?.cancel()
}
private func someOtherAsyncFunc() async throws -> String {
print("someOtherAsyncFunc ran")
return "Random number: \(Int.random(in: 1 ... 100))"
}
}
The point of async await is to let you write asynchronous code in a synchronous way. So you could remove the recursive function and simply write:
.task {
repeat {
// code you want to repeat
print("Tick")
try? await Task.sleep(for: .seconds(5)) // exception thrown when cancelled by SwiftUI when this view disappears.
} while (!Task.isCancelled)
print("Cancelled")
}
I noticed the print tick is always on main thread however if I move it out to its own async func then it correctly runs on different threads.
I have the following subscription:
class CheckInternet with ChangeNotifier {
StreamSubscription<ConnectivityResult> _subscription;
bool haveInternet = false;
static CheckInternet _instance;
static CheckInternet getInstance() {
if (_instance == null) {
_instance = CheckInternet();
_instance.checkConnectivity();
}
return _instance;
}
void checkConnectivity() {
if (_subscription == null) {
_subscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
bool res = result == ConnectivityResult.mobile ||
result == ConnectivityResult.wifi;
setHaveInternet = res;
});
}
}
I create this subscription in my main() method and while the app is open, it is checking the internet connection. My problem is that how to make a method what will cancel the subscription when the user closes the app.
I need something like the dispose() is for widgets.