Unable to call Material page route in state class with async delay - flutter

I am trying to display a splash screen for a few seconds then trying to navigate it to the next screen but I am getting this error. Probably because of accessing context in iniState methods but I am also using async so I don't think there's should be any issue.
E/flutter ( 6663): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: NoSuchMethodError: The method 'findAncestorStateOfType' was called on null.
E/flutter ( 6663): Receiver: null
E/flutter ( 6663): Tried calling: findAncestorStateOfType<NavigatorState>()
E/flutter ( 6663): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
E/flutter ( 6663): #1 Navigator.of
package:flutter/…/widgets/navigator.dart:2185
import 'package:awsomeNotes/appUtilities/dimensions.dart';
import 'package:awsomeNotes/views/phoneAuthPage/phoneAuthPage.dart';
import 'package:flutter/material.dart';
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
nextPage() async {
await Future.delayed(Duration(seconds: 5));
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhoneAuthPage(),
),
);
}
#override
void initState() {
nextPage();
super.initState();
}
#override
Widget build(BuildContext context) {
Dimensions(context);
return Material(
child: Center(
child: Text(
"This is Splash Screen",
style: TextStyle(fontSize: Dimensions.boxHeight * 5),
),
),
);
}
}
I am navigating to the next page but with the error specified.

Related

Getting exception when trying to open a new page with Navigator

I have a push notification manager class that is singleton where I listen for notifications from Firebase FCM and I handle them based on the logic and open specific screens.
I init with BuildContext that PushtNotificatonManager class inside HomePage that iS called from main.dart.
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
void initState() {
super.initState();
PushNotificationsManager().listen(context);
}
This is code from main.dart
Future<void> backgroundHandler(RemoteMessage message) async {
print('Handling a background message ${message.messageId}');
print('Content of message: ' + message.toString());
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
configureInjections();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(backgroundHandler);
runApp(SayApp());
}
This is part of the code from PushNotificationsManager class
#injectable
class PushNotificationsManager {
static final PushNotificationsManager _instance = PushNotificationsManager._internal();
factory PushNotificationsManager() {
return _instance;
}
PushNotificationsManager._internal();
Future<void> listen(BuildContext context) async {
if (Platform.isIOS) {
NotificationSettings settings = await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
provisional: false,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
print('User granted permission');
await _handleNotifications(context);
}
} else {
await _handleNotifications(context);
}
}
Future<void> _handleNotifications(BuildContext context) async {
// gives the message on which user taps and it opened the app from terminated state
FirebaseMessaging.instance.getInitialMessage().then((message) {
if (message != null) {
openNotification(message, context);
}
});
// foreground
FirebaseMessaging.onMessage.listen((message) {
if (message.notification != null) {
_showInAppNotification(message);
}
});
// When the app is in background but opened and user taps on the notification
FirebaseMessaging.onMessageOpenedApp.listen((message) {
openNotification(message, context);
});
}
Inside showInAppNotifications I am trying to display notifications as pop up and when the user clicks on it, I try to open a specific screen.
void _displayInAppNotifications(RemoteMessage message) {
FlutterRingtonePlayer.playNotification();
showOverlayNotification((context) {
return GestureDetector(
onTap: () {
openNotification(message, context);
OverlaySupportEntry.of(context)!.dismiss();
},
child: SafeArea(
child: Card(
child: ListTile(
leading: message.data[NotificationType.NOTIFICATION_TYPE] ==
NotificationType.PRIVATE_MESSAGE_1_ON_1
? Avatar(text: message.notification!.title!)
: SizedBox.fromSize(
size: const Size(40, 40),
child: ClipOval(
child: Container(
child: SvgPicture.asset(SvgIcons.sayAppWaveLogo),
))),
title:
Text(message.notification!.title!, overflow: TextOverflow.ellipsis, maxLines: 1),
subtitle: Text(
message.notification!.body!,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
trailing: IconButton(
icon: Icon(Icons.close),
onPressed: () {
OverlaySupportEntry.of(context)!.dismiss();
}),
),
),
),
);
}, duration: Duration(seconds: 4), position: NotificationPosition.top);
}
I have this method navigateToChannelDetailsPage that is called from the above method and here I am getting an exception.
Future<void> navigateToChannelDetailsPage(ChannelEntity channel, BuildContext context) async {
var cubit = BlocProvider.of<HomeCubit>(context);
var isUserChannelMember = await cubit.isCurrentUserChannelMember(channel);
if (!isUserChannelMember) return;
var isUserMutedPublic = await cubit.getIsUserMutedPublic(channel);
if (isUserMutedPublic) channel.isUserMutedPublic = isUserMutedPublic;
return PageNavigator.navigate(
context, ChannelDetailsPage(channel: channel, isFromAdminNotification: true))
.then((_) {
FocusManager.instance.primaryFocus?.unfocus();
cubit.updateChannelMemberChatDetailsDataOnChannelClosed(channel);
});
}
This is the exception:
E/flutter (25412): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter (25412): At this point the state of the widget's element tree is no longer stable.
E/flutter (25412): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
E/flutter (25412): #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:4241:9)
E/flutter (25412): #1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:4255:6)
E/flutter (25412): #2 Element.getElementForInheritedWidgetOfExactType (package:flutter/src/widgets/framework.dart:4286:12)
E/flutter (25412): #3 Provider._inheritedElementOf (package:provider/src/provider.dart:339:38)
E/flutter (25412): #4 Provider.of (package:provider/src/provider.dart:293:30)
E/flutter (25412): #5 BlocProvider.of (package:flutter_bloc/src/bloc_provider.dart:100:23)
E/flutter (25412): #6 PushNotificationsManager.navigateToChannelDetailsPage (package:say_app/presentation/notification/push_notifications_manager.dart:204:30)
E/flutter (25412): #7 PushNotificationsManager.openNotification (package:say_app/presentation/notification/push_notifications_manager.dart:180:9)
E/flutter (25412): <asynchronous suspension>
Any ideas?
You get that error because, by the time you wait for some things to finish the execution, you do something else on the app (go back one or few screens let's say) so your widget/screen gets disposed of and is no longer in the widget tree when you call PageNavigator.navigate(context, ...).
So to prevent that from happening, you can use the mounted flag to check if your StatefulWidget is still active, like:
if(mounted) PageNavigator.navigate(context, ...)
But in that case, the page will not get pushed if the widget is disposed of. So if you still want to push the new screen, no matter what happens in the meantime (even if that widget gets disposed of), you can have your global navigatorKey which you assign to the MaterialApp key property and use the context from navigatorKey while navigating, since it should always be usable (i.e. the MaterialApp should always be present in the widget tree). Solution example:
import 'package:flutter/material.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
home: const HomePage(),
);
}
}
So now you navigate like:
PageNavigator.navigate(navigatorKey.currentContext!, ...) // your use case
Navigator.of(navigatorKey.currentContext!).push(...) // the usual way using global navigatorKey

Flutter: Geolocator gives wrong location

When I execute, (click on the "show Longitude and Latitude" button) I have two problems, a wrong position and an error:
**W/GooglePlayServicesUtil( 5181): com.example.geolocalisation_youtube requires the Google Play Store, but it is missing.
I/flutter ( 5181): 10681894.898369517
I/flutter ( 5181): long c.longitude
I/flutter ( 5181): 37.421998333333335
E/flutter ( 5181): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(IO_ERROR, A network error occurred trying to lookup the supplied coordinates (latitude: 37.421998, longitude: -122.084000)., null, null)
E/flutter ( 5181): #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:653:7)
E/flutter ( 5181): #1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:296:18)
E/flutter ( 5181): <asynchronous suspension>
E/flutter ( 5181): #2 MethodChannelGeocoding.placemarkFromCoordinates (package:geocoding_platform_interface/src/implementations/method_channel_geocoding.dart:56:24)
E/flutter ( 5181): <asynchronous suspension>
E/flutter ( 5181): #3 _MyHomePageState.build.<anonymous closure> (package:geolocalisation_youtube/main.dart:84:47)
E/flutter ( 5181): <asynchronous suspension>
**
Here’s the code:
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:geocoding/geocoding.dart';
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> {
Future getPosition() async{
bool service= await Geolocator.isLocationServiceEnabled();
LocationPermission per= await Geolocator.checkPermission();
if (per==LocationPermission.denied){
per= await Geolocator.requestPermission();
if(per!=LocationPermission.denied){
}
}
print(service);
print("---------------");
print(per);
print("---------------");
if(!service ){
AwesomeDialog(
context: context,
title: "services",
body:
Text("service is enabled")
)..show();
}
}
Future <Position> getLatandLong() async{
return await Geolocator.getCurrentPosition().then((value) => value);
}
#override
void initState(){
getPosition();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
Container(
height: 500,
width:400 ,
),
ElevatedButton(
onPressed: () async{
var c = await getLatandLong();
var distance= await Geolocator.distanceBetween(c.latitude, c.longitude, 28.033886, 1.659626);
print(distance);
print("long c.longitude ");
print(c.latitude);
List<Placemark> placemarks = await placemarkFromCoordinates(c.latitude, c.longitude);
print(placemarks[0].administrativeArea);
},
child: Text(" show Longitude and Latitude"))
],
),
);
}
}
I got this error on the latest version of Android Studio.

Flutter Bad State: Future already completed and Updated Layout info required

Just started working on my app and I'm getting
Bad state: Future already completed
When the exception was thrown, this was the stack:
#1 WidgetsBinding.drawFrame.<anonymous closure> (package:flutter/src/widgets/binding.dart:865:30)
#2 SchedulerBinding._executeTimingsCallbacks (package:flutter/src/scheduler/binding.dart:289:19)
#6 _invoke1 (dart:ui/hooks.dart:182:10)
#7 PlatformDispatcher._reportTimings (dart:ui/platform_dispatcher.dart:441:5)
#8 _reportTimings (dart:ui/hooks.dart:120:31)
...
The TimingsCallback that gets executed was: Closure: (List<FrameTiming>) => void
But There is also
Updated layout information required for RenderParagraph#d3031 NEEDS-LAYOUT NEEDS-PAINT to calculate semantics.
'package:flutter/src/rendering/object.dart':
Failed assertion: line 2653 pos 12: '!_needsLayout'
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.md
When the exception was thrown, this was the stack:
#2 RenderObject._getSemanticsForParent (package:flutter/src/rendering/object.dart:2653:12)
#3 RenderObject._getSemanticsForParent.<anonymous closure> (package:flutter/src/rendering/object.dart:2675:61)
#4 ContainerRenderObjectMixin.visitChildren (package:flutter/src/rendering/object.dart:3331:14)
#5 RenderObject.visitChildrenForSemantics (package:flutter/src/rendering/object.dart:2760:5)
#6 RenderObject._getSemanticsForParent (package:flutter/src/rendering/object.dart:2670:5)
My code is this so far
Main.dart
void main() => runApp(AppName());
class AppName extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
initialRoute: WelcomeScreen.id,
routes: {
WelcomeScreen.id: (context) => WelcomeScreen(),
SignUpScreen.id: (context) => SignUpScreen(),
LoginScreen.id: (context) => LoginScreen(),
HomeScreen.id: (context) => HomeScreen(),
SearchScreen.id: (context) => SearchScreen(),
MessagesScreen.id: (context) => MessagesScreen(),
ProfileScreen.id: (context) => ProfileScreen(),
RegistraionScreen.id: (context) => RegistraionScreen(),
},
);
}
}
welcome_screen.dart
class WelcomeScreen extends StatefulWidget {
static String id = 'welcome_screen';
#override
_WelcomeScreenState createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: Text(
'Welcome',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 40,
color: Colors.white,
),
),
),
);
}
}
I have tried restarting my app restarting my IDE, simulator, and computer to no avail and all the other questions with the same error had something wrong with their flutter however when I run flutter doctor I get no errors
Sorry if it's something small and stupid I'm not seeing I'm new to dart and flutter. Thanks for your help.
I tried removing the Expanded widget in the welcome screen and this solved the issue

Show snackbar message after widget rebuild

I try to do login page (do the transaction in some Future function) and show the error message by Snackbar.
Click login
Show loading
Future transaction done (back to original page), and then show the error message
Here is the flow I want to achieve (the last part failed):
I don't know how to show the snackBar correctly using the message from future.
showSnackBar need BuildContext but the context inside the signIn page seems no longer valid anymore after the message come back from Future.
I am now using package flutter_hooks and hooks_riverpod for the state management.
My State
class MyState{
MyState({this.data,this.isLoading});
final bool isLoading;
final String data;
MyState copyWith({data, isLoading}) => MyState(data: data, isLoading: isLoading);
}
State control and provider
Future<String> getData() fetch data and return error message
class MyStateNotifier extends StateNotifier<MyState> {
MyStateNotifier(MyState state) : super(state);
Future<String> getData() async {
state = state.copyWith(isLoading: true);
await Future.delayed(Duration(seconds: 3)); // simulate getting data
state = state.copyWith(isLoading: false, data: 'some data');
return 'error message';
}
}
final myStateProvider = StateNotifierProvider<MyStateNotifier>((ref) {
return MyStateNotifier(MyState(data: null, isLoading: false));
});
My widget
myState.isLoading: show loading page or sign in page
class WidgetA extends HookWidget {
const WidgetA({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final myState = useProvider(myStateProvider.state);
return Center(
child: myState.isLoading ? CircularProgressIndicator() : SignInPage(),
);
}
}
class SignInPage extends HookWidget {
const SignInPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () async {
context.read(myStateProvider).getData().then(
(message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
},
);
},
child: Text('login'),
);
}
}
I use showSnackBar inside then after getData(), but it show the error message:
E/flutter ( 6869): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Looking up a
deactivated widget's ancestor is unsafe.
E/flutter ( 6869): At this point the state of the widget's element tree is no longer stable.
E/flutter ( 6869): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
E/flutter ( 6869): #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3938:9)
E/flutter ( 6869): #1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3952:6)
E/flutter ( 6869): #2 Element.findAncestorWidgetOfExactType (package:flutter/src/widgets/framework.dart:4044:12)
E/flutter ( 6869): #3 debugCheckHasScaffoldMessenger.<anonymous closure> (package:flutter/src/material/debug.dart:142:17)
E/flutter ( 6869): #4 debugCheckHasScaffoldMessenger (package:flutter/src/material/debug.dart:154:4)
E/flutter ( 6869): #5 ScaffoldMessenger.of (package:flutter/src/material/scaffold.dart:218:12)
E/flutter ( 6869): #6 SignInPage.build.<anonymous closure>.<anonymous closure> (package:flutter_app_test2/main.dart:171:35)
E/flutter ( 6869): #7 _rootRunUnary (dart:async/zone.dart:1198:47)
E/flutter ( 6869): #8 _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter ( 6869): #9 _FutureListener.handleValue (dart:async/future_impl.dart:143:18)
E/flutter ( 6869): #10 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:696:45)
E/flutter ( 6869): #11 Future._propagateToListeners (dart:async/future_impl.dart:725:32)
E/flutter ( 6869): #12 Future._completeWithValue (dart:async/future_impl.dart:529:5)
E/flutter ( 6869): #13 _completeOnAsyncReturn (dart:async-patch/async_patch.dart:254:13)
E/flutter ( 6869): #14 MyStateNotifier.getData (package:flutter_app_test2/main.dart)
E/flutter ( 6869): <asynchronous suspension>
You can copy paste run full code below
Reason : Because SignInPage disappear after click login button
Quick fix is use ScaffoldMessenger and provide scaffoldMessengerKey then call scaffoldMessengerKey.currentState.showSnackBar(SnackBar(content: Text(message)));
code snippet
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScaffoldMessenger(
key: scaffoldMessengerKey,
child: Scaffold(
...
context.read(myStateProvider).getData().then(
(message) {
scaffoldMessengerKey.currentState
.showSnackBar(SnackBar(content: Text(message)));
},
);
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hooks_riverpod/all.dart';
class MyState {
MyState({this.data, this.isLoading});
final bool isLoading;
final String data;
MyState copyWith({data, isLoading}) =>
MyState(data: data, isLoading: isLoading);
}
class MyStateNotifier extends StateNotifier<MyState> {
MyStateNotifier(MyState state) : super(state);
Future<String> getData() async {
state = state.copyWith(isLoading: true);
await Future.delayed(Duration(seconds: 3)); // simulate getting data
state = state.copyWith(isLoading: false, data: 'some data');
return 'error message';
}
}
final myStateProvider = StateNotifierProvider<MyStateNotifier>((ref) {
return MyStateNotifier(MyState(data: null, isLoading: false));
});
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScaffoldMessenger(
key: scaffoldMessengerKey,
child: Scaffold(
appBar: AppBar(title: const Text('example')),
body: WidgetA(),
),
);
}
}
class WidgetA extends HookWidget {
const WidgetA({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final myState = useProvider(myStateProvider.state);
return Center(
child: myState.isLoading ? CircularProgressIndicator() : SignInPage(),
);
}
}
class SignInPage extends HookWidget {
const SignInPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () async {
context.read(myStateProvider).getData().then(
(message) {
scaffoldMessengerKey.currentState
.showSnackBar(SnackBar(content: Text(message)));
},
);
},
child: Text('login'),
);
}
}
You'll have to await you getData() request and return the message in a variable, then call the snackbar. You're trying to call the snackbar inside the future call. This can't be done on the UI.
final message = await context.read(myStateProvider).getData();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));

Singleton in Flutter gives runtime error "Unhandled Exception: Reading static variable '_instance#545324594' during its initialization"

I tried to implement the MVP-Pattern in a Flutter App (Flutter version 1.12.13+hotfix8).
My controller (presenter) is a singleton and looks like this:
import 'package:flutter/material.dart';
// local file imports
import 'package:prototype/gui/screens/welcome/welcome.dart';
// this is a singleton
class Controller {
static final Controller _instance = Controller._internal();
Widget _currentWidget;
Controller._internal() {
this._currentWidget = ScreenWelcome();
}
factory Controller() => _instance;
Widget get currentWidget => this._currentWidget;
set currentWidget(Widget widget){
_currentWidget = widget;
}
}
My home screen looks like:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// local file imports
import 'package:prototype/controller/conroller.dart';
import 'package:prototype/gui/screens/register/register.dart';
import 'package:prototype/gui/screens/register/sign_in.dart';
import 'package:prototype/gui/screens/text_viewer/text_viewer.dart';
class ScreenWelcome extends StatelessWidget {
final _controller = Controller();
#override
Widget build(BuildContext context) {
return Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Image.asset('resources/zombiepaar.jpg',
width: 500, height: 500, fit: BoxFit.fitWidth),
SizedBox(
height: 20.0,
),
RaisedButton(
child: Text("Ein neues Konto erstellen"),
onPressed: () => _controller.currentWidget = ScreenRegister(),
),
SizedBox(
height: 20.0,
),
RaisedButton(
child: Text("Mit bestehendem Konto einloggen"),
onPressed: () => _controller.currentWidget = ScreenSignIn(),
),
SizedBox(
height: 20.0,
),
Row(
children: <Widget>[
GestureDetector(
child: Text(
"Nutzungsbedingungen",
style: TextStyle(
decoration: TextDecoration.underline,
color: Colors.blue),
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ScreenTextViewer(
title: "Nutzungsbedingungen",
ressourceFileToLoad: 'resources/AGBs.txt'),
)),
),
SizedBox(
width: 20.0,
),
GestureDetector(
child: Text(
"Datenschutzrichtlinien",
style: TextStyle(
decoration: TextDecoration.underline,
color: Colors.blue),
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ScreenTextViewer(
title: "Nutzungsbedingungen",
ressourceFileToLoad: 'resources/AGBs.txt'),
),
),
),
],
),
],
),
),
);
}
}
But when I ran my app I get the following runtime error:
D/FlutterActivityAndFragmentDelegate(21372): Executing Dart entrypoint: main, and sending initial route: /
E/flutter (21372): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Reading static variable '_instance#545324594' during its initialization
E/flutter (21372): #0 Controller._instance (package:prototype/controller/conroller.dart:9:27)
E/flutter (21372): #1 new Controller (package:prototype/controller/conroller.dart:17:27)
E/flutter (21372): #2 new ScreenWelcome (package:prototype/gui/screens/welcome/welcome.dart:11:23)
E/flutter (21372): #3 new Controller._internal (package:prototype/controller/conroller.dart:14:27)
E/flutter (21372): #4 Controller._instance (package:prototype/controller/conroller.dart:9:50)
E/flutter (21372): #5 Controller._instance (package:prototype/controller/conroller.dart:9:27)
E/flutter (21372): #6 new Controller (package:prototype/controller/conroller.dart:17:27)
E/flutter (21372): #7 new MyApp (package:prototype/main.dart:10:22)
E/flutter (21372): #8 main (package:prototype/main.dart:6:23)
E/flutter (21372): #9 _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:239:25)
E/flutter (21372): #10 _rootRun (dart:async/zone.dart:1126:13)
E/flutter (21372): #11 _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (21372): #12 _runZoned (dart:async/zone.dart:1518:10)
E/flutter (21372): #13 runZoned (dart:async/zone.dart:1502:12)
E/flutter (21372): #14 _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:231:5)
E/flutter (21372): #15 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:307:19)
E/flutter (21372): #16 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174:12)
Any idea how to fix this? I found nothing helpful with Google and I'm pretty new to dart and Flutter. Maybe I implemented the singleton class the wrong way?
Edit: Mi main.dart file is
import 'package:flutter/material.dart';
// local file imports
import 'package:prototype/controller/conroller.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of the application.
final controller = Controller(); // presenter in MVP design pattern
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
home: controller.currentWidget, // controller decides the main widget
);
}
}
You have a cyclic dependency. The creation of a ScreenWelcome calls the Controller constructor, which reads the _instance field, which constructs a Controller, which creats a ScreenWelcome. You're lucky that the _instance field is lazy because then it detects the cycle early instead of hitting a stack overflow.
If you have two classes with final fields which must point to each other. That's basically impossible. Luckily the _currentWidget is not final, so that's the one you need to set after both objects have been created.
I'd do something like:
class Controller {
static final _instance = Controller._();
Widget_currentWidget;
factory Controller() => _instance;
Controller._();
}
class ScreenWelcome {
final Controller _controller;
ScreenWelcome() : _controller = Controller() {
// This is the soonest a reference to this widget is available.
_controller.currentWidget = this;
}
}
Finally, I found the answer: I have to call the constructor of the widget as late as possible. Therefore, I called it in the getter function. The working code looks like:
import 'package:flutter/material.dart';
// local file imports
import 'package:prototype/gui/screens/welcome/welcome.dart';
// this is a singleton
class Controller {
static final Controller _instance = Controller._internal();
Function _currentWidget;
Controller._internal() {
this._currentWidget = () => ScreenWelcome();
}
factory Controller() => _instance;
Widget get currentWidget => this._currentWidget();
set currentWidget(Widget widget){
_currentWidget = () => widget;
}
}
Maybe one day it will help someone :)