I have the following code snippet:
_openEditListingDialog(Listing listing,
Function(Listing) onSubmittedCallback) async {
Navigator
.of(context)
.push(
new MaterialPageRoute<Listing>(
builder: (BuildContext context) {
return new ListingDialog.edit(listing);
},
fullscreenDialog: true,
),
)
.then((Listing newEntry) {
if (newEntry != null) {
newEntry.id = listing.id;
onSubmittedCallback(newEntry);
}
});
}
VSCode complains on the .then line with the following error:
[dart] The argument type '(Listing) -> Null' can't be assigned to the parameter type '(Object) -> FutureOr<Null>'.
What does this mean and how do I correct this? I am new to dart and flutter and error messages like this are cryptic for me. My code is based on this example: https://github.com/MSzalek-Mobile/weight_tracker/blob/v0.4.1/lib/home_page.dart.
TBH I'm not sure why VS Code is flagging that as an error for you - I see nothing substantially wrong with it and neither does my VS Code or IntelliJ. I pasted your code in and after adding a Listing class, I had no problem at all (for the record, if you paste enough code that someone can actually run things when you have a problem it makes it a lot easier for them to help you).
And in this particular case, the line number for the error might be quite helpful.
Maybe make sure your VS Code and Flutter are up to date (I'm using the Master channel/branch which has been updated to dart 2.0 so that might be part of it maybe...).
There are a couple of things you could try. First, it seems as though your push method isn't picking up the right type - I'm fairly sure the error you're seeing is at the .then(... where it expects and Object and you're telling it to be a Listing. You can change your .push to .push<Listing> to force it to return a Listing.
Next, I'm not actually familiar with what you're doing with Function(Listing). I don't think that's proper dart, or at least any more. I think the recommended way of doing that is typedef-ing a method with the right parameter type i.e. typedef void ListingCallback(Listing listing); and then using that as the argument, if you're going to use a callback (see later on in the answer for an arguably better alternative).
The other thing you're doing wrong, which many people seem to do at first in dart/flutter, is mis-using async. If you use async in a method signature, you should not be using .then for futures, but rather using await. See here and this part of effective dart. I've re-written your code to do that properly:
//outside class somewhere
typedef void ListingCallback(Listing listing);
...
_openEditListingDialog(
Listing listing, ListingCallback onSubmittedCallback) async {
Listing newEntry = await Navigator.of(context).push<Listing>(
new MaterialPageRoute<Listing>(
builder: (BuildContext context) {
return new ListingDialog.edit(listing);
},
fullscreenDialog: true,
),
);
if (newEntry != null) {
newEntry.id = listing.id;
onSubmittedCallback(newEntry);
}
}
Note that this also gets rid of that whole function that was causing you problems.
Also, you could just use futures and remove the async in the method signature, but then you're going to have to make sure to actually return a future.
And lastly, you could (should) probably be returning a Future rather than passing in a callback.
Future<Listing> _openEditListingDialog(Listing listing) async {
Listing newEntry = await Navigator.of(context).push<Listing>(
new MaterialPageRoute<Listing>(
builder: (BuildContext context) {
return new ListingDialog.edit(listing);
},
fullscreenDialog: true,
),
);
if (newEntry != null) {
newEntry.id = listing.id;
}
return newEntry;
}
You pass a function
Function(Listing) onSubmittedCallback) async {
// no return here
});
This means Dart infers return type Null
I think changing it to
FutureOr<Null> Function(Listing) onSubmittedCallback) async =>
and removing } at the end (last line of your code) should fix it.
If you want to keep the function body {...} use
FutureOr<Null> Function(Listing) onSubmittedCallback) async {
await Navigator
.of(context)
...
Related
I tried to create a "Future" function that returns the "Navigator.push" class instead of "Widget".
I tried the normal method but it didn't work, the current script is like this:
...
return new FutureBuilder<Map<String, dynamic>>(
future: fetchUserQR(new http.Client(),snapshot.data), //scan qr code
builder: (context1, snapshot1) {
if(snapshot1.hasData) {
return Navigator.push( //this the problem
...
my goal is, when I finish scanning the QR code a new page will appear.
hopefully my explanation can be understood.
thank you, best regards.
You need to return a Widget in the futurebuilder's builder method. So return a Container and after this frame push a new page.
return new FutureBuilder<Map<String, dynamic>>(
future: fetchUserQR(new http.Client(),snapshot.data), //scan qr code
builder: (context1, snapshot1) {
if(snapshot1.hasData) {
SchedulerBinding.instance.addPostFrameCallback((_) {
// Navigator.push....
});
return Container();
}
//...
I'm experiencing an annoying thing, where the first item in my listview keeps re-render, when scrolling up. Even if I'm at the top.
only way i noticed this, was because I have a widget, that on load, fetches an url, and get the meta title, description and image, and displaying it in a nice card.
My listviews are fairly simple:
ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
itemCount: model.posts.posts.length,
itemBuilder: (context, index) {
// Items goes here
});
How do I stop it from happening?
The widget that keeps re-rendering, is a stateless widget that imports a ScopedModel model, that fetches some data from the internet, and scraped for meta data, and then updated the model.
#override
Widget build(BuildContext context) {
UrlEmbedModel _model = new UrlEmbedModel(); // <-- the ScopedModel
_model.fetchHtml(url); // <-- the url param comes from the constuctor
// Rest of the widget
}
Here is the code that fetches content from the net.
void fetchHtml(url) {
http.get(url).then((response) {
if (response.statusCode == 200) {
// If server returns an OK response, parse the JSON
var document = parse(response.body);
var list = document.getElementsByTagName('meta');
for (var item in list) {
if (item.attributes['property'] == "og:title") {
_title = item.attributes['content'];
}
if (item.attributes['property'] == "og:description") {
_description = item.attributes['content'];
}
if (item.attributes['property'] == "og:image") {
_imageUrl = item.attributes['content'];
}
notifyListeners();
}
} else {
// If that response was not OK, throw an error.
throw Exception('Failed to load post');
}
});
}
The code you wrote, seems OK, but what about the function that makes the request? Can you show it?
If it's a Future function, it'll only make a request once and then finish it, it's not like a stream function that will be always listening to an event.
EDIT
First of all, if this functions makes a request, then, the type of the functions must be Future, void type if don't return anything, after that, add the async call. You could change the .then method to an await method, it'll suit you better.
Future<void> fetchHtml(url) async {
final Response response = await get(url);
final dynamic documents = json.decode(response.body); //import 'dart:convert';
print(documents); // print to see what you get than, pass it to the variables you want the data
if (response.statusCode == 200) {
//in here
}
}
I can see a feel things in the fetch request, I'd be glad if you answer it:
Why you're not deserializing the json you receiving?
var documents = json.decode(response.body)
You could print the documents variable after deserializing it and atribute it to the widgets you want
The way you're doing it it's not wrong, but could improve it.
Found the culprit.
The issue wasn't the listview, it was the RefreshIndicator that I used.
As soon I removed it, the issue went away.
This seems to be a bug with Widget.
I have a Dart function that looks like:
Future beAwesome() {
if (notActuallySupported) {
return new Future.error(new UnsupportedError('uh oh'));
}
return new Future.value(42);
}
// ...
beAwesome().then((answer) => print(answer));
I want to use the new async/await functionality. How do I change my function?
In general, add the word async after your function's signature and before the {. Also, return raw values instead of wrapping those values in futures. Also, throw actual exceptions instead of wrapping the errors with a future.
Here's the new version:
Future beAwesome() async {
if (notActuallySupported) {
throw new UnsupportedError('uh oh');
}
return 42;
}
// ...
var answer = await beAwesome();
print(answer);
Note that you should still use Future as the return-type annotation.
I defined some class to query a database.
class SqlGetData {
ConnectionPool pool;
List<String> rows;
SqlGetData(this.pool);
Future <List<String>> run(String sQuery) {
rows = new List<String>();
return readData(sQuery).then((_) {
return rows;
});
}
Future readData(String sQuery) {
return pool.query(sQuery).then((result) {
return result.forEach((row) {
String s = JSON.encode(row);
rows.add(s);
});
});
}
}
which I call like this:
var sql = new SqlGetData(pool);
sql.run('select firstName, lastName from person where id =' + s1).then((rows) {
some code here to process the data
});
If the database is not running I get an error on the return pool.query in readData, which I want to catch and pass to the client in some error message.
How and where can I code the try ... catch ... to prevent the server from dying? My problem is that I have to return futures, which is still difficult for me to grasp.
Take a look at this article Futures and Error Handling (if you haven't already).
There are two places:
.then((_) => doSomething(),
onError: (e) => doErrorHandling()).catchError((e) => doErrorHandling());
Guenter's answer is good. Here are a couple of extra tips.
It's more common to use .catchError() than the named parameter, if in doubt just use .catchError().
One problem with async code is if you forget to add a catchError handler anywhere in your codebase, and an error is triggered, it will bring your whole server down. Not good. However You can use Zones to handle uncaught errors in your code, and prevent this from happening.
There isn't much documentation about Zones at the time of writing, as it is a new feature. Florian Loitsch is working on an article which will appear here sometime soon. Here is an example of using runZoned():
runZoned(() {
var pool = new Pool.connect(...); // Don't know pool API, just making this up.
pool.query(sql).then((result) {
print(result);
});
// Oops developer forgot to add a .catchError() handler for the query.
// .catchError((e) => print('Query error: $e);
}, onError: (e) => print("Uncaught error: $e"));
This code will run without bringing down your server, despite the missing catchError() handler. Note, you will need to start the pool/connection within the same zone as the query is executed within.
I've added a quickfix option to my DSL in which I want to make some modifications to the document text - including renaming some element. I can change text in that element just fine, but I want to also rename all of its references - i.e. rename refactoring. How do I do that?
Can I somehow trigger the built-in rename refactoring from inside a quickfix? Or alternatively, how do I go over all of the element's references and change them?
So, I found a way to programmatically trigger a rename refactor. I don't know if it's the "proper" way - I guess it isn't, since I had to add #SuppressWarnings("restriction") to my code - but it works:
private void performDirectRenameRefactoring(EObject object, String newName) throws InterruptedException {
XtextEditor editor = EditorUtils.getActiveXtextEditor();
IRenameElementContext renameContext = new IRenameElementContext.Impl(
EcoreUtil.getURI(object),
object.eClass(),
editor,
editor.getSelectionProvider().getSelection(),
null);
IRenameSupport rename = renameSupportFactory.create(renameContext, newName);
rename.startDirectRefactoring();
}
So to call this from a quick fix, all you need to do is to get the EObject and calculate the new name. If the issue occupies a part of the EObject itself, the object could be retrieved by:
private EObject findObject(IXtextDocument doc, final Issue issue) {
EObject object = doc.readOnly(new IUnitOfWork<EObject, XtextResource>() {
public EObject exec(XtextResource state) throws Exception {
return state.getEObject(issue.getUriToProblem().fragment());
}
});
}
You can get an IXtextDocument from either IssueResolutionAcceptor (which you should have if you're handling an issue) or from IModificationContext (which you should have if you're proposing a change).
Oak, thank you very much for the solution. Here is my version in Xtend.
#Inject(optional=true)
IRenameSupport.Factory renameSupportFactory;
#Inject(optional=true)
IRenameContextFactory renameContextFactory;
#Fix(VhdlValidator::INVALID_SIGNAL_NAME_ENDING)
def addSignalEnding(Issue issue, IssueResolutionAcceptor acceptor) {
acceptor.accept(issue, 'Add the "_s" ending', 'Add the "_s" ending.', 'upcase.png') [
EObject element, IModificationContext context |
val editor = EditorUtils.getActiveXtextEditor();
val renameElementContext = editor.getDocument().readOnly(
new IUnitOfWork<IRenameElementContext, XtextResource>()
{
override def IRenameElementContext exec(XtextResource state)
{
renameContextFactory.createRenameElementContext(element,
editor, null, state);
}
});
val rename = renameSupportFactory.create(renameElementContext, (element as Signal).name + "_s" );
rename.startDirectRefactoring();
]
}