Navigation inside future method flutter - flutter

I am trying to navigate to a screen from a future method. However I get an error saying undefined name context. I tried navigating from Widget build but the parameter is created within this method and I need it for navigating. I've been stuck on this for a very long time. Any help will be really appreciated.
Future<void> addBookingConversation(Booking booking) async {
Conversation conversation = Conversation();
await conversation.addConversationToFirestore(booking.posting.host); //additional method working fine
String text = "Hi, my name is ${AppConstants.currentUser.firstName}";
await conversation.addMessageToFirestore(text); //additional method working fine
//this is where i should navigate to the conversation page and facing the error here
Navigator.push(
context, //error here context undefined
MaterialPageRoute(builder:
(context) => ConversationPage(conversation: conversation,),
),
);
}
class ConversationPage extends StatefulWidget {
final Conversation conversation;
static final String routeName = '/conversationPageRoute';
ConversationPage({this.conversation, Key key}) : super(key: key);
#override
_ConversationPageState createState() => _ConversationPageState();
}
class _ConversationPageState extends State<ConversationPage> {
Conversation _conversation;
// additional code of wiget build
}

I don't know where your function resides, so this is some general advice:
If you cannot access a variable in your method you have two options: pass it in as a parameter from the caller. Or return the result to the caller so they can do the part where the variable is needed themselves.
What does that mean for your scenario: either you need the context as an additional parameter in your method, or you need to return Future<Conversation> from your method and handle the navigation where it's called.
Personally, I'd favor the second option, since your business logic of starting a conversation and your in-app navigation are two different concerns that should not be mixed in one method.

If you want to call the navigator method anywhere in the app.
class NavigationService {
final GlobalKey<NavigatorState> globalKey = GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(Route Route) {
return globalKey.currentState.push(Route);
}
}
and in main.dart.
navigatorKey: NavigationService().globalKey,
and then anywhere within the app.
Just use this
Future<void> addBookingConversation(Booking booking) async {
Conversation conversation = Conversation();
await conversation.addConversationToFirestore(booking.posting.host);
//additional method working fine
String text = "Hi, my name is ${AppConstants.currentUser.firstName}";
await conversation.addMessageToFirestore(text); //additional method working
fine
//this is where i should navigate to the conversation page and facing the
error here
NavigationService().navigateTo(
MaterialPageRoute(builder:
(context) => ConversationPage(conversation: conversation,),
),);
}

Wrap your Navigator inside :
WidgetsBinding.instance.addPostFrameCallback((_){
// 👈 Your Navigation here
});
Your Code:
Future<void> addBookingConversation(Booking booking) async {
...
WidgetsBinding.instance.addPostFrameCallback((_){
Navigator.push( //👈 add your navigation here
context, //error here context undefined
MaterialPageRoute(builder:
(context) => ConversationPage(conversation: conversation,),
),
);
...
}

This method help you to navigate the route without FutureBuilder. see the code
onPressed: () async {
// then await the future You want to complete and then use `.then()`
//method to implement the code that you want to implement when the future is completed
await //call your future widget //
.then((result) {
print('future completed');
// Navigate here
// For errors use onError to show or check the errors.
}).onError((error, stackTrace) {
print(error);
});
}

Related

How can i reload my page every time i am on it on my flutter app?

Assume that I'm on page-A now. I navigate to page-B. When I pop the page-B and come back to page-A, currently nothing happens. How can I reload page-A and load the new API data from the init state of page-A? Any Ideas?
first main page
void refreshData() {
id++;
}
FutureOr onGoBack(dynamic value) {
refreshData();
setState(() {});
}
void navigateSecondPage() {
Route route = MaterialPageRoute(builder: (context) => SecondPage());
Navigator.push(context, route).then(onGoBack);
}
second page
RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go Back'),
),
more details check here
From the explanation that you have described, so when you are popping the page.
This below Code will be on the second page.
Navigator.of(context).pop(true);
so the true parameter can be any thing which ever data that you want to send.
And then when you are pushing from one page to another this will be the code.
this is on the first page.
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const PageOne()),
);
so if you print the result you will get the bool value that you send from the second page.
And based on bool you can hit the api. if the bool true make an api call.
Let me know if this works.
There are one more solutions for this situtation.
Ä°f you want to trigger initState again
You can use pushAndRemoveUntil method for navigation. ( if you use only push method this is not remove previous page on the stack)
You can use key
You can set any state manegement pattern.( not for only trigger initState again)
There are 2 ways:
Using await
await Navigator.push(context, MaterialPageRoute(builder: (context){
return PageB();
}));
///
/// REFRESH DATA (or) MAKE API CALL HERE
Passing fetchData constructor to pageB and call it on dispose of pageB
class PageA {
void _fetchData() {}
Future<void> goToPageB(BuildContext context) async {
await Navigator.push(context, MaterialPageRoute(builder: (context) {
return PageB(onFetchData: _fetchData);
}));
}
}
class PageB extends StatefulWidget {
const PageB({Key? key, this.onFetchData}) : super(key: key);
final VoidCallback? onFetchData;
#override
State<PageB> createState() => _PageBState();
}
class _PageBState extends State<PageB> {
#override
void dispose() {
widget.onFetchData?.call();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}

How to refactor this code to avoid passing BuildContext between async scopes?

I have a PermissionsManager class, and I'm getting a "Do not use BuildContext across async gaps" for this particular method:
class PermissionsManager {
static Future<void> requestLocationPermission(BuildContext context) async {
final status = await Permission.location.request();
if (!status.isGranted) {
await showOpenSettingsDialog(context,
title: "Grant Location Access",
message:
"TODO");
}
}
}
I thought about splitting this into multiple functions, but then the caller needs to check the status, and based on the status call another method that will show this dialog box.
Is there a way to do this in the same method and handle this build context issue?
Good question! Assuming you are in a "Stateful Widget", add if (mounted) check before using BuildContext across an async gap.
For example:
onPressed: () async {
final status = await Future.delayed(const Duration(seconds: 1));
if (mounted) { // <-- add this check
if (!status.isGranted) {
showOpenSettingsDialog(context);
}
}
}
The reason we don't want to use BuildContext across an async gap is because the widget could've become unmounted during the wait. If we check if (mounted) we won't have this concern. Basically, if the widget is somehow destroyed during the wait, we just don't show the dialog any more.
If this is a stateless widget, you can convert it into a stateful one.
You can also read my detailed explanation on this topic here.
Store the NavigatorState before executing your requestLocationPermission function, and then use it to handle the navigation:
onPressed: () async {
final navigator = Navigator.of(context); // store the Navigator
await requestLocationPermission(navigator); // use the Navigator, not the BuildContext
},
class PermissionsManager {
static Future<void> requestLocationPermission(NavigatorState navigator) async {
final status = await Permission.location.request();
if (!status.isGranted) {
await showOpenSettingsDialog(
context,
title: "Grant Location Access",
message: "TODO",
);
}
navigator.pop(); // Do whatever with your navigator;
}
}
This answer is basically a shorthand of: https://stackoverflow.com/a/69512692
Which I highly suggest for you to look at it for a more detailed explanation.

Flutter use context when handling dynamic link from app closed

I see this weird behaviour when handling dynamic links. What I want to do is that when coming from a link containing matchId parameter I want to clean up the navigation stack, put up the AvailableMatches page and after the MatchDetails page and finally show a modal. This is the code I use in the link handler
FirebaseDynamicLinks.instance.onLink(
onSuccess: (PendingDynamicLinkData dynamicLink) async {
final Uri deepLink = dynamicLink?.link;
if (deepLink != null) {
handleLink(deepLink);
});
Future<void> handleLink(Uri deepLink) async {
var context = navigatorKey.currentContext;
var matchId = deepLink.queryParameters["match_id"];
Navigator.of(context).pushNamedAndRemoveUntil(
AvailableMatches.routeName,
(Route<dynamic> route) => false
);
Navigator.of(context).pushNamed(MatchDetails.routeName,
arguments: ScreenArguments(
matchId, false)
);
await showModalBottomSheet(context: context, builder: (context) => Text("done"));
}
If the app is already open this works fine. If the app is starting from this link I have the following initState in the first StatefulWidget
void initState() {
super.initState();
initDynamicLinks();
loadData(context);
}
Future<void> loadData(BuildContext context) async {
final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink();
handleLink(data.link);
}
In this case the handeLink method works until the latest model. The pages are pushed correctly on the stack however the last model doesn't show up.
I am adding prints and things like that but it seems that this line never gets executed. There is no crash or exception. It just gets ignored

How to get data from bloc stream from other page in flutter

I have a problem like this :
In Splash Page , i check in sharedpreference to get saved token when login successfully .If i have token , i request Api to get account information and move to next page like this:
Future check() async {
String _getToken = await splashBloc.getTokenFormSharedPref();
if (_getToken=='0') {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => LoginMain()));
} else {
splashBloc.getAccountInfo();
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => HomeScreenMain()));
}
}
and this is BLoC class:
class SplashBloc extends BlocBase{
String _getToken = '';
Future<String> getTokenFormSharedPref() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
_getToken = (prefs.getString('token') ?? '0');
return _getToken;
}
final accountInfoController = new StreamController<Account>();
Sink<Account> get accountInfoSink => accountInfoController.sink;
Stream<Account> get accountInfoStream => accountInfoController.stream;
Future getAccountInfo() async{
Account account = await NetworkService().getAccountInfo2(_getToken);
accountInfoSink.add(account);
print('from splash: '+account.fullName);
}
#override
void dispose() {
accountInfoController.close();
}
}
When i check log , it totally request successfully and the problem is how can i acesss data in streambuilder in next page that is HomeScreenMain()?
Thanks for help!!
You can declare a variable in HomeScreenMain() and send the data you received before to the class constructor like this:
HomeScreenMain() {
final data;
HomeScreenMain(this.data)
//....
}
and when you want to call this widget you can pass that data from block to this widget
You appear to use a very basic approach with BLoC. Not sure if my answer helps there.
But if you use the library flutter_bloc, then you can use on the next page
Ë‹final bloc = context.read;
This looks for a provider of this bloc type upstream in the Widget tree and assigns it to Ë‹bloc

Flutter: Async function in Getx Controller takes no effect when initialized

Updates:
2021/06/11 After hours of debugging yesterday, I confirmed that the problem is caused by aws amplify configuration: _configureAmplify(). Because the location of the amplify server was set wrong, so _configureAmplify() takes several seconds to work... and therefore, the readPost() function did not work on initialization, as it must run after _configureAmplify()...
2021/06/10I made changes to my code according to S. M. JAHANGIR's advice, and updated the question. The issue still presists. The value of posts is not updated when called in initialization and the data only shows up after reload. (if I commented out the _controller.readPost() in UI, the value of posts is always empty.
I have this page that loads information from aws amplify with getx implemented. However, I found out the readPost() async funtion in getx controller dart file is not reading from database, when the controller instance is initialized. I have to add a _controller.readPost() in UI file to make it work. And the data only shows up after a reload of that UI page...
Getx Controller dart file:
class ReadPostController extends GetxController {
var isLoading = true.obs;
var posts = <Posty>[].obs;
#override
void onInit() {
_configureAmplify();
await readPost();
super.onInit();
// print('show post return value: $posts');
}
void _configureAmplify() {
final provider = ModelProvider();
final dataStorePlugin = AmplifyDataStore(modelProvider: provider);
AmplifyStorageS3 storage = new AmplifyStorageS3();
AmplifyAuthCognito auth = new AmplifyAuthCognito();
AmplifyAPI apiRest = AmplifyAPI();
// Amplify.addPlugin(dataStorePlugin);
Amplify..addPlugins([dataStorePlugin, storage, auth, apiRest]);
Amplify.configure(amplifyconfig);
print('Amplify configured');
}
// read all posts from databases
Future readPost() async {
try {
isLoading(true);
var result = await Amplify.DataStore.query(Posty.classType);
print('finish loading request');
result = result.sublist(1);
posts.assignAll(result);
// print(the value of posts is $posts');
} finally {
isLoading(false);
}
}
#override
void onClose() {
// called just before the Controller is deleted from memory
super.onClose();
}
}
And in the UI part:
class TabBody extends StatelessWidget {
TabBody({Key? key}) : super(key: key);
final ReadPostController _controller = Get.put(ReadPostController());
#override
Widget build(BuildContext context) {
_controller.readPost();//if commented out, _controller.post is empty
return Container(
child: Obx(
() => Text('showing:${_controller.posts[1].title}'),
));
}
}
In my understanding, the readPost() function should be called when the ReadPost_controller is initiallized. And the UI will update when the posts = <Posty>[].obs changes. Guys, what am I doing wrong here?
First, when you are calling readPost on onInit you are not awaiting. So change it to:
onInit() async{
...
await readPost();
...
}
Secondly, posts is a RxList so you need to use the assignAll method to update it.
Therefore, in your readPost method, instead of posts.value = reault you need to use posts.assignAll(result)
Calling from the UI works because readPost every time the build method is called by the Flutter framework and actually the UI shows the data from every previous call.
I think try with GetBuilder instead of Obx.
GetBuilder<ReadPostController>(
builder: (value) => Text('showing:${value.posts[1].title}'),
)
and also use update(). in readPost() method.