Flutter Futures: How can I make this StompClient connection awaitable with Futures? - flutter

I am using StompClient to connect to my gameservers websocket endpoint. For the sake of using FutureBuilder of Flutter I'd like to have async/await features with this client.
Therefore I wrapped the StompClient in the following way:
class CombatClient {
late StompClient _client;
Future<bool> connect() async {
_client = StompClient(
config: StompConfig.SockJS(
url: '${ServerGlobals.backendHost}/connect',
onConnect: (frame) async {
_client.subscribe(destination: "/gameserver/foo", callback: (data) => {print("foo: ${data.body}")});
},
),
);
_client.activate();
return await Future.doWhile(() => _client.connected);
}
}
In my main logic I am using the whole like this:
final combatClient = CombatClient();
await combatClient.connect();
However this does not work because of the following error:
E/flutter (23147): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: type 'Null' is not a subtype of type 'FutureOr<bool>'
E/flutter (23147): #0 CombatClient.connect (package:app/combat/websocket/combat_client.dart:19:12)
E/flutter (23147): <asynchronous suspension>
E/flutter (23147): #1 _DebugCombatPageState.build.<anonymous closure> (package:app/debug/debug_combat_page.dart:61:27)
E/flutter (23147): <asynchronous suspension>
E/flutter (23147):
Regardless of the error I'd just love to know what the proper approach is. I always struggle when it comes to Futures etc. I can't find the right API usage to make my connection to the game server awaitable.

Quoted from #pskink
simply use Completer class instead of return await Future.doWhile(... - the docs say: "A way to produce Future objects and to complete them later with a value or error"

Related

In Flutter MaterialApp, using Provider inside LocaleResolutionCallback has an error

In my flutter project, I selected riverpod_hook for DI.
In order to change the language setting of the application according to the language setting of the mobile device, I wrote the code as follows.
// app.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class Application extends ConsumerWidget {
const Application({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
theme: defaultTheme,
localizationsDelegates: Resource.localizationsDelegates,
supportedLocales: Resource.supportedLocales,
localeResolutionCallback: (Locale? locale, Iterable<Locale> supportedLocales) {
if (supportedLocales.any((element) => locale?.languageCode.contains(element.toString()) == true)) {
ref.watch(localeCodeProvider.notifier).state = locale!.languageCode;
return locale;
}
return const Locale('ko', '');
},
routes: {
homePageRoutes: (_) => const HomePage(),
detailPageRoutes: (_) => const DetailPage(),
},
);
}
}
import 'package:hooks_riverpod/hooks_riverpod.dart';
final localeCodeProvider = StateProvider<String>((ref) => 'en');
When I run this code, this error was occurred.
E/flutter ( 3747): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: Tried to modify a provider while the widget tree was building.
E/flutter ( 3747): If you are encountering this error, chances are you tried to modify a provider
E/flutter ( 3747): in a widget life-cycle, such as but not limited to:
E/flutter ( 3747): - build
E/flutter ( 3747): - initState
E/flutter ( 3747): - dispose
E/flutter ( 3747): - didUpdateWidget
E/flutter ( 3747): - didChangeDepedencies
E/flutter ( 3747):
E/flutter ( 3747): Modifying a provider inside those life-cycles is not allowed, as it could
E/flutter ( 3747): lead to an inconsistent UI state. For example, two widgets could listen to the
E/flutter ( 3747): same provider, but incorrectly receive different states.
E/flutter ( 3747):
E/flutter ( 3747):
E/flutter ( 3747): To fix this problem, you have one of two solutions:
E/flutter ( 3747): - (preferred) Move the logic for modifying your provider outside of a widget
E/flutter ( 3747): life-cycle. For example, maybe you could update your provider inside a button's
E/flutter ( 3747): onPressed instead.
E/flutter ( 3747):
E/flutter ( 3747): - Delay your modification, such as by encasuplating the modification
E/flutter ( 3747): in a `Future(() {...})`.
E/flutter ( 3747): This will perform your upddate after the widget tree is done building.
I can see from the error message that the error occurred because the provider watch was used inside the build function.
However, I unwillingly need to utilize that localeResolutionCallback attribute because I want to set the language setting value of the mobile device and set the language across the application.
In this situation, could I get an appropriate solution on how to use the provider?
Try if calling WidgetsBinding.instance.addPostFrameCallback would work, somethign like this:
if (supportedLocales.any((element) => locale?.languageCode.contains(element.toString()) == true)) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.watch(localeCodeProvider.notifier).state =
locale!.languageCode;
});
return locale;
}
What it does - it allows your build to complete; and then changes the provider - which will most likely call another build.

Unhandled Exception: Null check operator used on a null value in flutter

I hope you are well, I will give you a bit of context about my problem...
I really don't know why this error happens and I've seen many similar publications but they haven't worked for me.
I'm new to flutter and I'm working on an app, but when the user wants to log out, the app crashes and shows the following error:
E/flutter (12659): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Null check operator used on a null value
E/flutter (12659): #0 StatefulElement.state
package:flutter/…/widgets/framework.dart:4999
E/flutter (12659): #1 Navigator.of
package:flutter/…/widgets/navigator.dart:2543
E/flutter (12659): #2 Navigator.pushReplacement
package:flutter/…/widgets/navigator.dart:2105
E/flutter (12659): #3 MainCoordinator.logoutNavigation
package:domicilios/…/MainCoordinator/MainCoordinator.dart:62
E/flutter (12659): #4 UserActions.signOut.<anonymous closure>.<anonymous closure>
package:domicilios/…/View/ProfileTab.dart:222
E/flutter (12659): <asynchronous suspension>
This is the code that generates this error, I hope you can help me, thank you very much for your attention.
this is the class:
extension UserActions on _ProfileTabState {
void signOut(BuildContext context) {
AlertView.showAlertDialog(
model: AlertViewModel(
context,
const AssetImage('assets/logout.png'),
'Cierre de sesión en curso',
"¿Desear salir de la sesión actual?",
'Cerrar sesión',
"Cancelar", () {
_profileTabViewModel
.signOut()
.then((value) => coordinator.logoutNavigation(context: context));
}, () {
Navigator.pop(context);
}));
}
}
That's how I call it in the code:
onTap: () => signOut(context),
This is most likely due to the widget being unmounted before navigating.
You can check if the widget is mounted before navigating.
if (mounted) {
_profileTabViewModel.signOut().then((value) {
if (mounted) {
coordinator.logoutNavigation(context: context);
}
});
}
Also,
if(mounted){
Navigator.pop(context);
}

Flutter platform player already exists just audio

So happy with the just audio flutter package from Ryan Heise for our radio app. Best package I know.
We have no problems with iOS, but on Android now some strange behavior, especially on Android 10. I'm also using the audio service and audio session packages to play the radio as background music. When the user hits play, no music is playing and error in the console:
Error:
E/flutter ( 6721): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(Platform player 6f26b522-c6c3-4226-be22-80f9d15dfc2c already exists, null, null, null)
E/flutter ( 6721): #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:653:7)
E/flutter ( 6721): #1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:296:18)
E/flutter ( 6721):
E/flutter ( 6721): #2 MethodChannelJustAudio.init (package:just_audio_platform_interface/method_channel_just_audio.dart:13:5)
E/flutter ( 6721):
E/flutter ( 6721): #3 AudioPlayer._setPlatformActive.setPlatform (package:just_audio/just_audio.dart:1255:13)
E/flutter ( 6721):
Code in main.dart:
Future<void> main() async {
// initialise audio_service with our audio_handler
myAudioHandler = await AudioService.init(
builder: () => MyAudioHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
),
);
Code in audio_handler.dart:
Future<void> _loadRadio() async {
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.music());
// Try to load the radio from the source and catch any errors.
try {
await _player.setAudioSource(AudioSource.uri(
Uri.parse([stream_address])));
} catch (e) {
print("Error loading audio source: $e");
}
}
What does this error mean? And why is it only giving problems on some Android versions?
On Android 12 this is not happening all the time.
On Android 10 this is happening a lot.
We have several user complaints with the Android platform.
The error was coming from a myAudioHandler.stop command inside an internet connection checker function. When removed, everything worked again with all our Android users. This code was for testing purposes, and shouldn't be there. I hope this helps others with this error. Thanks for your help.

Null check operator error in Flutter application

I am developing a Flutter app that implements OpenID authentication. I am getting the following error in one of the flutter libraries I am using (library https://pub.dev/packages/openid_client). The error is as follows:
E/flutter (14084): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled ExceptionNull check operator used on a null value
E/flutter (14084): #0 new Flow._
package:solidauth/…/src/openid.dart:344
E/flutter (14084): #1 new Flow.authorizationCode
package:solidauth/…/src/openid.dart:361
E/flutter (14084): #2 new Authenticator
package:solidauth/openid/openid_client_io.dart:23
E/flutter (14084): #3 AppLogin.authenticate1
package:solidauth/main.dart:873
E/flutter (14084): <asynchronous suspension>
E/flutter (14084): #4 AppLogin.build.<anonymous closure>
package:solidauth/main.dart:574
E/flutter (14084): <asynchronous suspension>
The piece of code that I am getting the error is the following:
class Authenticator {
final Flow flow;
final Function(String url) urlLancher;
final int port;
Authenticator(Client? client,
{this.port = 3000,
this.urlLancher = _runBrowser,
Iterable<String> scopes = const [],
Uri? redirectUri})
: flow = redirectUri == null
? Flow.authorizationCodeWithPKCE(client)
: Flow.authorizationCode(client)
..scopes.addAll(scopes)
..redirectUri = redirectUri ?? Uri.parse('http://localhost:$port/');
... some other functions ...
}
The error is coming from the above class constructor when I call it like the following. But I don't see any obvious errors in there. Not sure what I am doing wrong here.
// create an authenticator
var authenticator = new Authenticator(client,
scopes: scopes,
port: 4000,
urlLancher: urlLauncher,
redirectUri: Uri.parse(redirUrl));
I think the issue comes from this line in the class Flow because the client is null, so the null check ! will throw that error.
Flow._(this.type, this.responseType, this.client, {String? state})
: state = state ?? _randomString(20) {
var scopes = client!.issuer!.metadata.scopesSupported; <-- This line
for (var s in const ['openid', 'profile', 'email']) {
if (scopes!.contains(s)) {
this.scopes.add(s);
break;
}
}

Dio Interceptor not working on first request in same callback, only subsequent calls

I am testing out dio and tried to add a interceptor to simply add a token to future requests but am getting a weird results I can't seem to fix. All I have as a test is a 2 buttons. One when clicked should log me in and add the token to the interceptor, and the second button requests the auth profile data. For some reason, clicking the log in button I log in fine but get a 403 Forbidden when clicking the second button to access the auth profile data (even though i request the profile data after adding the interceptor). The weird part is that when i click the second button again (without changing any code or even hot reloading) everything works fine and the auth profile data is printed out. Every time I hot restart I get back to this same problem where my first request of the auth profile data has a 403 but subsequent requests work fine. I've been trying to figure out what is going on for a couple hours and cannot understand whats wrong. Please help. Thank you. (The backend is handled by django but the problem cannot be there as the api works with other frameworks and even in dio works fine on subsequent button presses, just not the first)
Code
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Dio session = Dio(
BaseOptions(
connectTimeout: 30000,
baseUrl: 'http://127.0.0.1:8000',
responseType: ResponseType.json,
contentType: ContentType.json.toString(),
),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
RaisedButton(
child: Text('Log In'),
onPressed: () async {
print('starting');
var res = await session.post('/auth/login/',
data: {'username': 'tester', 'password': 'tester'});
print(res.data);
session.interceptors.clear();
session.interceptors.addAll([
InterceptorsWrapper(
onRequest: (RequestOptions requestOptions) {
session.interceptors.requestLock.lock();
String token = res.data['key'];
if (token != null) {
session.options.headers[HttpHeaders.authorizationHeader] =
‘token $token’;
}
session.interceptors.requestLock.unlock();
return requestOptions;
},
onError: (e) => print(e.message),
),
]);
print(session.interceptors);
}),
RaisedButton(
child: Text('Get Profile'),
onPressed: () async {
session.get('/api/auth/').then((res) => print(res.data));
}),
],
),
),
);
}
}
Console on clicking the log in button
Restarted application in 11,586ms.
flutter: starting
flutter: {key: 745c0a53112e61d54bea5ea725f7fa92e3a2cdbb}
flutter: [Instance of 'InterceptorsWrapper']
Console on the first time clicking the get profile button
flutter: Http status error [403]
[VERBOSE-2:ui_dart_state.cc(177)] Unhandled Exception: DioError [DioErrorType.RESPONSE]: Http status error [403]
#0 DioMixin._request._errorInterceptorWrapper.<anonymous closure>.<anonymous closure>.<anonymous closure>
package:dio/src/dio.dart:870
#1 _rootRunUnary (dart:async/zone.dart:1198:47)
#2 _CustomZone.runUnary (dart:async/zone.dart:1100:19)
#3 _FutureListener.handleValue (dart:async/future_impl.dart:143:18)
#4 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:696:45)
#5 Future._propagateToListeners (dart:async/future_impl.dart:725:32)
#6 Future._completeWithValue (dart:async/future_impl.dart:529:5)
#7 Future._asyncCompleteWithValue.<anonymous closure> (dart:async/future_impl.dart:567:7)
#8 _rootRun (dart:async/zone.dart:1190:13)
#9 _CustomZone.run (dart:async/zone.dart:1093:19)
#10 _CustomZone.runGuarded (dart:async/zone.dart:997:7)
#11 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zon<…>
Console on subsequent times clicking the get profile button
flutter: {id: 3, username: tester, first_name: , last_name: , email: tester#tester.com}
5 more hours and finally figured out the problem.
session.options.headers[HttpHeaders.authorizationHeader] = 'token ' + token;
should be
requestOptions.headers[HttpHeaders.authorizationHeader] = 'token ' + token;