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
Related
Hey I am building an app in flutter and when I run google sign in it gives me error that
E/flutter (30710): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter (30710): At this point the state of the widget's element tree is no longer stable.
E/flutter (30710): 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.
can anyone help on this
my main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerStatefulWidget {
const MyApp({super.key});
#override
ConsumerState<MyApp> createState() => _MyAppState();
}
class _MyAppState extends ConsumerState<MyApp> {
UserModel? userModel;
void getData(WidgetRef ref, User data) async {
userModel = await ref
.watch(authControllerProvider.notifier)
.getUserData(data.uid)
.first;
debugPrint("cdsdvs $userModel");
ref.read(userProvider.notifier).update((state) => userModel);
setState(() {});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Mama App',
debugShowCheckedModeBanner: false,
theme: Pallete.darkModeAppTheme,
home: ref.watch(authStateChangeProvider).when(
data: (data) {
if (data != null) {
const HomeScreen();
}
return const LoginScreen();
},
error: ((error, stackTrace) => ErrorText(error: error.toString())),
loading: () => const Loader()),
);
}
}
There is probably a problem here:
void getData(WidgetRef ref, User data) async {
userModel = await ref
.watch(authControllerProvider.notifier)
.getUserData(data.uid)
.first;
debugPrint("cdsdvs $userModel");
ref.read(userProvider.notifier).update((state) => userModel);
setState(() {});
}
You should not use ref.watch in callbacks and asynchronous code. Use instead ref.read
I'm attempting to enable auto-login by obtaining a token saved in the local mobile instance. I'm encountering this error. I'm using Provider for state management.
In my main file , I want to retrieve my token to check whether user has been logged in or not. What i'm, doing wrong ?
My Error
E/flutter ( 6547): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: No ScaffoldMessenger widget found.
E/flutter ( 6547): MyApp widgets require a ScaffoldMessenger widget ancestor.
E/flutter ( 6547): The specific widget that could not find a ScaffoldMessenger ancestor was:
E/flutter ( 6547): MyApp
E/flutter ( 6547): The ancestors of this widget were:
E/flutter ( 6547): _InheritedProviderScope<UserProvider?>
E/flutter ( 6547): ChangeNotifierProvider<UserProvider>
E/flutter ( 6547): _NestedHook
E/flutter ( 6547): MultiProvider
E/flutter ( 6547): [root]
E/flutter ( 6547): Typically, the ScaffoldMessenger widget is introduced by the MaterialApp at the top of your application widget tree.
Main.dart file
void main() {
runApp(MultiProvider(
providers: [ChangeNotifierProvider(create: (context) => UserProvider())],
child: const MyApp()));
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}):super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final AuthService authService = AuthService();
#override
void initState() {
// TODO: implement initState
authService.getUserData(context);
super.initState();
}
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ShopOne',
theme: ThemeData(
scaffoldBackgroundColor: GlobalVariables.backgroundColor,
textTheme: Theme.of(context)
.textTheme
.apply(bodyColor: Colors.white, displayColor: Colors.white),
colorScheme:
const ColorScheme.light(primary: GlobalVariables.secondaryColor),
appBarTheme: const AppBarTheme(
elevation: 0, iconTheme: IconThemeData(color: Colors.white)),
//
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
onGenerateRoute: ((settings) => generateRoute(settings)),
home: Provider.of<UserProvider>(context).user.token.isNotEmpty
? const HomeScreen()
: const AuthScreen()
);
if (Provider.of<UserProvider>(context).user.token.isNotEmpty) {
print('true');
} else {
print('false');
;
}
}
}
get user function
void getUserData(
BuildContext context,
) async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? token = prefs.getString("auth-token");
if (token == null) {
prefs.setString('auth-token', '');
}
var tokenRes = await http.post(Uri.parse('$uri//tokenisvalid'),
headers: <String, String>{
'Content-Type': 'application/json;charset=UTF-8',
'auth-token': token!
});
print('tokenres working 1');
var response = jsonDecode(tokenRes.body);
if (response == true) {
http.Response userRes = await http.get(Uri.parse('$uri/'),
headers: <String, String>{
'Content-type': 'application/json;charset=UTF-8',
'auth-token': token
});
print('tokenres working 2');
var userProvider = Provider.of<UserProvider>(context, listen: false);
userProvider.setUser(userRes.body);
}
} catch (e) {
showSnackBar(context, e.toString());
}
}
This happened because the context that you used in showSnackBar, doesn't belongs to any Scaffold, you need to separate the MaterialApp's home to new StatefulWidget class like this:
class ScreenManager extends StatefulWidget {
const ScreenManager({Key? key}) : super(key: key);
#override
State<ScreenManager> createState() => _ScreenManagerState();
}
class _ScreenManagerState extends State<ScreenManager> {
#override
void initState() {
// TODO: implement initState
authService.getUserData(context);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Provider.of<UserProvider>(context).user.token.isNotEmpty
? const HomeScreen()
: const AuthScreen(),
);
}
void getUserData(BuildContext context) async {
try {
...
} catch (e) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showSnackBar(context, e.toString());
});
}
}
}
then use it like this:
home: ScreenManager();
Note: don't forget to remove authService.getUserData(context) from _MyAppState's initState.
Not sure why I am not able to sign out.
When I click a button on the app bar, see 'sign out',
I click it,
system then asks me to confirm to sign out, I click it.
then I get an error and I am not signed out,
The expected result should be signing out without an error
CODE SNIP::
class _NotesViewState extends State<NotesView> {
void _popupMenuActionsPressed(value) async {
devtools.log('App bar $value was pressed');
switch (value) {
case MenuAction.logout:
final shouldLogout = await showLogOutDialog(context);
devtools.log('User logout');
if (shouldLogout) {
devtools.log('inside if statement');
await FirebaseAuth.instance.signOut();
Navigator.of(context)
.pushNamedAndRemoveUntil(homeRoute, (_) => false);
}
break;
default:
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Main UI'),
actions: [
PopupMenuButton<MenuAction>(
onSelected: (value) {
_popupMenuActionsPressed(value);
},
itemBuilder: (context) {
return [
const PopupMenuItem<MenuAction>(
value: MenuAction.logout,
child: Text('Sign out'),
)
];
},
)
],
),
body: const Text('Hello world'),
);
}
}
ERROR SNIP
User logout
[log] inside if statement
E/flutter (10193): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: [core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()
E/flutter (10193): #0 MethodChannelFirebase.app
package:firebase_core_platform_interface/…/method_channel/method_channel_firebase.dart:193
E/flutter (10193): #1 Firebase.app
package:firebase_core/src/firebase.dart:56
E/flutter (10193): #2 FirebaseAuth.instance
package:firebase_auth/src/firebase_auth.dart:44
E/flutter (10193): #3 _NotesViewState._popupMenuActionsPressed
package:ijob_clone_app/main.dart:81
E/flutter (10193): <asynchronous suspension>
E/flutter (10193):
The error is not from the code, it is because you are not initializing firebase in your main function.
Edit your main function as follows:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(myApp());
}
Go to your main.dart file and in the main function, you have to initialize Firebase first before you can use any of its services:
void main() async {
await Firebase.initializeApp();
...
runApp(your_widget_here);
}
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)));
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.