-
Notifications
You must be signed in to change notification settings - Fork 28.9k
Description
I got this error message:
══╡ EXCEPTION CAUGHT BY SCHEDULER LIBRARY ╞════════════════════════════
The following assertion was thrown during a scheduler callback:
'package:flutter/src/widgets/heroes.dart': Failed assertion: line 732 pos 14: 'manifest.fromHero == newManifest.toHero': is not true.
Either the assertion indicates an error in the framework itself,
or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=2_bug.yml
When the exception was thrown, this was the stack:
#2 _HeroFlight.divert (package:flutter/src/widgets/heroes.dart:732:14)
#3 HeroController._startHeroTransition (package:flutter/src/widgets/heroes.dart:1041:26)
#4 HeroController._maybeStartHeroTransition.<anonymous closure> (package:flutter/src/widgets/heroes.dart:958:9)
#5 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1442:15)
#6 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1369:11)
#7 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1208:5)
#8 _invoke (dart:ui/hooks.dart:316:13)
#9 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:428:5)
#10 _drawFrame (dart:ui/hooks.dart:288:31)
(elided 2 frames from class _AssertionError)
═══════════════════════════════════════════════════════════════
The program does function correctly, but seeing this error message was asking me to file a bug report & I couldn't find the bug being reported before, I decided to go ahead.
I'll try to include as much information as I can that I think are relevant.
I've got this widget that loads a page based on a future:
import 'package:flutter/material.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:wink/WaitingRooms.dart';
enum FutureState {
FETCH_ERROR,
BAD,
GOOD,
}
class LoadPage extends StatelessWidget {
final Future future;
final void Function(FutureState, String) on_error;
final String name;
final bool is_host;
const LoadPage({
super.key,
required this.future,
required this.on_error,
required this.name,
required this.is_host,
});
@override
Widget build(BuildContext context) => FutureBuilder(
future: future,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Scaffold(
body: Center(
child: LoadingAnimationWidget.twoRotatingArc(
color: Theme.of(context).colorScheme.primary,
size: 69,
),
),
);
else if (
snapshot.hasError ||
!snapshot.hasData ||
(snapshot.data! as (FutureState, String)).$1 == FutureState.BAD
) {
final state = snapshot.hasError ||(!snapshot.hasData) ? FutureState.FETCH_ERROR : FutureState.BAD;
final err_msg = (snapshot.data as (FutureState, String)).$2;
Future.microtask(() => on_error(state, err_msg));
return const Scaffold();
}
final String code = (snapshot.data! as (FutureState, String)).$2;
return WaitingRoom(room: code, name: name, is_host: is_host);
}
);
}
I have this method joinRoom
that ends up pushing the LoadPage widget:
void joinRoom() {
final name = name_controller.text.trim();
if (name.isEmpty) return errMsg(context, "Please enter your name!");
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text("Joining..", style: TextStyle(fontSize: 32)),
content: TextField(
controller: code_controller,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: "room code",
),
),
actions: [
TextButton(
child: const Text("Cancel", style: TextStyle(fontSize: 20, color: Colors.red)),
onPressed: Navigator.of(context).pop,
),
TextButton(
child: const Text("Join", style: TextStyle(fontSize: 20)),
onPressed: () async {
final input_code = code_controller.text.trim();
if (input_code.isEmpty) return; // ignore empty input
final Future<(FutureState, String)> Function() future = () async {
if (!await roomExists(input_code))
return (FutureState.BAD, "Room not found!..");
final players = await getPlayerNames(input_code);
if (players.contains(name))
return (FutureState.BAD, "Name taken!");
// else //, we're good to go
await addPlayer(room: input_code, name: name);
return (FutureState.GOOD, input_code);
};
Navigator.of(context).pop(); // close dialog
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (_, _, _) => LoadPage(
future: memo.runOnce(future),
on_error: (state, err) {
if (state == FutureState.FETCH_ERROR) errMsg(context, "An error has occurred!");
else errMsg(context, err);
Navigator.of(context).pop();
},
name: name,
is_host: false,
),
settings: const RouteSettings(name: "Waiting Room"),
transitionsBuilder:(_, animation, _, child) => FadeTransition(opacity: animation, child: child),
)
);
memo = AsyncMemoizer(); // VERY IMPORTANT LOL
}
),
],
),
);
}
I've also got this error message function that displays a snackbar. You can see it being called inside the on_error
lambda when pushing the LoadPage
widget.
void errMsg(final BuildContext context, final String label) {
ScaffoldMessenger.of(context).removeCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(label),
behavior: SnackBarBehavior.floating,
),
);
}
The assertion failure happens when I try to show the SnackBar before the previous SnackBar disappears. For example, if I try to enter a room that doesn't exist, I will get a SnackBar saying "Room not fond!..". Attempting to join the room again before waiting for the SnackBar to disappear will cause the assertion failure. There is no assertion failure if I try to join the room after the SnackBar disappears.
This is the only case in the app where displaying a SanckBar when another is already being displayed causes an issue. Usually, the previous SnackBar gets removed since the errMsg
function calls ScaffoldMessenger.of(context).removeCurrentSnackBar()
before displaying the new SnackBar.
This is all that I could gather in the meantime. I didn't try to get a minimal working example so feel free to do so.