StreamBuilder and AsyncSnapshot + rxdart flutter question - flutter

I have final _fetcher = PublishSubject<MyModel>() ; in my bloc Component. Here is structure of MyModel:
MyModel { List<MyObjects> _objects = [];
List<MyObjects> get allObjects => _objects; }
also there is
Observable<MyModel> get myObjects => _fetcher.stream;
in bloc.
I have two pages, first displays list of MyObjects inside Listview.builder, and second displays selected MyObject data.
I'm trying to get data from myObjects using StreamBuilder.
In the first page all objects displays perfectly. But when I open a page with selected object, my AsyncSnapshot inside StreamBuilder always has connections.state waiting, although I have data in stream.
What am I doing wrong?

Having data doesn't mean you always have access to it.
By default streams (and subjects) don't store the data they received earlier. So if you're coming late to the party, then sorry for you but no data.
To solve this problem rxdart introduces a ReplaySubject and BehaviorSubject. Both are used so that late listeners can still grab the last few events. ReplaySubject will keep track of the N latest, while BehaviorSubject will keep only the last one.
Using a BehaviorSubject instead of PublishSubject should do the trick

Related

Navigating to another screen gets slower and slower each time i repeat click, go back, click, go back

I have a Navigator.push and MaterialPageRoute() to navigate to another screen. But navigation to other screens gets slower and slower becuase in my initState() i have a method which initializes the json data which i show each time i navigate to another screen. The json data is big and i only use one file with big json data which has objects and each object is shown i different screens. In my usecase i have to use one file with big json data.
In my initState() i initialize the data i grabbed with the method setTestData() and inside this method i set the data inside an object:
late Map<String, dynamic> grabbedData = {};
setTestData() async {
await TestData()
.getTestData()
.then((result) => setState(() => grabbedData = result));
}
#override
initState() {
setTestData();
super.initState();
}
In my view i can for example navigate to another screen and then show different objects inside the same object json i grabbed in setTestData(). I only use one view called AppView() to show different screen so when i navigate for example from screen A to B, both A and B screen are shown with AppView() widget. This is necessary for my use case which is irrelevant for this question.
This is the navigation which i use to load another screen and which technacly runs initState() from AppView() again because the previous route is also AppView():
Navigator.push(
context,
MaterialPageRoute(
maintainState: false,
builder: (_) => AppView(
selectedMenuItems:
grabbedData['data']
['views'][
widget.selectedMenuItems[
index]
[
'content']],
)));
But the problem is that each time i navigate to AppView() and click back on my phone to show previous data from AppView() and navigate again, it re-initializes the state and so the proces is slowed after i repeat it a couple of times. How do i solve this problem?
Better to use Provider package for this task. Provider does not rebuild the widget rather it only updates the state of the widget so it is faster. Then, you can get your data or data stream only once at the beginning of your app or a particular screen. No need to generate data each time. Also, provider automatically disposes data when the screen is closed. It is recommended by flutter team as well and an awesome package. For more example, check some YouTube videos. For your particular problem, I think it is better to use provider.value. Check the references and hopefully later on you will understand what is a provider.value object.
If you are generating new data every time then you need to set your provider.value each time where you are using Navigator.push, otherwise if you do not use your provider at the beginning of your app at MaterialApp section then after the push the provider won't be available.
As an example, to add provider inside a push please follow the following code snippet:
Navigator.push(context,
MaterialPageRoute(builder: ((context) {
return StreamProvider<User>.value(
initialData: initialUserData,
value: userDataStream,
child: const UpdateUser(),
);
})));
Next, access the value in the UpdateUser page like this:
final user = Provider.of<User>(context);
If you are not using any data stream then just try the normal ChangeNotifierProvider and get the value from the Consumer. Try some youtube tutorials and you will love it.

What is a snapshot in Flutter?

I've been using a Firebase database in my project. I've been following a tutorial, and when returning widgets to the future builder it says to use:
if(snapshot.hasError) {
// Cannot connect to database
}
else {
// Return widgets as normal
}
I have checked the Flutter documentation and they say a snapshot is:
Immutable representation of the most recent interaction with an asynchronous computation.
But what does this mean, and why does the above code make sense?
Snapshot is the result of the Future or Stream you are listening to in your FutureBuilder.
Before interacting with the data being returned and using it in your builder, you have to access it first.
To access this data, which is technically being delivered indirectly to you, by your FutureBuilder, you need to ask FutureBuilder for it.
You do this by first, saying snapshot, because that is the nickname so to speak you told Flutter that you will be using, because your Future builder looks something like this:
FutureBuilder(
future: someFutureFunction(),
builder: (context, snapshot) { // Here you told Flutter to use the word "snapshot".
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());
else
return Text(counter.toString());
}),
If you refereed to it as "finno", you can later on access this information by typing finno.data.
snapshot has many properties you can make use of, like hasData and connectionStatus.
If your future was expected to return an object you created, for example
Student(String name, int age)
You can print the name by saying print(snapshot.data.name).
Caution: there are two common meanings for snapshot. One is the one you use with StreamBuilder or FutureBuilder in the build method. The other is the kind of data you get back from Firebase. Unfortunately, you often use a Firebase snapshot in a FutureBuilder or StreamBuilder, which also uses the term snapshot, and the snapshot from Firebase ends up in the snapshot.data value in the builder at the appropriate time. Ugh!
Snapshot is just the response you may have from Firebase. So here they are just trying to check if the response is empty.
To access data from the received response, you can just do:
final responseData = snapshot.data
In fact you can also change the snapshot to any desired name you want.

How can I listen to changes in my bloc stream

I am new to flutter and am trying to build an app using Bloc architecture.
I could manage to figure out a sink, inputting my event bloc.updateNavigation(_selectedItem.value);. I also figured out how to use that data by calling my bloc (is that what its called). I had a TextFormField and changed some settings like this if (bloc.navigationProvider.currentNavigation == 1), if (bloc.navigationProvider.currentNavigation == 2)
There is one thing I couldn't figure out how to do though. I would like to set _Controller.clear(); whenever the bloc changes. So that whenever a user makes a new selection and changes the "currentNavigation" the text controller clears. Is there a way I can find out just when a change is made, rather than having to call by the changes. I would like to do something like if (change is made to currentNavigation) {_Controller.clear();}
My bloc file looks like this:
class NavigationBloc {
final navigationController = StreamController();
NavigationProvider navigationProvider = new NavigationProvider();
Stream get getNavigation => navigationController.stream;
void updateNavigation(int navigation) {
navigationProvider.updateNavigation(navigation);
navigationController.sink.add(navigationProvider.currentNavigation);
}
void dispose() {
navigationController.close();
}
}
final bloc = NavigationBloc();
Streams have a .listen(..) method which gets called everytime data is passed into the stream.
For better usage, use the flutter_bloc package. It provides widgets to provide a bloc to all descendants (BlocProvider) and widgets to listen to state-changes (BlocListener). There you don't have to mess with streams at all and there are a few plugins that help creating the boilerplate code.

What is the difference between Stream transform and listen in flutter? When to implement them?

I am learning BLoC pattern in Flutter without using any package. What I know is data are sent as Sink and output is given as Stream in BLoC pattern. StreamController is there to handle these thing. Looks like both method make changes to the input data but I am confused about their purpose. Went through the documentation but couldn't understand.
Class CartBloc{
final _cart = Cart();
Sink<Product> get addition => _additionalController.sink;
final _additionController = StreamController<Product>();
Stream<int> get itemCount => _itemCountSubject.stream;
final _itemCountSubject = BehaviorSubject<int>();
CartBloc(){
_additionaController.stream.listen(_handle);
}
void _handle(Product product){
_cart.add(product);
_itemCountSubject.add(_cart.itemCount);
}
}
Above code is from Build reactive mobile apps with Flutter (Google I/O '18). They mentioned listen is necessary because we are not just capturing data but doing some other operation through the _handle() method. Now my question is can't we implement same with stream.transform(). If yes what will be the equivalent implementation and if no what is the reason.
stream.transform(transformer) will create a new stream that is somehow modified from the original. It does not start listening on the stream, and so no values will flow through the stream until it has a listener.
Think of transform as a way to change a stream, usually for some other bit of code to listen to. Think of listen as a way to react to values that come through a stream.

Why is the Streamlistener reacting differently?

I'm trying to use the Streambuilder in cooperation with Bloc. The problem is that somehow the UI updates only when the expensive Funktions are finished. It seems that then, and only then updates are made to the stream. But I can not figure out why?
I Implemented a simple Bloc that has 4 events:
1. a Future called over the Bloc Sate Object
2. a Future called inside the Bloc
3. a Funktion called inside the Bloc
4 just using Future.delay
I'm Trying to understand why everything behaves as expectetd, but not when I use first two. My guess is I have a basic misunderstanding of the eventloop but I can not see why there should be a difference in behavior between 2 and 4 or 1 and 4
To make it easy I made an example Project on Github
https://github.com/pekretsc/bloceventloop.git
So I have my refresh methode that ads the new state to the Stream.
if (event is ExpensiveEventInState) {
refresh(BlocUIState.Waiting);
String result = await blocState.doSomeThing();
if (result == '') {
refresh(BlocUIState.Fin);
} else {
refresh(BlocUIState.Fail);
}
}
if (event is ExpensiveEventWhyDoesThisWork) {
refresh(BlocUIState.Waiting);
await Future.delayed(Duration(seconds: 3));
refresh(BlocUIState.Fin);
}
so the question is, should the first and second event not behave the same way?
What happens though is that in the first case the refresh is ignored completely and just the Fin is added to the stream. (I checked that, its not that it is too fast to recognize)
StreamListener's callback is called immediately when an event is pushed on the stream.
On the other hand, StreamBuilder's builder callback isn't. For most common use-cases, it is called at most once per frame.