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.
Related
There is a StreamBuilder (using RxDart) which displays some date. After click on InkWell widget I need to calculate a new date on basis of old one. The code below simply explains the algo but when I run it there is nothing happens and execution stops after underlined row, i.e. I never see the value of lastCalcDate.
GUI:
child: StreamBuilder(
stream: bloc.getDate,
builder: (context,snapshot) {
return InkWell(
onTap: () => tapHandler
);
}),
void tapHandler() async {
var lastCalcDate = await bloc.getDate.single;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
print(lastCalcDate);
var newCalcDate = lastCalcDate.add(Duration(days:1));
bloc.setDate(newCalcDate)
}
BLoC:
class Bloc {
// BehaviourSubject is usedto be sure that last sent date will be accessible in `tapHandler`.
final _dateSubject = BehaviourSubject<DateTime>();
Observable<DateTime> get getDate => _dateSubject.stream;
Function(DateTime) get setDate => _dateSubject.add;
}
To implement what I need I created some workaround but I don't like it because I fill that I can do the same thing using observables.
BLoC (workaround):
class Bloc {
final _dateSubject = BehaviourSubject<DateTime>();
Observable<DateTime> get getDate => _dateSubject.stream;
DateTime _date;
void setDateWorkaround(DateTime date) {
_date = date;
_dateSubject.add(date);
}
}
Could you someone to give me advise. What I did wrong?
single will not work because it is going to return the next item in the stream, however, that has to be added first. This means that single will just wait for the next item and in your case it will not happen.
Since you are using rxdart and BehaviorSubject already, you can easily access the current element like this:
class Bloc {
final _dateSubject = BehaviourSubject<DateTime>();
Observable<DateTime> get getDate => _dateSubject.stream;
Function(DateTime) get setDate => _dateSubject.add;
DateTime get currentDate => _dateSubject.value;
}
In this case, I am making use of BehaviorSubject.value, which is actually the whole point of that class.
Now, you can just use currentDate in your tap handler:
void tapHandler() async {
var lastCalcDate = bloc.currentDate;
print(lastCalcDate);
var newCalcDate = lastCalcDate.add(Duration(days:1));
bloc.setDate(newCalcDate)
}
Use StreamProvider from provider
Listen to a Stream and expose the latest value emitted.
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 am trying to get a value from firestore and save it to a variable in flutter. I tried using StreamBuilder, but since i am not building a widget it does not work.
To clarify my problem, I am trying to get a url from a firestore document and then open it when i press a button in the app.
I also tried to adapt code i found in another question, but that returns null.
Future _getUrl() async{
DocumentReference docRef = Firestore.instance.collection('information').document('pdf');
var data;
docRef.get().then((datasnapshot){
data = datasnapshot.data['url'];
});
return data;
}
The collection is called information, the document pdf, and the field url
This method will return null because you are not waiting for the get() future to return before you return data. docRef.get() is a Future, so it will execute asychronously. Meanwhile, your program will move on to the next line, which is return data.
Something like this would do what you want I think:
Future _getUrl() async{
DocumentReference docRef = Firestore.instance.collection('information').document('pdf');
return docRef.get().then((datasnapshot){
return datasnapshot.data['url'];
});
}
Since _getUrl is already marked as async you can also use await in its body to return the right value:
Future _getUrl() async {
DocumentReference docRef = Firestore.instance.collection('information').document('pdf');
await datasnapshot = docRef.get();
let data = datasnapshot.data['url'];
return data;
}
I have encountered an issue while experimenting with flutter.
I have an AppBar with some Actions.
One of these actions is a calendar widget. My desired behavior will be by the new date selection the data on my Scaffold to be changed accordingly.
The issue is that, although I have managed to accomplished this behavior, the call to my API performed twice. I have identify that the issue was the RefreshIndicator that I had put in place (in order for the user to pull to refresh the page on demand), but I do not understand why...
For some reason when I change the date and consequently the data changed, it identify this as refresh state and then executes _handleRefresh(). The problem is, I still want to have the pull-down-to-refresh behavior.
Files on (tabView.dart file)
Scaffold's widget tree
RefreshIndicator(
key: _modelRefreshKey,
child: ListView.builder(
itemCount: this._fetchedData?.length,
itemBuilder: (BuildContext context, int index) {
if (this._fetchedData!= null) {
final MyModel myModel = this._fetchedData[index];
return (index == 0)
? ResultsLayout(
model: myModel ,
lastUpdateTxt: myModel.someTXT,
)
: MyModelInheritedWidget(
model: myModel,
child: ModelCardLayout(),
);
} else {
return EmptyWidget();
}
},
),
onRefresh: _handleRefresh,
),
Handle on refresh function
Future<Null> _handleRefresh() async {
Completer<Null> completer = new Completer<Null>();
this.getData().then((_) {
completer.complete();
});
return completer.future;
}
On select new date this function executes which refresh call again the data (hometab.dart file)
if (picked != null && picked != _selectedDate) {
_selectedDate = picked;
modelRefreshKey.currentState.widget.selectedDate = picked;
modelRefreshKey.currentState?.getData();
}
It is worth to point out the date method is located at where I create the tabs and the actual data to refresh is a part of a tab. I mention this in case it is some how related to my issue.
Any insights will be really helpful.
Thanks in advance.
I would have a variable like the following to see if the app is waiting for an API response:
_isWaitingForResponse = false;
Future getData() {
if(_isWaitingForResponse) return;
_isWaitingForResponse = true;
//change _isWaitingForResponse on api's response
}
I'm using the Lawndart library to access browser data, and want to collect the results of a set of queries. Here's what I thought should work:
numberOfRecordsPerSection(callback) {
var map = new Map();
db_sections.keys().forEach((_key) {
db_sections.getByKey(_key).then((Map _section) {
int count = _section.length;
map[_key] = count;
});
}).then(callback(map));
}
However, when the callback is called, map is still empty (it gets populated correctly, but later, after all the Futures have completed). I assume the problem is that the Futures created by the getByKey() calls are not "captured by" the Futures created by the forEach() calls.
How can I correct my code to capture the result correctly?
the code from How do I do this jquery pattern in dart? looks very similar to yours
For each entry of _db.keys() a future is added to an array and then waited for all of them being finished by Future.wait()
Not sure if this code works (see comments on the answer on the linked question)
void fnA() {
fnB().then((_) {
// Here, all keys should have been loaded
});
}
Future fnB() {
return _db.open().then((_) {
List<Future> futures = [];
return _db.keys().forEach((String key_name) {
futures.add(_db.getByKey(key_name).then((String data) {
// do something with data
return data;
}));
}).then((_) => Future.wait(futures));
});
}