How to access BuildContext in main() method of Flutter launcher class? - flutter

I need to access BuildContext in main() {} method of the main.dart class to show a popUp dialog in firebase FirebaseMessaging.onMessage.listen method when a new push notification is received, how to access it while there is no BuildContext in main() ?

You can't really.
In your main function in your main.dart, you have to call runApp() to launch your first widget, and within this widget, you will have access to BuildContext since all the stateful/stateless widget will receive the BuildContext through their build method.
For example you may want to do something like this:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MySuperApplication(),
);
}
class MySuperApplicationextends StatefulWidget {
const MySuperApplication({
Key? key,
}) : super(key: key);
#override
_MySuperApplicationState createState() => _MySuperApplicationState();
}
class _MySuperApplicationState extends State<MySuperApplication> {
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
#override
Widget build(BuildContext context) {
// You have access to your BuildContext here, and you can initialize FirebaseMessaging.
_firebaseMessaging.subscribeToTopic("all");
// return any widget.
return WhatEverWidget()
}
}

Related

Flutter run a function before the buildcontext with FutureBuilder

The problem I have is that I want to run a function before the buildcontext is rendered.
What the function does is read the storage to get the token. The function works with Async Await.
Why do I want to do this?
Because in the build context I have a FutureBuilder and in this I need token data.
class ProfileScreen extends StatefulWidget {
ProfileScreen({Key? key}) : super(key: key);
#override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
dynamic token;
getToken()async{
token= await decodeJwt();
print("first this");
print(token);
return token;
}
#override
void initState() {
getToken();
super.initState();
}
#override
Widget build(BuildContext context) {
print("after this");
final profileService=Provider.of<ProfileService>(context);
final listDropDownService=Provider.of<ListDropDown>(context);
return FutureBuilder(
future: Future.wait([profileService.getSpecificWalker(token["id"]), listDropDownService.getDropDown("services")]),
builder: (context,dynamic snapshot){
as you can see i used some print also to see what the sequence was like.
But first it prints "then this" and then "first this"
My strongest suggestion would be to use some state management solution, such as flutter_bloc, that is not directly dependent on the UI components.
If not, then why don't you just try and wrap the getToken() method and the profileService.getSpecificWalker(token["id"]) call in a single async method and call that from the FutureBuilder.
What you said shows that you need state manager, because in one state you want to get token then when token get available, call the futureBuilder. I recommended bloC.

How to update state in a fcm handler with StateNotifierProvider?

In my Flutter app i need being able to react on incoming fcm messages which can instruct the app to go a different navigation tab by having a corresponding key/value pair in its data payload.
Currently the selected index is stored in the stateful widget which also hosts the bottom navigation bar:
#override
Widget build(BuildContext context) {
presentTicketsModel = ref.watch(presentTicketsModelProvider);
contractsModel = ref.watch(contractsModelProvider);
return Scaffold(
body: PersistentTabs(
currentTabIndex: index,
screenWidgets: buildPages(),
),
bottomNavigationBar: NavigationBar(
height: 60,
selectedIndex: index,
onDestinationSelected: (index) => setState(() {
this.index = index;
}),
destinations: _buildNavigationDestinations(),
),
);
}
With the new challenge i thought about moving that state index into a separate object and use Riverpod's StateNotifierProvider to provide that state object, as it is described in the official doc (https://riverpod.dev/docs/providers/state_notifier_provider).
What i don't get is: How can the following service class (which listens for incoming fcm messages) get hold of that state object and update the index in order that the watching view class gets notified and can switch to the targeted navigation tab?
class PushNotificationService {
final fcm = FirebaseMessaging.instance;
Future initialise() async {
print('initialising push notification service...');
}
/// foreground handler
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('onMessage: $message');
// here the state change would be done
});
/// handler if the app has been opened from a background state
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('onMessageOpenedApp: $message');
});
}
}
Or asked differently: How does this service class get passed a ProviderReference in order to access the mentioned state object and change the index value?
The service currently is registered with GetIt as a lazy singleton:
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerLazySingleton(PushNotificationService.new);
}
The initialise method can have a Ref parameter like so:
class PushNotificationService {
final fcm = FirebaseMessaging.instance;
Future initialise(WidgetRef ref) async {
print('initialising push notification service...');
}
/// foreground handler
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// Use ref.read here -> ref.read();
});
/// handler if the app has been opened from a background state
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('onMessageOpenedApp: $message');
});
}
}
Then you can pass a ref when calling initialise.
PushNotificationService.initialise(ref);
If you'd be calling initialise in a widget, use WidgetRef instead of Ref
EDIT: Where to pass ref (notice that we're using WidgetRef now)
Follow these steps
Make MyApp a ConsumerStatefulWidget
Call PushNotificationService.initialise(ref); in initState
Full code:
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerStatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
ConsumerState<ConsumerStatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends ConsumerState<MyApp> {
#override
initState(){
PushNotificationService.initialise(ref);
super.initState();
}
class _MyAppState extends ConsumerState<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "App",
home: Home(),
);
}
}

Flutter getx controller get the current page context

I would like to use context to show a custom dialog from cool alert in getxcontroller method.
I have created the following controller
class HomePageController extends GetxController {
#override
void onInit() {
super.onInit();
getData();
}
void getData(){
//perform http request here
//show cool alert
CoolAlert.show(
context: context, //here needs the build context
type: CoolAlertType.success
);
}
}
Am using this controller in my stateless widget like
class HomePage extends StatelessWidget {
HomePage({ Key? key }) : super(key: key);
final _c = Get.find<HomePageController>();
#override
Widget build(BuildContext context) {
return Container(
);
}
}
How can i get the current homepage BuildContext in the controller inorder to show the cool alert.
If you want to show a dialog or snackbar what need context as a required agument. You can use Get.dialog() and Get.snackbar, there function work same as showDialog and showSnackbar but *without* context or scaffod
you can add the context to the construct function:
#override
Widget build(BuildContext context) {
Get.put(HomePageController(context: context));
return Container();
}
and for the HomePageController:
note: you need to wrap the function with Future.delayed(Duration.zero)
otherwise it will throw an error
class HomePageController extends GetxController {
late BuildContext context;
HomePageController({required this.context});
void getData(){
Future.delayed(Duration.zero,(){
CoolAlert.show(
context: context, //here needs the build context
type: CoolAlertType.success
);
});
}
...
}
You Need to Initialize the controller on the homepage like following
class HomePage extends StatelessWidget {
HomePage({ Key? key }) : super(key: key);
final _c = Get.put(HomePageController())..getData(context);
#override
Widget build(BuildContext context) {
return Container(
);
}
}
This will call getData Function and remove the onInit Function and Pass Buildcontext context parameter in getData Function.

setState is not updating the UI even though state is updating

I am trying to wait till amplify configuration is done then load the login screen. Even though state seems to be getting updated I am still getting the loadinscreen. Why is that?
I am not sure if setState is proper method on the init : Importance of Calling SetState inside initState
As per the doc : https://docs.amplify.aws/start/getting-started/integrate/q/integration/flutter/#configure-amplify
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isAmplifyConfigured = false;
late AmplifyAuthCognito auth;
#override
void initState() {
_initializeApp();
super.initState();
}
Future<void> _initializeApp() async {
await _configureAmplify();
setState(() {
_isAmplifyConfigured = true;
});
}
Future<void> _configureAmplify() async {
auth = AmplifyAuthCognito();
try {
await Amplify.addPlugin(auth);
await Amplify.configure(amplifyconfig);
} on AmplifyAlreadyConfiguredException {
print(
'Amplify was already configured. Looks like app restarted on android.');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: AppRoutes.onGenerateRoute,
initialRoute: _isAmplifyConfigured
? LoginScreen.routeName
: LoadingScreen.routeName,
);
}
}
I think the issue is with you trying to reassign your initialRoute. I'm not super familiar with this property, but given the name I assume this is set once and is not rebuilt, not even when the state changes. It would make sense, also, because the rest of your code sounds like it should work.
Before trying anything else, I'd recommend you move your logic to initialize Amplify to the LoginScreen, and having its body depend on the _isAmplifyConfigured boolean value. So show spinner if it's false, and show Login fields when it's true.
Even better would be to create a HomeScreen, so you can keep this Amplify initialization at the bottom of your app's stack. And then have your HomeScreen either show the Login widgets, the home screen of your app, or a loading state.

Flutter: RouteAware triggers only didPush event

Hello i'm new to flutter and although i have already searched on this topic, it is unclear to me. So here is the code:
main.dart file
RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await DotEnv().load('.env');
final UserRepository userRepository = new UserRepository();
runApp(BlocProvider(
create: (context) => AuthenticationBloc(userRepository)..add(AppStarted()),
child: App(
userRepository: userRepository
),
));
}
profile_form.dart (is on another file and imports the routeObserver from main.dart)
class ProfileForm extends StatefulWidget {
final UserRepository _userRepository;
final User _user;
ProfileForm(
{Key key, #required UserRepository userRepository, #required User user})
: assert(userRepository != null && user != null),
_userRepository = userRepository,
_user = user,
super(key: key);
State<ProfileForm> createState() => _ProfileFormState();
}
class _ProfileFormState extends State<ProfileForm> with RouteAware {
UserRepository get _userRepository => widget._userRepository;
User get _user => widget._user;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
routeObserver.subscribe(this, ModalRoute.of(context));
super.didChangeDependencies();
}
#override
void didPush() {
print('didPush FirstPage');
}
#override
void didPopNext() {
print('didPopNext FirstPage');
}
#override
void didPop() {
print('didPop FirstPage');
}
#override
void didPushNext() {
print('didPushNext FirstPage');
}
#override
void dispose() {
print("dis");
routeObserver.unsubscribe(this);
super.dispose();
}
}
This form is entered through the Navigator.pushReplacementNamed(context, '/profile');
Although the trigger of didPush() event fires when i go back to another page (again from Nanigator or back button) and waiting for the didPop() event to fire it does not. What do i miss here? My main problem is that i want to save changes when the user exits the profile screen but before entering the init of another screen.
I had a similar problem so if this can help anyone, here's what caused it. The problem was that the RouteObserver that the RouteAware was subscribing to, was a different one from the one that was used by MaterialApp.
So the RouteObserver in MaterialApp was checking didPush & didPop events, but didn't have any listeners. The reason that you still get a didPush event, is because the observer always sends a didPush when you subscribe.
The root cause in our case, was that we used a Provider to provide the RouteObserver to the RouteAware widget, but everytime the MaterialApp was rebuilt, it got a new RouteObserver different from the one created by the provider.
Check if the RouteObserver you are subscribing to is the same one that's being used by MaterialApp, using the hashCode property!