Retry Future<void> API call on timeout - flutter

I've implemented on TimeoutException on each API calls. Initially, it was just showing a simple bottomsheet dialog that is being called from a Utils class which is accessible globally so that I don't have to paste the Widget in each API call and just call the function in the Utils class with a String message parameter.
Like:
on TimeoutException {
Utils().showError("login timeout");
}
Now to improve the UX, I added a "retry" button inside the bottomsheet dialog for the purpose of retrying/recalling the function.
I've created dynamic widgets that is reusable across the project and receives a Function and it works.
However, my issue here is how can I pass Future<void> functionName to the Utils widget so that it can be used in other API calls?
What I tried:
static Future<void> userLogin(BuildContext context, String email,
String password, bool rememberMe) async {
// does something
} on TimeoutException {
Utils().showTimeoutDialog(context, userLogin);
then in my Utils class:
showTimeoutDialog(BuildContext context, Function callback) {
onPress: () async { await callback;}
it works but the callback is being called immediately without even pressing the button.

Related

How to implement a communication chain in Flutter app with non-Flutter component

I'm trying to implement a communication chain in my app.
The app has at least to layer:
core, which is responsible for network communications. It's implemented as a Dart library
UI, which is responsible for communication with user. It's implemented as a Flutter app.
The core has a piece that handles invitations. Communication part is asynchronous and works in a way:
receive a request
handle request
send a response
void _handleMemberInviteRequest(AtNotification notification) async {
final sender = AtSignMember(atSign: notification.from);
if (await onMemberInvitation(sender)) {
send(notification.from, CommunicationVerbs.memberInviteRespond.name,
"accept");
} else {
send(notification.from, CommunicationVerbs.memberInviteRespond.name,
'reject');
}
}
onMemberInvitation is an event handler that in my understanding should be implemented in Flutter app. My problem is that I need user to accept an invitation. The whole chain of actions I see:
Request is received (core) -> onMemberInvitation is invoked (core) -> Confirmation dialog pops up (Flutter app) -> Response is returned by onMemberInvitation (core) -> Response is sent (core).
What I can't figure out is how to make Flutter to pop up the confirmation and answer with the result. I use BLoC patter for state management. So I though of having a separate Cubit that would emit a state that would be listened by a BlocListener on a top of application and pop up the dialog.
class Confirmation extends Cubit {
void askForConfirmation(sender) {
emit(ConfirmationState("InvitationConfirmation"));
}
void gotConfirmation(bool confirmation) {
emit(ConfirmationResponseState(confirmation));
}
}
and in app initialization implement an onMemberInvitation handler:
Future<bool> onMemberInvitation(sender) async {
askForConfirmation(sender);
await for (/* here somehow wait for `ConfirmationResponseState`*/) {
return confirmation;
}
}
But then I can't realise how do I wait for the response in onMemberInvitation handler.
Any ideas? Can BLoC be utilised here as well? Or because it's outside of Flutter app some custom streams have to be implemented? Or there is another way?
What you need is an async onMemberInvitation function that you can finish from outside the scope of the function itself.
You can achieve this using a Completer. This enables you to emit the result of the confirmation from anywhere while pausing the execution of onMemberInvitation until the result arrived. Check the sample below.
import 'dart:async';
Completer completer = new Completer<bool>();
void main() async {
String sender = 'test';
completer = new Completer();
if (await onMemberInvitation(sender)) {
print("accept");
} else {
print('reject');
}
}
Future<bool> onMemberInvitation(String sender) async {
askForConfirmation(sender);
print('UI event emitted');
return await completer.future;
}
void askForConfirmation(String sender) async {
// TODO: emit the state to the UI here
await Future.delayed(Duration(seconds: 3));
//TODO: call this when you get the confirmation event
gotConfirmation(true);
}
void gotConfirmation(bool confirmation) {
completer.complete(confirmation);
}

flutter : run provider function to load data before/while widget is loading the layout

Structure of code:
I have a function, that on a Button click returns a stateful widget A.
In a separate file, I have a ChangeNotifier class B, which A
needs (it needs the decoded json file) and I use the ChangeNotifier class as a general memory container which all the different widgets have access to.
B:
class Database_Controller extends ChangeNotifier {
Future<void> _readVeryLargeJsonFrame() async {
final String response =
await rootBundle.loadString('assets/tables/largejson.json');
final data = await json.decode(response);
return data;
}
Other functions to give back entries of data
}
Problem:
I would like to execute _readVeryLargeJsonFrame as soon as A is called (potentially with a loading spinner for the user) and before A is loaded (or at least in parallel).
How do I call the ChangeNotifier function in the "init" part of a stateful widget? Peter Koltai mentioned the initState method. But how do I call the ChangeNotifier function from this?
(2. Context problem: So far, I would be using Provider.of<Database_Controller>(context,listen: false)._readVeryLargeJsonFrame(); but how do I get the context argument here?)
(3. Is the Future<void> ... async nature of the _readVeryLargeJsonFrame function a problem here?)
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => yourFunction(context));
}

Navigation inside future method 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);
});
}

Calling a Future from another file in Flutter

I'm trying to get data to a different .dart file from Future.
For doing this, I created a class after Future code.
The code in pageone.dart:
class Popups extends StatefulWidget {
#override
_PopupsState createState() => _PopupsState();
}
class _PopupsState extends State<Popups> {
Future oyuncuSec() async {
await showDialog(
context: context,
...
...
}
}
class UserService {
oyuncuSec() {}
}
The code in pagetwo.dart, where I would like to call the same Future code:
import 'package:myappname/pageone.dart';
.
.
UserService userService = UserService();
.
.
RaisedButton(
onPressed: () => userService.oyuncuSec(), child: Text('Futbolcu Resmi'),),
But, when I run the page, and press on the RaisedButton (Futbolcu Resmi), it does not show up the pop-up. I'm stick on this for days. Please help me.
I see that you have created a separate class with the function definition inside it, that's the right way of doing it!
You must be missing something minimal in your code, make sure you define the class and the function as follows:
Let's say you are writing the class which has the function in a file called data.dart.
data.dart should look like this:
Class DataSource {
Future oyuncuSec() async {
await showDialog(
context: context,
...
...
}
}
Now let's say you want to use this function in a file called x.dart, this goes as follows:
in x.dart (which has a stateful widget), outside the build function:
var dataSource = new Datasource();
//an example function:
getData() async {
await dataSource.oyuncuSec(); //use a .then() in the end if the function oyuncuSec returns something and you want to do something with the result...
}
Edit: If you don't want oyuncuSec to return anything and just make a popup, there is no need to set its return type as Future. Just define it as void oyuncuSec() {//code for popup}

Complete scoped model async call in initstate before widget builds

I have a requirement which is to make an API call when the page is opened and use the result of the API call to populate a widget. I have to cater for cases like when the call is being made, show a loader, if an error occurs, show a widget that allows the user to reload the page which calls that method again if the call is successful use the result to populate the widget.
This looks like a perfect use case for a FutureBuilder, however, should the call fail, there won't be a way to reload as FutureBuilder is executed once.
An alternative is to use a StreamBuilder where I just add to the stream the error or response, this allows me to reload the page if the call fails. The streambuidler approach works fine.
Initially, I've tried to use a ScopedModel approach by making the async API call in the initstate method, however, as expected the api call takes some time and the widget Build method is called so the page is built before the api call completes and when it's done, even if I call notifylisteners() the widget tree doesn't rebuild with the data from the API call. Is there a way to make this work using a ScopedModel approach?
DashboardService.dart
Future<string> getDashboardState() async{
try{
var response = await client.get('/getdashboardstate');
return response;
}catch(error){
return error;
}
}
DashboardModel.dart
Future<BaseDataResponse<MambuLoan>> getDashboardState() async {
try {
setState(ViewState.Busy);
var info = await _dashboardService.getDashboardState();
if (info.isSuccessful) {
return info;
} else {
setState(ViewState.Error);
return info;
}
} catch (error) {
setState(ViewState.Error);
return error;
}
}
DashboardView.dart
#override
void initState() {
// TODO: implement initState
super.initState();
getDashboard() // result of this set some data in the scopedmodel
}
Future<String> getDashboard() async{
var response = await dashboardModel.getDashboardState();
return response;
}
Widget Build(BuildContext context){
//gets data in scoped model to build widget
return Container() //actual widget here
}
UPDATE
I Was approaching this the wrong way. I ended up not needing a stateful widget but a stateless one in which I called my Future in the build method before returning a widget.