Unnecessary Widget Rebuilds While Using Selector (Provider) inside StreamBuilder - flutter

I am using a Selector which rebuilds when a data in Bloc changes. Which woks fine but when the data changes it reloads the whole tree not just the builder inside Selector.
In my case the selector is inside a StreamBuilder. I need this because the stream is connected to API. So inside the stream I am building some widget and One of them is Selector. Selector rebuilds widgets which is depended on the data from the Stream.
Here is My Code. I dont want the Stream to be called again and again. Also the Stream gets called because the build gets called every time selector widget rebuilds.
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/data_bloc.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MultiProvider(providers: [
ChangeNotifierProvider<DataBloc>(
create: (_) => DataBloc(),
)
], child: ProviderTest()),
);
}
}
class ProviderTest extends StatefulWidget {
#override
_ProviderTestState createState() => _ProviderTestState();
}
class _ProviderTestState extends State<ProviderTest> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text("Outside Stream Builder"),
StreamBuilder(
stream: Provider.of<DataBloc>(context).getString(),
builder: (_, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Column(
children: <Widget>[
Text("Widget Generated by Stream Data"),
Text("Data From Strem : " + snapshot.data),
RaisedButton(
child: Text("Reload Select"),
onPressed: () {
Provider.of<DataBloc>(context, listen: false).changeValue(5);
}),
Selector<DataBloc, int>(
selector: (_, val) =>
Provider.of<DataBloc>(context, listen: false).val,
builder: (_, val, __) {
return Container(
child: Text(val.toString()),
);
}),
],
);
}
return Container();
},
)
],
),
);
}
}
bloc.dart
import 'package:flutter/foundation.dart';
class DataBloc with ChangeNotifier {
int _willChange = 0;
int get val => _willChange;
void changeValue(int val){
_willChange++;
notifyListeners();
}
Stream<String> getString() {
print("Stream Called");
return Stream.fromIterable(["one", "two", "three"]);
}
}
Also if I remove the StreamBuilder then the Selector acts like its suppose to. Why does StreamBuilder Rebuilds in this case? Is there anyway to prevent this?

Based on the code that you've shared, you can create a listener to your Stream on your initState that updates a variable that keeps the most recent version of your data, and then use that variable to populate your widgets. This way the Stream will only be subscribed to the first time the Widget loads, and not on rebuilds. I can't test it directly as I don't have your project. But please try it out.
Code example based on your code
class ProviderTest extends StatefulWidget {
#override
_ProviderTestState createState() => _ProviderTestState();
}
class _ProviderTestState extends State<ProviderTest> {
String _snapshotData;
#override
void initState() {
listenToGetString();
super.initState();
}
void listenToGetString(){
Provider.of<DataBloc>(context).getString().listen((snapshot){
setState(() {
_snapshotData = snapshot.data;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text("Outside Stream Builder"),
Column(
children: <Widget>[
Text("Widget Generated by Stream Data"),
Text("Data From Strem : " + _snapshotData),
RaisedButton(
child: Text("Reload Select"),
onPressed: () {
Provider.of<DataBloc>(context, listen: false).changeValue(5);
}
),
Selector<DataBloc, int>(
selector: (_, val) =>
Provider.of<DataBloc>(context, listen: false).val,
builder: (_, val, __) {
return Container(
child: Text(val.toString()),
);
}
),
],
)
],
),
);
}
}

I found the problem after reading this blog post here. I lacked the knowlwdge on how the Provider lib works and how its doing all the magic stuff out of Inherited widgets
The point and quote that solves this problem is. ( A quation from the blog post above)
When a Widget registers itself as a dependency of the Provider’s
InheritedWidget, that widget will be rebuilt each time a variation in
the “provided data” occurs (more precisely when the notifyListeners()
is called or when a StreamProvider’s stream emits new data or when a
FutureProvider’s future completes).
That means the variable that i am changing and the Stream that i am listning to, exists in the Same Bloc! that was the mistake. So when I change the val and call notifyListener() in a single bloc, all things reloads which is the default behaviour.
All I had to do to solve this problem is to make another Bloc and Abstract the Stream to that particular bloc(I think its a Good Practice also). Now the notifyListener() has no effect on the Stream.
data_bloc.dart
class DataBloc with ChangeNotifier {
int _willChange = 0;
String data = "";
int get val => _willChange;
void changeValue(int val){
_willChange++;
notifyListeners();
}
Future<String> getData () async {
return "Data";
}
}
stream_bloc.dart
import 'package:flutter/foundation.dart';
class StreamBloc with ChangeNotifier {
Stream<String> getString() {
print("Stream Called");
return Stream.fromIterable(["one", "two", "three"]);
}
}
And the problem is solved. Now the Stream will only be called if its invoked but not when the variable changes in the data_bloc

Related

how to use riverpod, by combing futureprovider with stateprovider?

I am using futureprovider for getting response from api , but i want get the list from api and assign it to the stateprovider by using listprovider.state="data from api" , how to will it work ,
how to combine future provdier with the state provider .
UPDATED ANSWER
After a discussion with #31Carlton7, he's opened an issue on github, and after a discussion with Remi Rousselet (the creator of riverpod) we've reached a better solution for this problem.
(from the final solution on the issue)
Running the app:
main.dart
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const ProviderScope(
child: MaterialApp(
home: MyHomePage(),
),
);
}
}
Creating the foo class and provider:
foo.dart
part 'foo.g.dart';
class Foo {
final int bar;
int? baz;
Foo(
this.bar, {
this.baz,
});
}
#riverpod
class FooController extends _$FooController {
FooController(this.foo);
Foo foo;
#override
FutureOr<Foo> build() async {
foo = await getFoo();
return foo;
}
Future<Foo> getFoo() async {
await Future.delayed(const Duration(seconds: 1));
return Foo(1);
}
}
Implementation using Async capabilities:
home.dart
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Consumer(
builder: (context, ref, _) {
// Get the provider and watch it
final fooAsync = ref.watch(fooControllerProvider);
// Use .when to render UI from future
return fooAsync.when(
data: (foo) => Text('bar: ${foo.bar}, baz: ${foo.baz}'),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text(err.toString()),
);
},
),
);
}
}
Implementation using Notifier capabilities: home.dart
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Consumer(
builder: (context, ref, _) {
// Get Foo provider and set the state of it.
// Use it as if it were a State Provider.
ref.watch(fooControllerProvider.notifier).foo = Foo(3);
// Use Foo in UI (.requireValue is used to be able to listen to changes)
final foo = ref.watch(fooControllerProvider).requireValue;
// Use .when to render UI from future
return Text('bar: ${foo.bar}, baz: ${foo.baz}');
},
),
);
}
}
OLD ANSWER
This is a topic that I've been struggling with and thinking about a lot lately.
What I think is missing in Remi's answer, is the ability to convert the Future data to a maniputable data.
When you're recieving Future data using either a FutureProvider and implementing the ui using the when method OR using the FutureBuilder widget, they both will trigger a rebuild when the remote data is received, so if you try to assign the value to your StateProvider it will trigger a rebuild during another rebuild which will throw.
I currently have 2 workarounds for this, and I will be updating my answer as I get more info about this.
For this example, we'll have a future provider that will wait and then return a fake data:
final _futureCounterProv = FutureProvider(
(ref) async {
Future.delayed(
Duration(seconds: 3),
);
return Random().nextInt(100);
},
);
1. Future.microtask:
Future.microtask enables you to run an operation after the current rebuild ends.
You have to make sure that your StateProvider dependencies are in a Consumer below the Future.microtask call or the Future.microtask will be called on each state update, which will keep reseting the StateProvider's value to the future value
// this provider will provide the current value of the counter
final _counterProv = StateProvider((ref) => 0);
class Body extends ConsumerWidget {
const Body({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(_futureCounterProv).when(
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
error: (error, stackTrace) {
return Text(error.toString());
},
data: (data) {
Future.microtask(
() {
// Assigning the future value to the `StateProvider`
return ref.read(_counterProv.notifier).state = data;
},
);
return Consumer(
builder: (context, ref, _) {
final count = ref.watch(_counterProv);
return Column(
children: [
IconButton(
onPressed: () {
ref
.read(_counterProv.notifier)
.update((value) => value + 1);
},
icon: const Icon(Icons.add),
),
Text(
count.toString(),
),
],
);
},
);
},
);
}
}
2. ChangeNotifierProvider:
StateProvider has 2 options to update its value: the value setter and the update method, and they both trigger a rebuild. In this workaround we want to implement a state update that does not trigger rebuild. A way to do this is by using a ChangeNotifierProvider instead of StateProvider. By using a ChangeNotifierProvider we can control our own update actions and call notifyListeners (which will trigger a rebuild) whenever we want.
You have to make sure that your ChangeNotifierProvider dependencies are in a Consumer below the updateNoNotify call, or the ChangeNotifierProvider's will keep reseting to the future's value. Also you have to make sure that all the widgets that are consuming this ChangeNotifierProvider are in the widget tree below the updateNoNotify, or they will not be rebuilt as we're not triggering a rebuild
// the new `_counterProv`
final _counterProv = ChangeNotifierProvider(
(ref) => _CounterNotifier(),
);
class _CounterNotifier extends ChangeNotifier {
int _value = 0;
int get value => _value;
void update(int Function(int value) update) {
_value = update(_value);
// trigger a rebuild
notifyListeners();
}
void updateNoNotify(int Function(int value) update) {
_value = update(_value);
}
}
// the ui
class Body extends ConsumerWidget {
const Body({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(_futureCounterProv).when(
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
error: (error, stackTrace) {
return Text(error.toString());
},
data: (data) {
// calling `updateNoNotify` which does not trigger
// trigger rebuild as it does not call `notifyListeners`
ref.read(_counterProv.notifier).updateNoNotify(
(e) => data,
);
return Consumer(
builder: (context, ref, _) {
final count = ref.watch(_counterProv).value;
return Column(
children: [
IconButton(
onPressed: () {
ref.read(_counterProv.notifier).update(
(value) => value + 1,
);
},
icon: const Icon(Icons.add),
),
Text(
count.toString(),
),
],
);
},
);
},
);
}
}
These are not the safest workarounds, but they are workarounds, and I will be updating this answer once I find a safe way to do this.

Is there other method to update data in custom listview in provider Flutter

I am fetching data from firebase and show in listview. I store data in list and and then notify it but still list length is null.
I study all related question but still not get solution.
i want whenever I add some new data in firebase It appears same time in listview. So for that I am using provider here. please also give me some idea for this.
Thank you in advance
Here is Code
class Home121 extends StatefulWidget {
#override
_Home121State createState() => _Home121State();
}
final fb = FirebaseDatabase.instance.reference().child("Data");
}
#override
Widget build(BuildContext context) {
Future<void> StoreData() async {
dynamic key=CreateCryptoRandomString(32);
fb.child(key).set({
"id": key,
"link": "EasyCoding",
}).then((value) {
ShowToastNow();
});
}
return ChangeNotifierProvider<TaskManager>(
create: (context) => TaskManager(),
child: Scaffold(
appBar: AppBar(
title: Text("Kkkk"),
),
body: Container(
margin: EdgeInsets.all(
8,
),
child: Consumer<TaskManager>(
builder: (context, myModel, child) {
return myModel.list.length==0? Text("zero"):ListView.builder(
itemCount:myModel.list.length,
itemBuilder: (context,index){
return Text(
myModel.list[index].Name,
style: TextStyle(
fontSize: 20,
),
);
},
);
}
)
),
)
);
}
#override
void initState() {
TaskManager task=TaskManager();
task.GetData();
}
}
class TaskManager extends ChangeNotifier{
List<Model> list=new List();
GetData(){
//get data here
}
AddNewTask(){
// add data to databse
list.add(model);
notifyListeners();
}
}
Edit:
class MyClass extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<TaskManager>(
create: (context) => TaskManager(),
child: Home121());
}
}
This is because in initState() you are creating another instance of TaskManager and calling the method GetData() on that instance.
So the instance of TaskManager which the Consumer widgets use is different and so the list inside that one always remain empty.
Instead you need to do this inside initState()..
void initState() {
TaskManager task = Provider.of<TaskManager>(context);
task.GetData();
}
By doing this, you call the GetData() method on the same instance which is used by the Consumer widgets.

Flutter Streambuilder snapshot is null after moving to OnGenerateRoute

Back again. I refactored my code after the advice in this thread: Flutter Multiple Blocs and NamedRoutes
However, since moving my bloc from the main material app tree to the router page, the data isn't loading as snapshot is null.
The Router:
class AppRouter {
final _centresBloc = CentresBloc();
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case routes.CentreSelectScreenRoute:
return MaterialPageRoute(
builder: (_) => BlocProvider(
bloc: _centresBloc,
child: CentreSelectScreen(),
),
);
default:
return MaterialPageRoute(builder: (context) => HomeScreen());
}
and the CentreSelectScreen class itself
class _CentreSelectScreenState extends State<CentreSelectScreen> {
#override
Widget build(BuildContext context) {
final _centresBloc = BlocProvider.of<CentresBloc>(context);
return Scaffold(
body: Container(
child: StreamBuilder<List<ClimbingCentre>>(
stream: _centresBloc.centres,
builder: (context, snapshot) {
print('snapshot == ${snapshot.data}'); //is always null now
if (snapshot.hasData) {
// If there are no centres (data), display this message.
if (snapshot.data.length == 0) {
return Text('No Centres listed');
}...
The blocprovider was originally in the centreselect screen class which all worked fine, but since moving it, it's not working, and I can't seem to figure out why.
Th blog itself seems to initialise properly when the app first loads, as it is printing out all the correct information. From CentresBloc:
void getCentres() async {
// Retrieve all the centres from the database
List<ClimbingCentre> centres = await ClimbDB.db.getCentres();
// Add all of the centres to the stream so we can grab them later from our pages
_inCentres.add(centres);
print('BLOC incentres is $centres'); //this works and prints all centres when the app first loads...
}
Any help most appreciated.
EDIT Adding CentresBloc Class
import 'dart:async';
import 'package:flutterapp/data/database.dart';
import 'package:flutterapp/models/centre_model.dart';
import 'bloc_provider.dart';
class CentresBloc implements BlocBase {
final _centresController = StreamController<List<ClimbingCentre>>.broadcast();
// Input stream. Add centres to the stream using this variable.
StreamSink<List<ClimbingCentre>> get _inCentres => _centresController.sink;
// Output stream. This one will be used within our pages to display the centres.
Stream<List<ClimbingCentre>> get centres => _centresController.stream;
CentresBloc() {
// Retrieve all the climbing centres on initialization
getCentres();
}
#override
void dispose() {
_centresController.close();
}
void getCentres() async {
// Retrieve all the centres from the database
List<ClimbingCentre> centres = await ClimbDB.db.getCentres();
// Add all of the centres to the stream so we can grab them later from our pages
_inCentres.add(centres);
print('CentreBloc _incentres is: $centres'); //this prints the correct centres when the app is first loaded
}
After a new route is pushed, StreamBuilder subscribes to the same stream _centresBloc.centres, but no new events are emitted on this stream, as it only happens once - during bloc's initialization in constructor. That's because Dart's default StreamController won't send previous events/values (including the last one) to stream's new subscribers.
However, you can use BehaviorSubject from rxdart library, which is based on StreamController, but it also stores the last value emitted and sends it to any new subscriber. Subjects are also always broadcast streams and can have initial (seed) value.
Just replace this:
final _centresController = StreamController<List<ClimbingCentre>>.broadcast();
with this:
final _centresController = BehaviorSubject<List<ClimbingCentre>>();
Full working code:
import 'dart:async';
import 'package:bloc_provider/bloc_provider.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/subjects.dart';
void main() => runApp(MyApp2());
class CentresBloc implements Bloc {
final _centresController = BehaviorSubject<List<String>>();
// Input stream. Add centres to the stream using this variable.
StreamSink<List<String>> get _inCentres => _centresController.sink;
// Output stream. This one will be used within our pages to display the centres.
Stream<List<String>> get centres => _centresController.stream;
CentresBloc() {
// Retrieve all the climbing centres on initialization
getCentres();
}
void dispose() {
_centresController.close();
}
void getCentres() async {
// Retrieve all the centres from the database
List<String> centres = ['LIST'];
// Add all of the centres to the stream so we can grab them later from our pages
_inCentres.add(centres);
print('CentreBloc _incentres is: $centres'); //this prints the correct centres when the app is first loaded
}
}
class AppRouter {
final _centresBloc = CentresBloc();
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case 'test':
return MaterialPageRoute(
builder: (_) => BlocProvider<CentresBloc>.fromBloc(
bloc: _centresBloc,
child: CentreSelectScreen(),
),
);
default:
return MaterialPageRoute(builder: (context) => HomeScreen());
}
}
void dispose() {
_centresBloc.dispose();
}
}
class MyApp2 extends StatefulWidget {
#override
_MyApp2State createState() => _MyApp2State();
}
class _MyApp2State extends State<MyApp2> {
final _router = AppRouter();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
onGenerateRoute: _router.generateRoute,
);
}
#override
void dispose() {
_router.dispose();
super.dispose();
}
}
class CentreSelectScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CentreSelectScreenState();
}
class _CentreSelectScreenState extends State<CentreSelectScreen> {
#override
Widget build(BuildContext context) {
final _centresBloc = BlocProvider.of<CentresBloc>(context);
return Scaffold(
body: Container(
child: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
MaterialButton(
child: Text('push index'),
onPressed: () {
Navigator.pushReplacementNamed(context, '/');
},
),
StreamBuilder<List<String>>(
stream: _centresBloc.centres,
builder: (context, snapshot) {
print('snapshot == ${snapshot.data}'); //is always null now
if (snapshot.hasData) {
// If there are no centres (data), display this message.
if (snapshot.data.length == 0) {
return Text('No Centres listed');
} else {
return Text(snapshot.data.toString());
}
}
return Container();
}
),
],
),
),
),
),
);
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: MaterialButton(
onPressed: () => Navigator.pushReplacementNamed(context, 'test'),
child: Text('push test')
)
)
)
);
}
}

How to use dart.core.sink in flutter

import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
//Using Bloc
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.darkThemeEnabled,
initialData: false,
builder: (context, snapshot) => MaterialApp(
theme: snapshot.data ? ThemeData.dark() : ThemeData.light(),
home: HomePage(snapshot.data)),
);
}
}
class HomePage extends StatelessWidget {
final bool darkThemeEnabled;
HomePage(this.darkThemeEnabled);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Dynamic Theme"),
),
body: Center(
child: Text("Hello World"),
),
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Dark Theme"),
trailing: Switch(
value: darkThemeEnabled,
onChanged: bloc.changeTheme,
),
)
],
),
),
);
}
}
class Bloc {
final _themeController = StreamController<bool>();
get changeTheme => _themeController.sink.add;
get darkThemeEnabled => _themeController.stream;
}
final bloc = Bloc();
1.A warning says to Close instances of dart.core.sink
2.Why dart.core.sink is used in flutter?
3.How can I solve this error
4.Its error documentation redirects me to this website link
5.I don't know how to use these methods in flutter please guide me
dart.core.sink is an interface that is implemented by Stream.
The warning is showing, because the dart compiler wants you to .close() your instance of a Stream. In this case that is your final _themeController = StreamController<bool>().
If you want to fix the warning, add
void dispose() {
_themeController.close();
}
to your Bloc class.
Just adding the method is not doing much, since it's not called. So you should change your main() method to call bloc.dispose() after runApp(MyApp()).
That error occur when missing close StreamController.
Simple way to fix:
Create abstract class:
abstract class Bloc {
void dispose();
}
Your bloc class implements Bloc, now you can close StreamController in dispose:
class ColorBloc implements Bloc {
// streams of Color
StreamController streamListController = StreamController<Color>.broadcast();
// sink
Sink get colorSink => streamListController.sink;
// stream
Stream<Color> get colorStream => streamListController.stream;
// function to change the color
changeColor() {
colorSink.add(getRandomColor());
}
// Random Colour generator
Color getRandomColor() {
Random _random = Random();
return Color.fromARGB(
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
// close Stream
#override
void dispose() {
streamListController.close();
}
}

StreamBuilder not re-rendering the widget inside?

I created this code, what i want to happen is when i press on the button i want the piechart to re-render with the new values (which should be old values but the food value increased by 1)
I am using a piechart from pie_chart: 0.8.0 package.
Deposit is nothing but a pojo (String category and int deposit)
the bloc.dart contains a global instance of the bloc, a getter for the stream and initialization of a stream of type
Here's my code:
import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart';
import 'bloc.dart';
import 'Deposit.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'bloc Chart',
theme: ThemeData(
primarySwatch: Colors.blueGrey,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
Map<String, double> datamap = new Map();
#override
Widget build(BuildContext context) {
datamap.putIfAbsent("Food", () => 5);
datamap.putIfAbsent("transportation", () => 3);
return Scaffold(
appBar: AppBar(
title: Text("PieChart using blocs"),
),
body: Column(
children: <Widget>[
StreamBuilder<Deposit>(
stream: bloc.data, //A stream of Deposit data
builder: (context, snapshot) {
addDeposit(Deposit("Food", 1), datamap);
debugPrint("Value of food in map is: ${datamap["Food"]}");
return PieChart(dataMap: datamap);
}),
SizedBox.fromSize(
size: Size(20, 10),
),
RaisedButton(
onPressed: () {
bloc.add(Deposit("Food", 1)); //returns the stream.add
},
child: Icon(Icons.add),
),
],
),
);
}
void addDeposit(Deposit dep, Map<String, double> map) {
if (map.containsKey(dep.category)) {
map.update(dep.category, (value) => value + dep.price);
} else
map.putIfAbsent(dep.category, () => dep.price);
}
}
I think your problem is that the stream doesn't trigger new events. You don't have to close the stream to rebuild. I can't see anywhere in your code where you are triggering new events for the stream. Check below code to see a simple way how you can update a StatelessWidget using a StreamBuilder.
class CustomWidgetWithStream extends StatelessWidget {
final CustomBlock block = CustomBlock();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
StreamBuilder(
stream: block.stream,
builder: (context, stream) {
return Text("${stream.data.toString()}");
}),
RaisedButton(
onPressed: () {
block.incrementNumber();
},
child: Text("Increment"),
)
],
);
}
}
class CustomBlock {
num counter = 10;
final StreamController<num> _controller = StreamController();
Stream<num> get stream => _controller.stream;
CustomBlock() {
_controller.onListen = () {
_controller.add(counter); // triggered when the first subscriber is added
};
}
void incrementNumber() {
counter += 1;
_controller.add(counter); // ADD NEW EVENT TO THE STREAM
}
dispose() {
_controller.close();
}
}
Although this is a working code snippet, I would strongly suggest to change your widget from StatelessWidget to StatefulWidget, for two reasons:
* if you go "by the book", if a widget changes the content by itself, then it's not a StatelessWidget, a stateless widget only displays data that is given to it. In your case, the widget is handling the tap and then decides what to do next and how to update itself.
* if you are using streams, in a stateful widget you can safely close the stream, as you can see in the above code, there's no safe way to close the stream. If you don't close the stream, there might be unwanted behaviour or even crashes.
This is my bloc file
import 'package:rxdart/rxdart.dart';
import 'package:testing/Deposit.dart';
class Bloc{
final _data = new BehaviorSubject<Deposit>();
Stream<Deposit> get data => _data.stream;
Function(Deposit) get add => _data.sink.add;
void dispose(){
_data.close();
}
}
Bloc bloc = new Bloc();