Flutter stream doesn't listen or detect change - dart

I have problem, i have 3 dart files,
home.dart contain button with onclick:
final cartEmiter = CartEmitter();
cartEmiter.emitCart("add_cart");
cart.dart contain:
class CartEmitter {
StreamController _controller = StreamController.broadcast();
void emitCart(action) {
_controller.add(action);
// print(action);
}
Stream get cartAction => _controller.stream;
}
and in main.dart I have this code to change the cart badge.
StreamSubscription _cartCountSubscribtion;
int _cartCount = 0;
#override
void initState() {
_cartCountSubscribtion = CartEmitter().cartAction.listen((action) {
print(action);
setState(() {
_cartCount++;
});
});
super.initState();
}
#override
void dispose() {
_cartCountSubscribtion.cancel();
super.dispose();
}
But it doesn't work, no error, no output printed.
Is my code wrong or how to listen to change?

You create a new CartEmitter in the initState function, and another one in the onclick code. Those two are not connected in any way, so the event you emit with the emitCart call is emitted on a different CartEmitter than the one you listen to.
You need to share the same CartEmitter instance between the initState and onclick code.
Alternatively, if you know that you will only ever need one CartEmitter, you can make the _controller static, so the same controller (and stream) is shared between all instances of CartEmitter.
In that case, you can make emitCart and cartAction static too, and never create any CartEmitter instance at all.

Related

flutter bloc pattern navigation back results in bad state

I have a problem/question regarding the bloc plattern with flutter.
Currently, i am starting my app like this
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return BlocProvider(
bloc: MyBloc(),
child: MaterialApp(
title: "MyApp",
home: MyHomePage(),
routes: {
'/homePage': (context) => MyHomePage(),
'/otherPage': (context) => OtherPage(),
'/otherPage2': (context) => OtherPage2(),
...
},
));
So that i can retrieve/access myBloc like
myBloc = BlocProvider.of(context) as MyBloc;
and the data represented by the state like
BlocBuilder<MyBlocEvent, MyObject>(
bloc: myBloc,
builder: (BuildContext context, MyObject myObject) {
....
var t = myObject.data;
....
myBloc.onFirstEvent();
...
};
wherever i need it.
MyBloc is implemented like this:
abstract clas MyBlocEvent {}
class FirstEvent extends MyBlocEvent {}
class SecondEvent extends MyBlocEvent {}
class MyBloc extends Bloc<MyBlocEvent , MyObject>
void onFirstEvent()
{
dispatch(FirstEvent());
}
void onSecondEvent()
{
dispatch(SecondEvent());
}
#override
Stream<MyObject> mapEventToState( MyObject state, MyBlocEvent event) async* {
if (event is FirstEvent) {
state.data = "test1";
}
else if (event is SecondEvent) {
state.otherData = 5;
}
yield state;
}
The problem i now have, is that as soon as i change on of the state values and call
Navigator.pop(context)
to go back in the current stack, i can't change anything is the state anymore because the underlying stream seems to be closed. It fails with the message:
Another exception was thrown: Bad state: Cannot add new events after calling close"
Now this only happens after i call pop. If i only push new screens i can happily change the state data without any problems.
Am i doing something wrong regarding the Navigation here or is there something else i didn't catch regarding flutter or the bloc pattern itself?
Bad state: Cannot add new events after calling close
This error means that you are calling add on a StreamController after having called close:
var controller = StreamController<int>();
controller.close();
controller.add(42); // Bad state: Cannot add new events after calling close
It is likely related to you calling close inside the dispose method the "wrong" widget.
A good rule of thumb is to never dispose/close an object outside of the widget that created it. This ensure that you cannot use an object already disposed of.
Hope this helps in your debugging.
The navigation of the app depends on your widget designs.
I use stateless widgets and render the view using bloc's data.
Whenever i navigate to another page, i would pop the current widget and navigate to the next widget.
The next stateless widget declare the bloc,
then in your subsequent stateless widgets should contain calls like MyBloc.dispatch(event(param1: value1, param2: value2));
In MyBloc, you need to set the factory of your state that contains final values;
#override
Stream<MyObject> mapEventToState( MyObject state, MyBlocEvent event) async* {
if (event is FirstEvent) {
// set it in the state, so this code is omitted
// state.data = "test1";
// add this
yield state.sampleState([], "test1");
}
else if (event is SecondEvent) {
// state.otherData = 5;
yield state.sampleState([], 5);
} else {
yield state.sampleState([], null);
}
The MyObjectState needs to be setup like this,
class MyObjectState {
final List<Bar> bars;
final String Foo;
const MyObjectState(
{this.bars,
this.foo,
});
factory MyObjectState.sampleState(List<Bar> barList, String value1) {
return MyObjectState(bars: barList, foo: message);
}
}
So that the stateless widget can use the bloc like this
MyBloc.currentState.sampleState.foo
You can try run Felix Angelov's flutter project.
Login Flow Example

Connecting a sink of BLoC with another BLoC

I am using the BLoC pattern as described at the Google IO talk.
I have a simple BLoC which is used to display a alert in the UI whenever a string is added to messageSink:
class AlertBloc {
final _message = BehaviorSubject<String>();
AlertBloc() {}
Stream<String> get message => _message.stream;
Sink<String> get messageSink => _message.sink;
void dispose() {
_message.close(); }
}
Elsewhere in the app, I have another BLoC which needs to add a string to messageSink, when a certain condition is met.
I noticed it is not a good idea to provide the whole BLoC from the Google I/O repo for the talk, and they provide advice for connecting a stream from a BLoC to another BLoC sink:
Note that we are not providing [CartBloc] to the
[ProductSquareBloc] directly, although it would be easier to
implement. BLoCs should not depend on other BLoCs (separation of
concerns). They can only communicate with each other using
streams. In this case, the [CartBloc.items] output plugs into the
[ProductSquareBloc.cartItems] input.
My question is how to connect a sink from a BLoC to another BLoC stream?
Here is a simple example for you. Imagine the following two BLoCs:
The first one exposes a Stream and populates it with some values:
class ProducerBLoC {
//Controller is private - you do not want to expose it
final StreamController<int> _productionController = StreamController<int>();
//Instead, you expose a stream
Stream<int> get production => _productionController.stream;
//This method generates some values and puts them to stream
void produceValue() {
_productionController.sink.add(1);
_productionController.sink.add(2);
_productionController.sink.add(3);
}
//Don't forget to close your controllers
void dispose() {
_productionController.close();
}
}
The other one exposes a Sink and processes values that are put into it.
class ConsumerBLoC {
//Controller is private - you do not want to expose it
final StreamController<int> _consumptionController = StreamController<int>();
//Instead, you expose a sink
StreamSink<int> get consumption => _consumptionController.sink;
//In class constructor we start listening to the stream of values
ConsumerBLoC() {
_consumptionController.listen((value) {_consumeValue(value);} );
//or simply: _consumptionController.listen(_consumeValue); //theese are the same
}
//This method generates some values and puts them to stream
void consumeValue(int value) {
//Do something with the value
print('Value processed: $value');
}
//Don't forget to close your controllers
void dispose() {
_consumptionController.close();
}
}
Now, the task is to connect production stream to consumption sink. As you have correctly noticed, you do not want for any of two BLoCs to know anything about existence of the other one. So none of the two should hold references to the other one or even create instances of another one. Instead, you connect them using your Widget class:
//Define some widget to represent main screen of your application
class MainScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MainScreenState();
}
//And define a state for this widget (state does not need to be public)
class _MainScreenState extends State<MainScreen> {
//You define both blocks here
ProducerBLoC _producer = new ProducerBLoC();
ConsumerBLoC _consumer = new ConsumerBLoC();
//Now, either do it in _MainScreenState constructor, or in the initState() method
#override
void initState() {
super.initState();
//Connect production stream with consumption sink
_producer.production.listen((value) => _consumer.consumption.add(value));
//Or, beautifully: _producer.production.pipe(_consumer.consumption);
}
#override
Widget build(BuildContext context) {
//The exact implementation does not matter in current context
}
//And don't forget to close your controllers
#override
dispose() {
super.dispose();
_producer.dispose();
_consumer.dispose();
}
}
This way, any value generated by ProducerBLoC will immediately be consumed by ConsumerBLoC. And, what's the most important, - both BLoCs are completely independent from one another!
The exact same way as you'd do with streams: Passing it as parameter
class Bloc {
final Sink<int> _external;
Bloc(this._external);
}

How to add event listeners in Flutter?

I'm pretty new to Flutter and experimenting with the SDK. I'm working on a simple app that has a countdown in the background and want to trigger an event at certain intervals. For example, when clock reaches one minute remaining, send a push notification. In general, I'm trying to get a feel for how to monitor certain activities such as time and usage of the app and once certain conditions are met, trigger other things. Is it as simple as an if-else statement placed in the right place?
What kind of thing am I looking for to implement this?
Thanks in advance.
I prefer to use streams for such tasks
Stream<int> timer = Stream.periodic(Duration(seconds: 1), (int count) => count);
...
_MyTextWidget(timer)
and my widget
class _MyTextWidget extends StatefulWidget {
_MyTextWidget(this.stream);
final Stream<int> stream;
#override
State<StatefulWidget> createState() => _MyTextWidgetState();
}
class _MyTextWidgetState extends State<_MyTextWidget> {
int secondsToDisplay = 0;
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: widget.stream,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return snapshot.hasData ? Text(snapshot.data.toString()) : Text('nodata');
});
}
}
The following is a better example of an event listener based on Streams that I have found to work.
In the widget you want to listen to..
class CmVideoPlayer extends StatefulWidget {
String titlePlaying;
StreamController changeController = StreamController<VideoPlayerEvent>();
CmVideoPlayer({Key key, #required this.titlePlaying})
: assert(titlePlaying != null), super(key: key);
#override
_CmVideoPlayerState createState() => _CmVideoPlayerState();
}
See the line "StreamController changeController = StreamController();" that uses a small class VideoPlayerEvent to carry the message.
class VideoPlayerEvent {
var eventType;
var eventMessage;
VideoPlayerEvent(this.eventType, this.eventMessage);
}
Then in the STATEFULLWIDGET...
Refer the the stream as
class _CmVideoPlayerState extends State<CmVideoPlayer> {
void Member() {
widget.changeController.add(new VideoPlayerEvent('state', 'finished'));
}
}
As it is inside the _CmVideoPlayerState class, and using the ability to reach into the parent class via the widget variable.
Then in the area of the code using the widget, and to listen for the messages..
To listen for the messages
CmVideoPlayer myPlayer = CmVideoPlayer();
myPlayer.changeController.stream.listen((e) {
print('Reciever event from CmVideoPlayer: ' + e.eventMessage.toString());
}
That should do it.
HOWEVER, this only allows ONE listener at a time. After I got this going, I moved on. But plan to implement a multi listener down the track.
Maybe some one can expand on this. I am keeping it as simple as possible. If some one has a multi listener example. Please post here.
You can use ValueNotifier for this.
When value is replaced with something that is not equal to the old value as evaluated by the equality operator ==, this class notifies its listeners.
Helpful Medium link

A build function returned null The offending widget is: StreamBuilder<Response>

I'm new to Flutter and I'm trying to accomplish a simple thing:
I want to create a signup functionality using BLoC pattern and streams.
For the UI part I have a stepper, that on the very last step should fire a request to the server with the collected data.
I believe I have everything working until the StreamBuilder part. StreamBuilders are meant to return Widgets, however, in my case I don't need any widgets returned, if it's a success I want to navigate to the next screen, otherwise an error will be displayed in ModalBottomSheet.
StreamBuilder is complaining that no widget is returned.
Is there anything else that could be used on the View side to act on the events from the stream?
Or is there a better approach to the problem?
If you don't need to render anything, don't use StreamBuilder to begin with.
StreamBuilder is a helper widget used to display the content of a Stream.
What you want is different. Therefore you can simply listen to the Stream manually.
The following will do:
class Foo<T> extends StatefulWidget {
Stream<T> stream;
Foo({this.stream});
#override
_FooState createState() => _FooState<T>();
}
class _FooState<T> extends State<Foo<T>> {
StreamSubscription streamSubscription;
#override
void initState() {
streamSubscription = widget.stream.listen(onNewValue);
super.initState();
}
void onNewValue(T event) {
Navigator.of(context).pushNamed("my/new/route");
}
#override
void dispose() {
streamSubscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}

Is there a way to load async data on InitState method?

I'm a looking for a way to load async data on InitState method, I need some data before build method runs. I'm using a GoogleAuth code, and I need to execute build method 'till a Stream runs.
My initState method is:
#override
void initState () {
super.initState();
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) {
setState(() {
_currentUser = account;
});
});
_googleSignIn.signInSilently();
}
I will appreciate any feedback.
You can create an async method and call it inside your initState
#override
void initState () {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
_asyncMethod();
});
}
_asyncMethod() async {
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) {
setState(() {
_currentUser = account;
});
});
_googleSignIn.signInSilently();
}
As of now using .then notation seems to work:
// ...
#override
initState() {
super.initState();
myAsyncFunction
// as suggested in the comment
// .whenComplete() {
// or
.then((result) {
print("result: $result");
setState(() {});
});
}
//...
Method 1 : You can use StreamBuilder to do this. This will run the builder method whenever the data in stream changes.
Below is a code snippet from one of my sample projects:
StreamBuilder<List<Content>> _getContentsList(BuildContext context) {
final BlocProvider blocProvider = BlocProvider.of(context);
int page = 1;
return StreamBuilder<List<Content>>(
stream: blocProvider.contentBloc.contents,
initialData: [],
builder: (context, snapshot) {
if (snapshot.data.isNotEmpty) {
return ListView.builder(itemBuilder: (context, index) {
if (index < snapshot.data.length) {
return ContentBox(content: snapshot.data.elementAt(index));
} else if (index / 5 == page) {
page++;
blocProvider.contentBloc.index.add(index);
}
});
} else {
return Center(
child: CircularProgressIndicator(),
);
}
});
}
In the above code StreamBuilder listens for any change in contents, initially its an empty array and shows the CircularProgressIndicator. Once I make API call the data fetched is added to contents array, which will run the builder method.
When the user scrolls down, more content is fetched and added to contents array which will again run builder method.
In your case only initial loading will be required. But this provides you an option to display something else on the screen till the data is fetched.
Hope this is helpful.
EDIT:
In your case I am guessing it will look something like shown below:
StreamBuilder<List<Content>>(
stream: account, // stream data to listen for change
builder: (context, snapshot) {
if(account != null) {
return _googleSignIn.signInSilently();
} else {
// show loader or animation
}
});
Method 2: Another method would be to create an async method and call it from you initState() method like shown below:
#override
void initState() {
super.initState();
asyncMethod();
}
void asyncMethod() async {
await asyncCall1();
await asyncCall2();
// ....
}
Create anonymous function inside initState like this:
#override
void initState() {
super.initState();
// Create anonymous function:
() async {
await _performYourTask();
setState(() {
// Update your UI with the desired changes.
});
} ();
}
#override
void initState() {
super.initState();
asyncInitState(); // async is not allowed on initState() directly
}
void asyncInitState() async {
await yourAsyncCalls();
}
Previous Answer!!
You can set a Boolean value like loaded and set it to true in your listen function and make your build function return your data when loaded is set to true otherwise just throw a CircularProgressIndicator
Edited --
I would not suggest calling setState in a method you call in initState. If the widget is not mounted while the setState is called (as the async operation completes) an error will be reported. I suggest you use a package after_layout
Take a look at this answer for better understanding setState in initState : https://stackoverflow.com/a/53373017/9206337
This post will give you an idea to know when the app finishes the build method. So that you can wait for your async method to setState after widget is mounted : https://stackoverflow.com/a/51273797/9206337
You can create an async method and call it inside your initState
#override
void initState() {
super.initState();
asyncMethod(); ///initiate your method here
}
Future<void> asyncMethod async{
await ///write your method body here
}
Per documentation at https://pub.dev/packages/provider
initState() {
super.initState();
Future.microtask(() =>
context.read<MyNotifier>(context).fetchSomething(someValue);
);
}
Sample code:
#override
void initState() {
super.initState();
asyncOperation().then((val) {
setState(() {});
print("success");
}).catchError((error, stackTrace) {
print("outer: $error");
});
//or
asyncOperation().whenComplete(() {
setState(() {});
print("success");
}).catchError((error, stackTrace) {
print("outer: $error");
});
}
Future<void> asyncOperation() async {
await ... ;
}
As loading or waiting for initial state is a (generally) aone off event FutureBuilder would seem to be a good option as it blocks once on an async method; where the async method could be the loading of json config, login etc. There is an post on it [here] in stack.(Flutter StreamBuilder vs FutureBuilder)
How about this?
#override
void initState() {
//you are not allowed to add async modifier to initState
Future.delayed(Duration.zero,() async {
//your async 'await' codes goes here
});
super.initState();
}
initState() and build cannot be async; but in these, you can call a function that is async without waiting for that function.
#override
void initState() {
super.initState();
_userStorage.getCurrentUser().then((user) {
setState(() {
if (user.isAuthenticated) {
Timer.run(() {
redirectTo();
});
}
});
});
}
void redirectTo() {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) => new ShopOrders()));
}
I would strongly suggest using a FutureBuilder. It takes care of executing the async function and building the widget according to the result!
Here's a link to a short intro video and the documentation.
Code Example:
Future<void> initControllers() async {
for (var filePath in widget.videoFilePaths) {
var val = VideoPlayerController.file(File(filePath));
await val.initialize();
controllers.add(val);
}
}
#override
Widget build(BuildContext context) {
FutureBuilder(
future: initControllers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return YourWidget();
} else {
return const ProgressIndicator();
}
},
));}
Tried all suggestions, none would keep my build from starting after the async method that I need in initState() finish, except one: the trick of having a a bool variable in the State class (let's call it _isDataLoaded) that is initialized to false upon definition, set to true inside a setState() that is invoked when the async function finishes inside initState(). In the build, condition a CircleProcessIndicator() or your Widget depending on the value of this variable.
I know it's dirty because it could break the build, but honestly nothing else that would make more sense -such as running super.initState() upon completion of the async function- has worked for me.
I came here because I needed to fetch some files from FTP on program start. My project is a flutter desktop application. The main thread download the last file added to the FTP server, decrypts it and displays the encrypted content, this method is called from initState(). I wanted to have all the other files downloaded in background after the GUI shows up.
None of the above mentioned methods worked. Constructing an Isolate is relatively complex.
The easy way was to use the "compute" method:
move the method downloading all files from the FTP out of the class.
make it an int function with an int parameter (I do not use the int parameter or the result)
call it from the initState() method
In that way, the GUI shows and the program downloads the files in background.
void initState() {
super.initState();
_retrieveFileList(); // this gets the first file and displays it
compute(_backgroundDownloader, 0); // this gets all the other files so that they are available in the local directory
}
int _backgroundDownloader(int value) {
var i = 0;
new Directory('data').createSync();
FTPClient ftpClient = FTPClient('www.guckguck.de',
user: 'maxmusterman', pass: 'maxmusterpasswort');
try {
ftpClient.connect();
var directoryContent = ftpClient.listDirectoryContent();
// .. here, fileNames list is reconstructed from the directoryContent
for (i = 0; i < fileNames.length; i++) {
var dirName = "";
if (Platform.isLinux)
dirName = 'data/';
else
dirName = r'data\';
var filePath = dirName + fileNames[i];
var myDataFile = new File(filePath);
if (!myDataFile.existsSync())
ftpClient.downloadFile(fileNames[i], File(filePath));
}
} catch (err) {
throw (err);
} finally {
ftpClient.disconnect();
}
return i;
I have used timer in initState
Timer timer;
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 1), (Timer timer) async {
await this.getUserVerificationInfo();
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
getUserVerificationInfo() async {
await someAsyncFunc();
timer.cancle();
}

Resources