How to get AlertDialog Callback from method in Flutter? - function

I have AlertDialog in static method, in that I wants to get callback when user clicks on OK button.
I tried using typedef but can't understand.
Below is My Code:
class DialogUtils{
static void displayDialogOKCallBack(BuildContext context, String title,
String message)
{
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text(title, style: normalPrimaryStyle,),
content: new Text(message),
actions: <Widget>[
new FlatButton(
child: new Text(LocaleUtils.getString(context, "ok"), style: normalPrimaryStyle,),
onPressed: () {
Navigator.of(context).pop();
// HERE I WANTS TO ADD CALLBACK
},
),
],
);
},
);
}
}

You can simply wait for the dialog to be dismissed {returns null} or closed by clicking OK which, in this case, will return true
class DialogUtils {
static Future<bool> displayDialogOKCallBack(
BuildContext context, String title, String message) async {
return await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text(title, style: normalPrimaryStyle,),
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text(LocaleUtils.getString(context, "ok"), style: normalPrimaryStyle,),
onPressed: () {
Navigator.of(context).pop(true);
// true here means you clicked ok
},
),
],
);
},
);
}
}
And then when you call displayDialogOKCallBack you should await for the result
Example:
onTap: () async {
var result =
await DialogUtils.displayDialogOKCallBack();
if (result) {
// Ok button is clicked
}
}

Then Callback function for Future work:
DialogUtils.displayDialogOKCallBack().then((value) {
if (value) {
// Do stuff here when ok button is pressed and dialog is dismissed.
}
});

This thread is a bit old, but I found a solution that wasn't touched upon, so I thought I'd add it here.
I had a form in my AlertDialog, and I needed to keep the dialog open if there were any errors. This solution worked for me.
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
Future _showEditDialog(BuildContext context) {
return showDialog(
context: context,
builder: (context) {
return WillPopScope(
onWillPop: () async {
return formKey.currentState!.validate();
},
child: AlertDialog(
title: const Text("Awesome AlertDialog"),
content: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Form(
key: formKey,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value!.isEmpty) return "Please Fill Out This Field";
return null;
},
),
],
),
),
),
actions: <Widget>[
MaterialButton(
child: const Text("Cancel"),
onPressed: () {
Navigator.pop(context);
},
),
],
),
);
},
);
}
The important part is the WillPopScope that I wrapped the AlertDialog. I think this absorbs all the Navigator.pop() calls and passes them through the onWillPop parameter. This parameter is passed an async function which returns a Future. I just returned the validation check boolean, but in the real world there would also be a http request here too.
Remember to add a way for the user to cancel the form without triggering the form validation. I just added a cancel button that runs Navigator.pop().
Hope this helps, let me know if anyone has any questions.

Related

User Input to Fetch data from API in Flutter

I need to fetch data from an API by taking an User Input number. When an user types a number in the txt form field, it generates the fact for that number.
API I used is NumbersApi.
This is my function for fetching data -
String fact;
int number;
void userInputForTrivia() async {
http.Response response;
response = await http.get(Uri.http('numbersapi.com', '$number/trivia'));
if (response.statusCode == 200) {
print(response.statusCode);
print(response.body);
setState(() {
fact = response.body;
});
}
}
#override
void initState() {
userInputForTrivia();
super.initState();
}
Following is the code for Text form filled-
AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
title: Text('Enter your lucky number'),
content: TextFormField(
cursorColor: Colors.purple[400],
keyboardType: TextInputType.number,
controller: _textFieldController,
autofocus: true,
decoration: InputDecoration(hintText: "Enter any number"),
),
actions: [
ElevatedButton(
onPressed: () {
userInputForTrivia();
},
child: Text("OK"),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all<Color>(Colors.purple[400]),
),
)
],
);
This is the txt widget-
Text(fact.toString(),
style: TextStyle(
fontSize: 20.0,
color: Colors.purple[400],
)),
Whenever user presses a button an alert dialog box is popped up in which user needs to type a number to generate a fact for that number.
But whenever I type a number its not showing the fact, what am I missing?
Any help will be much appreciated :)
add onChanged method
TextFormField(
//.... rest of the code ......
onChanged: (value) {
number = int.parse(value);
},
//.... rest of the code ......
),

Flutter: How to extract a function for re-use in multiple screens

I have an onBackPressed() function that displays an AlertDialog and returns a Future depending on user's choice by pressing EXIT or CANCEL buttons. Here's the code:
// BACK PRESSED BUTTON HANDLER (FUNCTION)
Future<bool> onBackPressed() {
return showDialog(
context: context,
builder: (context) => AlertDialog(
actionsPadding: EdgeInsets.only(right: 18, bottom: 10),
// title: Text('Are you sure?'),
content: Text('Do you want to exit the application?'),
actions: <Widget>[
GestureDetector(
onTap: () => Navigator.of(context).pop(false),
child: Text("CANCEL"),
),
SizedBox(width: 20),
SizedBox(height: 16),
GestureDetector(
onTap: () => Navigator.of(context).pop(true),
child: Text("EXIT"),
),
],
),
) ??
false;
}
I need to use this function in multiple screens to handle onBackPressed events with the help of the WillPopScope widget, like this:
return WillPopScope(
onWillPop: onBackPressed,
child: Scaffold(
appBar: AppBar( ...etc.
I tried to extract the function in a separate file but I get a problem with a missing context, required by showDialog.
How can I handle this? I feel that I missed some rather basic concepts but nevertheless would be grateful for anybody helping me in the right direction.
Pass context to your function as
Future<bool> _onBackPressed(BuildContext context) {
...
}
and use as
//inside build(context) => Widget;
return WillPopScope(
onWillPop: () => _onBackPressed(context),// context must be visible here
child: ...
You missed to accept a context parameter, so you showDialog() doesn't know where to draw.
Something like this would do:
Future<bool> _onBackPressed(BuildContext context) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
actionsPadding: EdgeInsets.only(right: 18, bottom: 10),
// title: Text('Are you sure?'),
content: Text('Do you want to exit the application?'),
actions: <Widget>[
GestureDetector(
onTap: () => Navigator.of(context).pop(false),
child: Text("CANCEL"),
),
SizedBox(width: 20),
SizedBox(height: 16),
GestureDetector(
onTap: () => Navigator.of(context).pop(true),
child: Text("EXIT"),
),
],
),
) ??
false;
}
And every time you call it pass the context of the widget.
_onBackPressed(context)

Flutter how to save list data locally

I am building a to-do list app and I would like to store the data locally such that every time I open the app, I get all the tasks that I had created previously. I am new to flutter and this is my first app. I already tried saving the data to a file, creating a JSON file to save the data and tried using a database. Nothing seems to work. Can someone help me with this?
This is my code: -
import 'package:flutter/material.dart';
class toDoList extends StatefulWidget
{
bool data = false;
#override
createState()
{
return new toDoListState();
}
}
class toDoListState extends State<toDoList>
{
List<String> tasks = [];
List<bool> completedTasks = [];
List<String> descriptions = [];
List<bool> importance = [];
#override
Widget build(BuildContext context)
{
return new Scaffold
(
body: buildToDoList(),
floatingActionButton: new FloatingActionButton
(
onPressed: addToDoItemScreen,
tooltip: 'Add Task',
child: new Icon(Icons.add),
),
);
}
Widget buildToDoList()
{
return new ListView.builder
(
itemBuilder: (context, index)
{
if(index < tasks.length)
{
if(tasks[index] == "#45jiodg{}}{OHU&IEB")
{
tasks.removeAt(index);
descriptions.removeAt(index);
importance.removeAt(index);
}
return row(tasks[index], descriptions[index], index);
};
},
);
}
Widget row(String task, String description, int index)
{
return Dismissible(
key: UniqueKey(),
background: Container(color: Colors.red, child: Align(alignment: Alignment.center, child: Text('DELETE', textAlign: TextAlign.center, style: TextStyle(color: Colors.white, fontSize: 18),))),
direction: DismissDirection.horizontal,
onDismissed: (direction) {
setState(() {
tasks.removeAt(index);
if(completedTasks[index])
{
completedTasks.removeAt(index);
}
descriptions.removeAt(index);
importance.removeAt(index);
});
Scaffold.of(context).showSnackBar(SnackBar(content: Text(task+" dismissed")));
},
child: CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
title: Text(task, style: (completedTasks[index]) ? TextStyle(decoration: TextDecoration.lineThrough) : TextStyle(),),
subtitle: Text(descriptions[index], style: (completedTasks[index]) ? TextStyle(decoration: TextDecoration.lineThrough) : TextStyle(),),
isThreeLine: true,
secondary: (importance[index])? Icon(Icons.error, color: Colors.red,) : Text(''),
value: completedTasks[index],
onChanged: (bool value) {
setState(() {
if(completedTasks[index])
{
completedTasks[index] = false;
}
else
{
completedTasks[index] = true;
}
});
},
));
}
void addToDoItemScreen() {
int index = tasks.length;
while (importance.length > tasks.length) {
importance.removeLast();
}
importance.add(false);
tasks.add("#45jiodg{}}{OHU&IEB");
descriptions.add("No Description");
completedTasks.add(false);
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
return StatefulBuilder(builder: (context, setState) { // this is new
return new Scaffold(
appBar: new AppBar(title: new Text('Add a new task')),
body: Form(
child: Column(
children: <Widget>[
TextField(
autofocus: true,
onSubmitted: (name) {
addToDoItem(name);
//Navigator.pop(context); // Close the add todo screen
},
decoration: new InputDecoration(
hintText: 'Enter something to do...',
contentPadding: const EdgeInsets.all(20.0),
border: OutlineInputBorder()),
),
TextField(
//autofocus: true,
//enabled: descriptions.length > desc,
onSubmitted: (val) {
addDescription(val, index);
},
decoration: new InputDecoration(
hintText: 'Enter a task decription...',
contentPadding: const EdgeInsets.all(20.0),
border: OutlineInputBorder()),
),
Row(
children: <Widget> [
Switch(
value: importance[index],
onChanged: (val) {
setState(() {
});
impTask(index);
},
),
Text('Important Task', style: TextStyle(fontSize: 18)),
],
),
RaisedButton(onPressed: () { Navigator.pop(context); }, child: Text('DONE', style: TextStyle(fontSize: 20)),)
],
),
));
});
}));
}
void addToDoItem(String task)
{
setState(() {
tasks.last = task;
});
}
void addDescription(String desc, int index)
{
setState(() {
descriptions.last = desc;
});
}
void impTask(int index)
{
setState(() {
if(importance[index])
{
importance[index] = false;
}
else
{
importance[index] = true;
}
});
}
}
I have 4 lists with the data. I need a simple way to save the lists such that the next time I open the app, the lists retain the data that was saved in them, the last time I had closed the app.
To do this you'll certainly have to use the path_provider package with this tutorial on the flutter.dev website. You should then be able to register a file and read it at the start of your application.
Once you have imported the path_provider and the dart:io packages, you can do something like this :
final directory = await getApplicationDocumentsDirectory();
final File file = File('${directory.path}/jsonObjects.json');
if (await file.exists()) {
json = await file.readAsString();
} else {
file.writeAsString(json);
}
First you get the application document directory ( the path ), then you create a File with the right path. Then if the file already exist, you read it, else you create it with the json you got and you should be good to go !

Why can't I retreive my data? (Flutter-Rest API)

I've been trying to retreive data from my database using REST API. There are not error in my code but there is no data shown in my emulator and it keeps showing circular progress indicator (that means no data)
Is this because of token that I want to get from shared preferences? or something else?
This is my getToken code :
String token;
Future<String> getToken() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String token = prefs.getString("token");
return token;
}
This is my ShowPost code :
Future<List<Post>> showPosts() async {
String token = await getToken();
var data = await http.get(
"https://api-wisapedia.herokuapp.com/posts?sortBy=createdAt:desc",
headers: {HttpHeaders.authorizationHeader: 'Bearer $token'},
);
var dataDecoded = json.decode(data.body);
List<Post> posts = List();
dataDecoded.forEach((post) {
post.add(post["destination"], post["owner"], post["image"]);
});
return posts;
}
And this is my body code :
FutureBuilder(
future: showPosts(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Card(
color: Color(0xFFE1F5FE),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Image(
image: AssetImage('lib/images/pantai.jpg')),
title: Text(snapshot.data[index].title),
subtitle: Text(snapshot.data[index].destination),
),
ButtonTheme.bar(
// make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('DETAILS'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return DetailPost();
}),
);
},
),
FlatButton(
child: const Text('JOIN'),
onPressed: () {
/* ... */
},
),
],
),
),
],
),
);
});
} else {
return Align(
alignment: FractionalOffset.center,
child: CircularProgressIndicator(),
);
}
})
How can I solve this?
remove the () in the future parameter of the future builder
future : showPosts not future :showPosts().
Edit :
replace FutureBuilder with FutureBuilder<List<Post>> and AsyncSnapshot with AsyncSnapshot<List<Post>> to specify the data type of the incoming data.

Is there a way to have an argument with two types in Dart?

For navigation, I built a simple factory class that generates a ListTile that pushes a route to the Navigator:
static Widget simpleNavRow(String text, BuildContext context, String route) {
return Column(
children: <Widget>[
ListTile(
title: Text(text),
onTap: () {
Navigator.pushNamed(context, route);
},
),
Divider(),
],
);
}
However, I soon realized that it would be convenient to support pushing widgets as well (or instantiate from their class if possible). I couldn't figure out how to make the "route" argument accept either a String or a Widget, so I created a class that initializes with one of those two types. This code works, but is there a better way to achieve this?
class NavTo {
String route;
Widget widget;
NavTo.route(this.route);
NavTo.widget(this.widget);
push(BuildContext context) {
if (route != null) {
Navigator.pushNamed(context, route);
}
if (widget != null) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return widget;
}));
}
}
}
class ListHelper {
static final padding = EdgeInsets.all(12.0);
static Widget simpleNavRow(String text, BuildContext context, NavTo navTo) {
return Column(
children: <Widget>[
ListTile(
title: Text(text),
onTap: () {
navTo.push(context);
},
),
Divider(),
],
);
}
}
// usage:
// ListHelper.simpleNavRow('MyWidget', context, NavTo.widget(MyWidget()))
Since you are expecting one of multiple types, what about having dynamic and then in the push method of NavTo, you could check the type:
class NavTo {
dynamic route;
push(BuildContext context) {
if (route is String) {
...
} else if (route is Widget) {
...
}
}
}
I don’t believe the union type is available in Dart. I like your solution over the use of dynamic as it is strongly typed.
You could use named parameters.
NavTo({this.route,this.widget})
But then you don’t have compile-type checking for one and only one parameter.
The only improvement I would make to your constructors is to add #required.
Personnally i like to give Items a MaterialPageRoute params
static Widget simpleNavRow(String text, BuildContext context, MaterialPageRoute route) {
return Column(
children: <Widget>[
ListTile(
title: Text(text),
onTap: () {
Navigator.push(context, route);
},
),
Divider(),
],);
}
items stays dumb like this and i decide what they do in the parent.
After you can create an item factory for each type you have that initialize the correct route like this :
class ItemExemple extends StatelessWidget {
final String text;
final MaterialPageRoute route;
ItemExemple(this.text, this.route);
factory ItemExemple.typeA(String text, BuildContext context) =>
new ItemExemple(text, new MaterialPageRoute(builder: (context) => new ItemA()));
factory ItemExemple.typeB(String text, BuildContext context) =>
new ItemExemple(text, new MaterialPageRoute(builder: (context) => new ItemB()));
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
ListTile(
title: Text(this.text),
onTap: () {
Navigator.push(context, route);
},
),
Divider(),
],);
}
}