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.
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 suspect that my understanding of Streams in Dart might have a few holes in it...
I have a situation in which I'd like a Dart app to respond to intermittent input (which immediately suggests the use of Streamss -- or Futures, maybe). I can implement the behavior I want with listener functions but I was wondering how to do this in a better, more Dartesque way.
As a simple example, the following (working) program listens to keyboard input from the user and adds a div element to the document containing what has been typed since the previous space, whenever the space bar is hit.
import 'dart:html';
main() {
listenForSpaces(showInput);
}
void listenForSpaces(void Function(String) listener) {
var input = List<String>();
document.onKeyDown.listen((keyboardEvent) {
var key = keyboardEvent.key;
if (key == " ") {
listener(input.join());
input.clear();
} else {
input.add(key.length > 1 ? "[$key]" : key);
}
});
}
void showInput(String message) {
document.body.children.add(DivElement()..text = message);
}
What I'd like to be able to do is to create a new Stream from the Stream that I'm listening to (in the example above, to create a new Stream from onKeyDown). In other words, I might set the program above out as:
var myStream = ...
myStream.listen(showInput);
I suspect that there is a way to create a Stream and then, at different times and places, insert elements to it or call for it to emit a value: it feels as though I am missing something simple. In any case, any help or direction to documentation would be appreciated.
Creating a new stream from an existing stream is fairly easy with an async* function.
For a normal stream, I would just do:
Stream<String> listenForSpaces() async* {
var input = <String>[];
await for (var keyboardEvent in document.onKeyDown) {
var key = keyboardEvent.key;
if (key == " ") {
yield input.join();
input.clear();
} else {
input.add(key.length > 1 ? "[$key]" : key);
}
}
}
The async* function will propagate pauses through to the underlying stream, and it may potentially pause the source during the yield.
That may or may not be what you want, since pausing a DOM event stream can cause you to miss events. For a DOM stream, I'd probably prefer to go with the StreamController based solution above.
There are several methods and there is a whole package rxdart to allow all kinds of things.
Only the final consumer should use listen and only if you need to explicitly want to unsubscribe, otherwise use forEach
If you want to manipulate events like in your example, use map.
I wasn't originally planning to answer my own question but I have since found a very simple answer to this question in the dartlang creating streams article; in case it's helpful to others:
Specifically, if we'd like to create a stream that we can insert elements into at arbitrary times and places in the code, we can do so via the StreamController class. Instances of this class have an add method; we can simply use the instance's stream property as our stream.
As an example, the code in my question could be rewritten as:
import 'dart:html';
import 'dart:async';
main() async {
// The desired implementation stated in the question:
var myStream = listenForSpaces();
myStream.listen(showInput);
}
Stream<String> listenForSpaces() {
// Use the StreamController class.
var controller = StreamController<String>();
var input = List<String>();
document.onKeyDown.listen((keyboardEvent) {
var key = keyboardEvent.key;
if (key == " ") {
// Add items to the controller's stream.
controller.add(input.join());
input.clear();
} else {
input.add(key.length > 1 ? "[$key]" : key);
}
});
// Listen to the controller's stream.
return controller.stream;
}
void showInput(String message) {
document.body.children.add(DivElement()..text = message);
}
(As mentioned in the article, we need to be careful if we want to set up a stream from scratch like this because there is nothing to stop us from inserting items to streams that don't have associated, active subscribers; inserted items would in that case be buffered, which could result in a memory leak.)
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 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)
...