Currently, I am trying to learn how bloc pattern works but i have some problems. I am trying to use BLOC pattern on flutter counter application which is default application for flutter project. Here is my main (layout) code:
import 'package:flutter/material.dart';
import 'package:suaybtest/my-home-page-state.dart';
import 'my-home-page-bloc.dart';
import 'package:flutter_bloc/flutter_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: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
final MyHomePageBloc _bloc = MyHomePageBloc();
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: BlocBuilder<MyHomePageEvent, MyHomePageState>(
bloc: _bloc,
builder: (context, MyHomePageState state) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${state.counter}',
style: Theme.of(context).textTheme.display1,
),
],
),
);
}),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () {
_bloc.dispatch(MyHomePageEvent.increment);
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
FloatingActionButton(
onPressed: () {
_bloc.dispatch(MyHomePageEvent.decrement);
},
tooltip: 'Decrement',
child: Icon(Icons.remove),
),
],
));
}
#override
void dispose() {
super.dispose();
}
}
Bloc page is this:
import 'package:bloc/bloc.dart';
import 'package:suaybtest/my-home-page-state.dart';
enum MyHomePageEvent {
increment,
decrement,
}
class MyHomePageBloc extends Bloc<MyHomePageEvent, MyHomePageState> {
#override
MyHomePageState get initialState => MyHomePageState.initial();
#override
Stream<MyHomePageState> mapEventToState(MyHomePageEvent event) async* {
MyHomePageState state = currentState;
switch (event) {
case MyHomePageEvent.increment:
state.counter++;
yield state;
break;
case MyHomePageEvent.decrement:
state.counter--;
yield state;
break;
}
}
}
and finally here is my state
class MyHomePageState {
int counter;
MyHomePageState._();
factory MyHomePageState.initial(){
return MyHomePageState._()..counter = 0;
}
}
I know there is no need to use state class like this because I have just one variable. However this is the closest sample for real world app.
The problem here when I click the buttons it works counter increasing you can display it in debug mode but build function of blocbuilder does not reload.
I use this library
dependencies:
flutter_bloc: ^0.11.1
Related
I have an AppBar in main.dart and I want to defined it as primary on it's child, But I want to change the title of AppBar itself when I'm on child's page, how can i do that properly?
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter App",
theme: ThemeData(
primaryColor: Colors.cyan,
brightness: Brightness.dark
),
home: Scaffold(
appBar: AppBar(
title: Text("Main Dart"),
),
body: HomeScreen(),
),
routes: <String, WidgetBuilder>{
'/homeScreen': (buildContext)=>HomeScreen(),
'/second': (buildContext)=>Second()
},
);
}
}
//HomeScreen or Second Widget on different dart file
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
//here I want to change the title of Main Dart to HomeScreen
return Container(
child: Center(
child: FlatButton(
child: new Text("Home screen"),
onPressed: (){
Route route = MaterialPageRoute(builder: (context) => Second());
Navigator.push(context, route);
},
),
),
);
}
}
or I need to put Scaffold(appBar:AppBar(...), ...) in every screen? it is the best approach?
Have a BLoC for app properties in app_properties_bloc.dart
final appBloc = AppPropertiesBloc();
class AppPropertiesBloc{
StreamController<String> _title = StreamController<String>();
Stream<String> get titleStream => _title.stream;
updateTitle(String newTitle){
_title.sink.add(newTitle);
}
dispose() {
_title.close();
}
}
Use stream builder in AppBar like this:
AppBar(
title: StreamBuilder<Object>(
stream: appBloc.titleStream,
initialData: "Main Dart",
builder: (context, snapshot) {
return Text(snapshot.data);
}
),
),
Use this to update title on button's onPressed()
onPressed: () {
appBloc.updateTitle('new title');
},
Just in case you are changing only the title of Scaffold then this will work.
I am creating a DefaultScaffold with the title each screen provides. Here the code will show the MainPage and two other pages which have the same AppBar with changed titles.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(initialRoute: 'home', routes: <String, WidgetBuilder>{
'home': (context) => SOMain(),
'/secondPage': (context) => DefaultScaffold("Second Screen", SOSecond()),
'/thirdPage': (context) => DefaultScaffold("Third Screen", SOThird()),
});
}
}
class DefaultScaffold extends StatelessWidget {
String title;
Widget body;
DefaultScaffold(this.title, this.body);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: body,
);
}
}
class SOMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultScaffold(
"Main Screen",
Center(
child: RaisedButton(
child: Text("Go to second screen"),
onPressed: () {
Navigator.pushNamed(context, '/secondPage');
}),
),
);
}
}
class SOSecond extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("Go the 3rd screen"),
onPressed: () => Navigator.pushNamed(context, "/thirdPage"),
),
);
}
}
class SOThird extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(child: Text("You are on last screen"));
}
}
Note: This is a simple workaround and may not be the best way to do this.
You can accomplish updating the state of the parent from a child by using a callback function.
Parent Class:
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return ParentState();
}
}
class ParentState extends State<Parent> {
String title = "Old Title";
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: DaysFragmentView(onTitleSelect: (String value) {
setTitle(value);
}
),
);
}
void setTitle(String value) {
setState(() {
title = value;
});
}
}
Child Class
typedef TitleCallback = void Function(Title color);
class DaysFragmentView extends StatelessWidget {
const DaysFragmentView({this.onTitleSelect});
final TitleCallback onTitleSelect;
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
RaisedButton(
child: Text('One'),
onPressed: () {
onTitleSelect("TITLE ONE");
},
),
RaisedButton(
child: Text('Two'),
onPressed: () {
onTitleSelect("TITLE TWO");
},
)
],
);
}
}
Reference:
call-method-in-one-stateful-widget-from-another-stateful-widget-flutter
working-with-callback-in-flutter
Using ValueListenableBuilder is an option.
Use an instance variable
String appTitle;
Then set the app bar as in the following block:
appBar: AppBar(
ValueListenableBuilder<String>(
valueListenable: appTitle,
builder: (context, value, child) {
return Text(appTitle.value);
},
),
After that you can simply set appTitle.value in the other class. The title will be changed too because it listens to that value.
appTitle.value = "Home Screen";
Some answer here are too complicated. Here is a full working example using app bar update from child with scafold widget.
You can run the example in dart pad
import 'package:flutter/material.dart';
void main() {
runApp(const MyHomePage(title: 'init title'));
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ValueNotifier<String?> _appBarTitleNotifier = ValueNotifier<String?>(null);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: ValueListenableBuilder<String?>(
builder: (BuildContext context, String? value, Widget? child) {
return Text(value ?? widget.title);
},
valueListenable: _appBarTitleNotifier,
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ChildDemoTitleBar(titleNotifier: _appBarTitleNotifier)
],
),
),
),
);
}
}
class ChildDemoTitleBar extends StatefulWidget {
final ValueNotifier<String?> titleNotifier;
const ChildDemoTitleBar({Key? key, required this.titleNotifier})
: super(key: key);
#override
State<ChildDemoTitleBar> createState() => _ChildDemoTitleBarState();
}
class _ChildDemoTitleBarState extends State<ChildDemoTitleBar> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: InkWell(
onTap: () {
_counter++;
widget.titleNotifier.value = "title updated $_counter";
},
child: const Text("tap to update title")));
}
}
I am new to Flutter, I code two pages, page A and page B. I use ReorderableListView to show a list of textfields in widget B. When I navigate to page B from page A, the textfields have lost focus immediately after I tap on textfield. Can someone help?
void main() => runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TapClass(),
));
class TapClass extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
child: Text('click me'),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => MyHomePage()));
},
),
));
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ReorderableListView(
children: <Widget>[
Row(
key: ValueKey('key'),
children: <Widget>[
Expanded(
child: TextField(
decoration: InputDecoration(hintText: 'type in'),
),
)
],
)
],
onReorder: (int oldIndex, int newIndex) {},
));
}
}
Because you have only one child in the ReorderaleListView . You should have more than one child in it and allot all of them different keys. Hope this will help you.
I'm trying to learn Dart/Flutter and am working on an example where there's a button on the app that says "Get Data", and when I touch it I want to retrieve JSON data from a restful service.
I see the web service being called in fetchPost, but the builder property of the FutureBuilder isn't called.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'ResultsList.dart';
import 'dart:convert';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Restul Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: (){
FutureBuilder<ResultsList>(
future: fetchPost(),
builder: (context, snapshot){
print('In Builder');
}
);
},
child: Text('Get data'),
)
],
),
)
);
}
}
Future<ResultsList> fetchPost() async {
final response = await http.get('http://mywebserviceurl');
if (response.statusCode == 200){
print('Received data');
return ResultsList.fromJson(json.decode(response.body));
}
else {
throw Exception('Failed to load data');
}
}
Interestingly though, if I move the FutureBuilder out of the onPressed of the button to the child of Center, I do see the builder property getting called.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'ResultsList.dart';
import 'dart:convert';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Restul Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: FutureBuilder<ResultsList>(
future: fetchPost(),
builder: (context, snapshot){
print ('In Builder');
return Container();
}
)
)
);
}
}
Future<ResultsList> fetchPost() async {
final response = await http.get('http://mywebserviceurl');
if (response.statusCode == 200){
print('Received data');
return ResultsList.fromJson(json.decode(response.body));
}
else {
throw Exception('Failed to load data');
}
}
Obviously I'm missing something, but any idea what I'm doing wrong?
If you want to get some data from request - you don't need FutureBuilder. You can do:
RaisedButton(
onPressed: (){
fetchPost().then((result) {
print('In Builder');
})
},
child: Text('Get data'),
)
or
RaisedButton(
onPressed: () async {
var result = await fetchPost()
print('In Builder');
},
child: Text('Get data'),
)
The onPressed method in this RaisedButton is actually not doing anything. It just creates a new FutureBuilder which does nothing but existing^^ It's like you would just call 1+1;, which just creates a value, but that value is not used to do anything.
RaisedButton(
onPressed: (){
FutureBuilder<ResultsList>(
future: fetchPost(),
builder: (context, snapshot){
print('In Builder');
}
);
},
child: Text('Get data'),
)
You could have body be assigned to a Widget(which could just be called body or whatever you want^^), which you then change in a setState((){body = FutureBuilder(/*...*/}); call.
For me FutureBuilder not working in onPresses...
I used this way :
I defined a variable in state:
bool visiblity = false;
and I used this code in build:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () {
visiblity=true;
fetchPost();
},
child: Text('Get data'),
),
FutureBuilder<ResultsList>(
future: ("Your View Model that return from call back"),
builder: (context, snapshot) {
if (visiblity) {
print('In Builder');
visiblity=false;
} else
return Container();
}
),
],
),
)
);
}
I didn't put FutureBuilder in onPressed. I put that in body and changed visibility after return result.
I'm using the Flutter UserAccountsDrawerHeader widget to display the user's data but I could not figure out how to implement the onDetailsPressed() function to call the user details. Here is my code:
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return new Scaffold(
drawer: _buildDrawer(context),
appBar: _buildAppBar(),
);
}
}
Widget _buildAppBar() {
return new AppBar();
}
Widget _buildDrawer(BuildContext context) {
return new Drawer(
child: new ListView(
children: <Widget>[
new UserAccountsDrawerHeader(
accountName: new Text("Cleudice Santos"),
accountEmail: new Text("cleudice.ms#gmail.com"),
onDetailsPressed: () {},
),
new ListTile(
title: new Text("Visão geral"),
leading: new Icon(Icons.dashboard),
onTap: () {
print("Visão geral");
},
),
],
),
);
}
I want to click the arrow and show the account details as shown below. That is, overlapping the content of the drawer. As the Gmail app does.
Basically, what you should be doing is replacing the rest of the content with user details rather than the current list. The simplest way to do this is to make your drawer into a stateful widget and have a boolean that keeps track of whether user details or the normal list should be shown.
I've added that to your code (and added a bit to make it self-contained so you can paste it to a new file to test out):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: UserDetailDrawer(),
appBar: _buildAppBar(),
);
}
}
Widget _buildAppBar() {
return AppBar();
}
class UserDetailDrawer extends StatefulWidget {
#override
_UserDetailDrawerState createState() => _UserDetailDrawerState();
}
class _UserDetailDrawerState extends State<UserDetailDrawer> {
bool showUserDetails = false;
Widget _buildDrawerList() {
return ListView(
children: <Widget>[
ListTile(
title: Text("Visão geral"),
leading: Icon(Icons.dashboard),
onTap: () {
print("Visão geral");
},
),
ListTile(
title: Text("Another tile??"),
leading: Icon(Icons.question_answer),
),
],
);
}
Widget _buildUserDetail() {
return Container(
color: Colors.lightBlue,
child: ListView(
children: [
ListTile(
title: Text("User details"),
leading: Icon(Icons.info_outline),
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(children: [
UserAccountsDrawerHeader(
accountName: Text("Cleudice Santos"),
accountEmail: Text("cleudice.ms#gmail.com"),
onDetailsPressed: () {
setState(() {
showUserDetails = !showUserDetails;
});
},
),
Expanded(child: showUserDetails ? _buildUserDetail() : _buildDrawerList())
]),
);
}
}
I'm trying to replace the increment flutter app code, by using Streams from Dart API without using scoped_model or rxdart.
So I read this and watched this, but could not get it work for me, my codes are:
StreamProvider.dart:
import 'package:flutter/widgets.dart';
import 'businessLogic.dart';
import 'dart:async';
class Something {
final _additionalContrllerr = StreamController<int>();
Sink<int> get addition => _additionalContrllerr.sink;
Stream<int> get itemCount => _additionalContrllerr.stream;
}
class StreemProvider extends InheritedWidget {
final Something myBloc; // Business Logic Component
StreemProvider({
Key key,
#required this.myBloc,
Widget child,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static Something of(BuildContext context) =>
(context.inheritFromWidgetOfExactType(StreemProvider) as StreemProvider)
.myBloc;
}
main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_app/StreemProvider.dart';
void main() => runApp(MyApp(
textInput: Text("Provided By the Main"),
));
class MyApp extends StatefulWidget {
final Widget textInput;
MyApp({this.textInput});
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
bool checkBoxValue = false;
int _counter = 0;
#override
Widget build(BuildContext ctxt) {
var x = Something(); //// Not sure if have to use this!
return StreemProvider(
myBloc: x, //// Not sure about this!!
child: MaterialApp(
home: SafeArea(
child: Scaffold(
body: new Center(
child: new Column(
children: <Widget>[
widget.textInput,
Text("clickec $_counter times"),
Text("clickec ${x.itemCount.listen((int i) => i)} times"),
/// How to get the value of i??!
Checkbox(
value: checkBoxValue,
onChanged: (bool newValue){
setState(() {
checkBoxValue = newValue;
});
}
)
],
)),
floatingActionButton: Incrementer(_increment),
// floatingActionButton: Incrementer(x),
),
),
),
);
}
_increment() {
setState(() {
_counter += 1;
});
}
}
class Incrementer extends StatefulWidget {
final Function increment;
Incrementer(this.increment);
#override
State<StatefulWidget> createState() {
return IncrementerState();
}
}
class IncrementerState extends State<Incrementer>{
#override
Widget build(BuildContext ctxt) {
final myBloc = StreemProvider.of(context);
return new FloatingActionButton(
//onPressed: widget.increment,
// How ot get the latest value!!
onPressed: () async {
var y = await myBloc.itemCount.last;
if (y.isNaN) y = 0;
myBloc.addition.add(y+1);
},
child: new Icon(Icons.add),
);
}
}
don't know the restrictions on rx_dart, but I can only try to answer by you using it. lol
your bloc doesnt define wht to listen in your input stream, this is how I could get it to work
counter_bloc.dart
import 'package:rxdart/rxdart.dart';
import 'dart:async';
class CounterBloc {
int _count = 0;
ReplaySubject<int> _increment = ReplaySubject<int>();
Sink<int> get increment => _increment;
BehaviorSubject<int> _countStream = BehaviorSubject<int>(seedValue: 0);
Stream<int> get count => _countStream.stream;
CounterBloc() {
_increment.listen((increment) {
_count += increment;
_countStream.add(_count);
});
}
}
In the constructor the listen method is set for that stream. for each increment sent, it'll increment the counter and send the current count to another stream.
In main.dart, removed the _counter property since that's now being handled by the BLOC. and to display I used a stream builder.
also added a second fab, with a +2 increment to test the logic.
hope this helps you model your bloc class. :)
a good bloc reference: https://www.youtube.com/watch?v=PLHln7wHgPE
main.dart
import 'counter_bloc.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
CounterBloc bloc = CounterBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder<int>(
stream: bloc.count,
initialData: 0,
builder: (BuildContext c, AsyncSnapshot<int> data) {
return Text(
'${data.data}',
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () {
bloc.increment.add(2);
},
tooltip: 'Increment 2',
child: Text("+2"),
),
FloatingActionButton(
onPressed: () {
bloc.increment.add(1);
},
tooltip: 'Increment 1',
child: Text("+1"),
),
],
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Thanks a lot to vbandrade his answer helped me figuring t out. the solution worked with me is:
I need to have 2 StreamController if I need to listen to a sink in my bloc Business Logic Component, then process and stream the output to other elements.
The counter_bloc.dart is:
import 'dart:async';
class CounterBloc {
int _count = 0;
// The controller to stream the final output to the required StreamBuilder
final _counter = StreamController.broadcast<int>();
Stream<int> get counter => _counter.stream;
// The controller to receive the input form the app elements
final _query = StreamController<int>();
Sink<int> get query => _query.sink;
Stream<int> get result => _query.stream;
// The business logic
CounterBloc() {
result.listen((increment) { // Listen for incoming input
_count += increment; // Process the required data
_counter.add(_count); // Stream the required output
});
}
void dispose(){
_query.close();
_counter.close();
}
}
And the main.dart is:
import 'counter_bloc.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
State<StatefulWidget> createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
var bloc = CounterBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder<int>( // Listen to the final output sent from the Bloc
stream: bloc.counter,
initialData: 0,
builder: (BuildContext c, AsyncSnapshot<int> data) {
return Text(
'${data.data}',
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () {
bloc.query.add(2); // Send input to the Bloc
},
tooltip: 'Increment 2',
child: Text("+2"),
),
FloatingActionButton(
onPressed: () {
bloc.query.add(1); // Send input to the Bloc
},
tooltip: 'Increment 1',
child: Text("+1"),
),
],
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
A simple implementation
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
int _counter = 0;
final StreamController<int> _streamController =
StreamController<int>.broadcast();
Stream<int> get _stream => _streamController.stream;
void incrementCounter() {
_counter++;
_streamController.add(_counter);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter demo'),
),
body: Center(
child: StreamBuilder<int>(
stream: _stream,
builder: (ctxt, snapshot) {
if (snapshot.hasData) {
return Text(
'You have pushed this button ${snapshot.data} times');
}
return Text('You have pushed this button ${0} times');
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}