Flutter Bloc state does not update no matter what - flutter

I am currently trying to learn state management for flutter and i have the below code, but for some reason the state is never updated, am i missing something?
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (context) => ItemBloc(),
child: MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Bloc Demo"),
),
body: Lister(),
floatingActionButton: FloatingActionButton(
onPressed: () {
ItemBloc().add(AddEvent());
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class Lister extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ItemBloc, ListState>(
builder: (context, state) {
if (state is ListAdded) {
return ListView.builder(
itemCount: state.items.length,
itemBuilder: (context, index) {
return Text("item"); // This never increases in count
},
);
}
return Center(
child: Text('err'),
);
},
);
}
}
// BLOC
class ItemBloc extends Bloc<ListEvent, ListState> {
#override
get initialState => ListAdded(items: ["initial"]);
#override
Stream<ListState> mapEventToState(ListEvent event) async* {
if (event is AddEvent) {
final lister = ["first", "second"];
yield ListAdded(items: lister);
}
}
}
abstract class ListEvent extends Equatable {
#override
List<Object> get props => [];
}
class AddEvent extends ListEvent {}
abstract class ListState extends Equatable {
const ListState();
#override
List<Object> get props => [];
}
class ListAdded extends ListState {
final List<String> items;
const ListAdded({
this.items,
});
#override
List<Object> get props => [items];
#override
String toString() => 'ItemsLoaded { items: ${items.length} }';
}
Please note: I have tried separating the code into individual files and importing the bloc.dart package instead of flutter_bloc.dart package in the Bloc file

When you do
ItemBloc().add(AddEvent())
you're creating new bloc instance and add event to it. It isn't connected with one created in MyApp and used in Lister.
You should get the created bloc instead:
BlocProvider.of<ItemBloc>(context).add(AddEvent())

Related

How to setState widget by other widget Flutter ,simplecode below

right widget has gesterdetector that adds a String ("ZzZ") to List;
left widget shows all String there in String list by List view Buildder,
right widget adds "ZzZ" to list after pressing the button successfully but it dosent sets ui state...
in android studio after hot reload it shows all added "ZzZ"
import 'package:flutter/material.dart';
List<String> ListOfZzZ=[];
class homescreen extends StatefulWidget {
#override
_homescreenState createState() => _homescreenState();
}
class _homescreenState extends State<homescreen> {
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: Row(children: [
Expanded(child:RightSidewidget()),
Expanded(child:LeftSidewidget())
],
)),
);
}
}
class RightSidewidget extends StatefulWidget {
#override
_RightSidewidgetState createState() => _RightSidewidgetState();
}
class _RightSidewidgetState extends State<RightSidewidget> {
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(child:Text("add new ZzZ"),),
**onTap: (){
setState(() {
ListOfZzZ.add("ZzZ");
});},);**
}
}
class LeftSidewidget extends StatefulWidget {
#override
_LeftSidewidgetState createState() => _LeftSidewidgetState();
}
class _LeftSidewidgetState extends State<LeftSidewidget> {
#override
Widget build(BuildContext context) {
return Container(child:
ListView.builder(
itemCount: ListOfZzZ.length,
itemBuilder: (context,index)=>Text(ListOfZzZ[index])),);
}
}
check the Provider package it can help you achieve what you want, ere is a really good tutorial by the flutter devs showing how to use manage the state of your app and notify widgets of the changes other widgets have.
setState rebuild in very specyfic way. you can read about this in here:
https://api.flutter.dev/flutter/widgets/State/setState.html
in simple world setState call the nearest build (I think this is not full true, but this intuitions works for me)
In your code when you tap right widget and call setState only rightwidget will be rebuild.
So this is the easy solutions:
Make left and right widget statless.
In homescreen in row add gestureDetector(or textButton like in my example) and here call setState. When you do that, all homeSreen will be rebuild so left and right widget too. and your list will be actual. Here is example:
List<String> ListOfZzZ = [];
class homescreen extends StatefulWidget {
#override
_homescreenState createState() => _homescreenState();
}
class _homescreenState extends State<homescreen> {
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: Row(
children: [
Expanded(
child: TextButton(
onPressed: () => setState(() {
ListOfZzZ.add("ZzZ");
}),
child: RightSidewidget())),
Expanded(child: LeftSideWidget())
],
)),
);
}
}
class RightSidewidget extends StatelessWidget {
const RightSidewidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.amber[50],
child: Text("add new ZzZ"),
);
}
}
class LeftSideWidget extends StatelessWidget {
const LeftSideWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: ListView.builder(
itemCount: ListOfZzZ.length,
itemBuilder: (context, index) => Text(ListOfZzZ[index])),
);
}
}
The hard way, but more elegant and better is to use some state manager like bloc. Here is official site: https://bloclibrary.dev/#/gettingstarted
there is a lot of tutorials and explanations. But this is not solutions for 5 minutes.
Edit: I make some solution with BLoC. I hope this help. I use flutter_bloc and equatable packages in version 7.0.1
void main() {
EquatableConfig.stringify = kDebugMode;
Bloc.observer = SimpleBlocObserver();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('myList'),
),
body: BlocProvider(
create: (context) => MylistBloc()..add(AddToList('Start')),
child: Row(
children: [
Expanded(flex: 1, child: buttonsPanel()),
Expanded(flex: 1, child: ListOfZzZ()),
],
),
),
),
);
}
}
class ListOfZzZ extends StatefulWidget {
const ListOfZzZ({Key? key}) : super(key: key);
#override
_ListOfZzZState createState() => _ListOfZzZState();
}
class _ListOfZzZState extends State<ListOfZzZ> {
late MylistBloc _mylistBloc;
#override
Widget build(BuildContext context) {
return BlocBuilder<MylistBloc, MylistState>(
//builder: (context, state) {return ListView.builder(itemBuilder: (BuildContext context,int index){return ListTile(title: state.positions[index];)},);},
builder: (context, state) {
if (state.positions.isEmpty) {
return const Center(child: Text('no posts'));
} else {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(state.positions[index]));
},
itemCount: state.positions.length,
);
}
},
);
}
}
class buttonsPanel extends StatefulWidget {
const buttonsPanel({Key? key}) : super(key: key);
#override
_buttonsPanelState createState() => _buttonsPanelState();
}
class _buttonsPanelState extends State<buttonsPanel> {
late MylistBloc _mylistBloc;
#override
void initState() {
super.initState();
_mylistBloc = context.read<MylistBloc>();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () => {_mylistBloc.add(AddToList('Spam'))},
child: Text('Spam')),
TextButton(
onPressed: () => {_mylistBloc.add(AddToList('Ham'))},
child: Text('Ham')),
],
);
}
class SimpleBlocObserver extends BlocObserver {
#override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
#override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print(error);
super.onError(bloc, error, stackTrace);
}
}
class MylistState extends Equatable {
final List<String> positions;
final int lenght;
const MylistState({this.positions = const <String>[], this.lenght = 0});
#override
List<Object> get props => [positions];
#override
String toString() => 'Lenght: {$lenght} Positions: {$positions}';
#override
MylistState copyWith(List<String>? positions) {
return MylistState(positions: positions ?? this.positions);
}
}
abstract class MylistEvent extends Equatable {
const MylistEvent();
#override
List<Object> get props => [];
}
class AddToList extends MylistEvent {
final String posToAdd;
#override
AddToList(this.posToAdd);
}
class MylistBloc extends Bloc<MylistEvent, MylistState> {
MylistBloc() : super(MylistState(positions: const <String>[]));
#override
Stream<MylistState> mapEventToState(
MylistEvent event,
) async* {
if (event is AddToList) {
yield await _mapListToState(state, event.posToAdd);
}
}
Future<MylistState> _mapListToState(
MylistState state, String posToAdd) async {
List<String> positions = [];
positions.addAll(state.positions);
positions.add(posToAdd);
return MylistState(positions: positions, lenght: positions.length);
}
}
}

I am trying provider, it seams everything fine, but the textField text is not updating in my text widget

I am practicing the state management. I am trying to update my textField (First class) to the TextWidgetwhich was in the Second() class. I don't know what problem i have made here?
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>(
create: (context) => Data(),
child: MaterialApp(
home: First(),
),
);
}
}
My First Class
class First extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TextField(
onChanged: (value) {
Provider.of<Data>(context).changeString(value);
},
),
Second(),
],
}
The Second Class
class Second extends StatelessWidget {
#override
Widget build(BuildContext context) {
return
Text(
Provider.of<Data>(context).text,
);
}
}
My Data class which extends ChangeNotifier
class Data extends ChangeNotifier {
String text = 'Type Something';
void changeString(String newText) {
text = newText;
notifyListeners();
}
}
Add a consumer to rebuild your widget with the new data.
Consumer<Data>(
builder: (context, value, _) => Text(
Provider.of<Data>(context).text,
);

Why is my "Data"."data" not updated in ChangeNotifier class using ChangeNotifierProvider?

I am new to flutter.
I want to ask why when my text field's onChange did not trigger: "Provider.ofContext).updateData(newString);".
The value of my Provider.of(context).data is not updated and with the 2 print statements, only 'called1' is always printed out.
Here is the code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>(
create: (_) => Data(),
lazy: false,
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(Provider.of<Data>(context).data),
),
body: Level2(),
),
),
);
}
}
class Level2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
MyTextField(),
],
);
}
}
class MyTextField extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TextField(onChanged: (newString) {
print('called1');
Provider.of<Data>(context).updateData(newString);
print('called2');
});
}
}
class Data extends ChangeNotifier {
String data = '1234567890';
void updateData(newString) {
data = newString;
notifyListeners();
}
}
You are trying to access provider in same widget where you are declaring, which is not right way to do, provider must declare in above widget where you are accessing.
Moreover always use provider data by variable(as used in MyTextField widget) other wise it will not work.
Following code may help you to understand more.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>(
create: (_) => Data(),
child: MaterialApp(home: Level1()),
);
}
}
class Level1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Provider.of<Data>(context).data),
),
body: Level2(),
);
}
}
class Level2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
MyTextField(),
],
);
}
}
class MyTextField extends StatelessWidget {
var dataprovider;
#override
Widget build(BuildContext context) {
dataprovider = Provider.of<Data>(context);
return TextField(
onChanged: (newString) {
print(dataprovider.data);
dataprovider.updateData(newString);
print('called2');
},
);
}
}
class Data extends ChangeNotifier {
String data = '1234567890';
void updateData(newString) {
print("cds");
data = newString;
notifyListeners();
}
}

how to edit value at the child widget

i try to edit the value of the child widget, i can do it with StatefulWidget parent but i want to do it with StatelessWidget parent and without using global value
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(child: Text('addFile'), onPressed: () {}),
FlatButton(child: Text('deleteFile'), onPressed: () {})
],
),
body: Child(),
);
}
}
class Child extends StatefulWidget {
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
var hasFile = true;
#override
Widget build(BuildContext context) {
return hasFile ? Text('has a file') : Text("no File");
}
}
You are thinking the wrong way. Child aka Text() should get its value from a model which is managed by the application or at least managed by the widget above. I would go with the provider package https://pub.dev/packages/provider and do this:
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
class MyState with ChangeNotifier {
String _myText;
MyState(this._myText);
getMyText() => _myText;
void changeText(String newText) {
_myText = newText;
notifyListeners();
}
}
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => MyState("initial Text")),
],
child: Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
child: Text('addFile'),
onPressed: () {
Provider.of<MyState>(context).changeText("addFile");
}),
FlatButton(
child: Text('deleteFile'),
onPressed: () {
Provider.of<MyState>(context).changeText("deleteFile");
})
],
),
body: Child(),
));
}
}
class Child extends StatelessWidget {
#override
Widget build(BuildContext context) {
MyState myState = Provider.of<MyState>(context);
return Text(myState.getMyText());
}
}
This is coded without IDE support or even compiling and running. But it should get you to the right direction.
You can use BLoC pattern to implement this kind of functionality,
Here is the BLoC class which will handle state of bool
import 'dart:async';
class Bloc {
final _fileController = StreamController<bool>();
changeState(bool val) {
_fileController.sink.add(val);
}
get hasFile => _fileController.stream;
dispose() {
_fileController.close();
}
}
final bloc = Bloc();
Then you can add stream builder in your Stateful Widget, in which you will provide stream of BLoC class.
StreamBuilder updates it's UI according to Stream.
class Child extends StatefulWidget {
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
var hasFile = true;
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.hasFile,
initialData: false,
builder: (context, snapshot) {
return snapshot.data ? Text('has a file') : Text("no File");
},
);
}
}
At last you can access BLoC class with your stateless widget as follows
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
child: Text('addFile'),
onPressed: () {
bloc.changeState(true);
}),
FlatButton(
child: Text('deleteFile'),
onPressed: () {
bloc.changeState(false);
})
],
),
body: Child(),
);
}
}
Full example is as below
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Homepage(),
);
}
}
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
child: Text('addFile'),
onPressed: () {
bloc.changeState(true);
}),
FlatButton(
child: Text('deleteFile'),
onPressed: () {
bloc.changeState(false);
})
],
),
body: Child(),
);
}
}
class Child extends StatefulWidget {
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
var hasFile = true;
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.hasFile,
initialData: false,
builder: (context, snapshot) {
return snapshot.data ? Text('has a file') : Text("no File");
},
);
}
}
class Bloc {
final _fileController = StreamController<bool>();
changeState(bool val) {
_fileController.sink.add(val);
}
get hasFile => _fileController.stream;
dispose() {
_fileController.close();
}
}
final bloc = Bloc();

How can I rerend data from Stream in Stateless Widget?

I am trying to re-rend Stateless widget every time when I am getting new data from Stream. But it does not work, and I can't understand when I should to call re-rend function:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Stream<int> stream; // I maked it's global for simplification
void main()
{
MyClass myClass = MyClass();
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft])
.then((_) {
runApp(new MyApp());
});
}
class MyClass
{
MyClass()
{
stream = Stream<int>.periodic(Duration(seconds: 1), (t) => t+1).take(9);
print("hello");
}
}
class MyApp extends StatelessWidget
{
#override
Widget build(BuildContext context)
{
return MaterialApp(
title: "Hello",
routes: {
'/' : (context) => SplashScreen(),
}
);
}
}
class SplashScreen extends StatelessWidget {
#override
Widget build(BuildContext context)
{
return Scaffold(
appBar: AppBar(),
body: Container(
child: Text("Hello World, $stream"),
),
);
}
}
please check example below
Both Parent and Child Widget are Stateless.
Use simple class to wrap stream controller, here class name is bloc
Child widget use StreamBuider
In parent widget call bloc function and pass parameter and then child widget will render
code snippet
bloc.changeState(true)
...
class Bloc {
final _fileController = StreamController<bool>();
changeState(bool val) {
_fileController.sink.add(val);
}
get hasFile => _fileController.stream;
dispose() {
_fileController.close();
}
}
full code
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Homepage(),
);
}
}
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
child: Text('addFile'),
onPressed: () {
bloc.changeState(true);
}),
FlatButton(
child: Text('deleteFile'),
onPressed: () {
bloc.changeState(false);
})
],
),
body: Child(),
);
}
}
class Child extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.hasFile,
initialData: false,
builder: (context, snapshot) {
return snapshot.data ? Text('has a file') : Text("no File");
},
);
}
}
class Bloc {
final _fileController = StreamController<bool>();
changeState(bool val) {
_fileController.sink.add(val);
}
get hasFile => _fileController.stream;
dispose() {
_fileController.close();
}
}
final bloc = Bloc();