BlocBuilder is not updating on state change - flutter

I'm trying to implement a player using a BLOC pattern. When I change the state in the mapEventToState() method the BlocBulder() widget is not updating as expected.
This is the PlayerBloc:-
class PlayerBloc extends Bloc<PlayerEvent, PlayerState> {
#override
get initialState => PlayerState.Initialized;
#override
Stream<PlayerState> mapEventToState(PlayerEvent event) async* {
if (event is InitialLoad) {
yield PlayerState.Initialized;
} else if (event is PlayStation) {
yield PlayerState.Playing;
} else if (event is StationPlaying) {
yield PlayerState.Playing;
} else if (event is StationStopped) {
yield PlayerState.Stopped;
} else {
yield PlayerState.Stopped;
}
}
}
This is the BlocBuilder:-
class PlayerCollapsed extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<PlayerBloc, PlayerState>(
bloc: PlayerBloc(),
builder: (BuildContext context, PlayerState state) {
if (state == PlayerState.Initialized) {
print(state);
return Center(child: CircularProgressIndicator());
} else {
return Text("State Updated");
}
},
);
}
}
Any help will be appreciated. 😊

Okay what happened is you used an instance of PlayerBloc provided by BlocProvider to emit event but you use different instance of PlayerBloc for your BlocBuilder.
If you have provided PlayerBloc to the subtree with BlocProvider, you don't wanna make a new one.
class PlayerCollapsed extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<PlayerBloc, PlayerState>(
// bloc: PlayerBloc(), <= delete this part
builder: (BuildContext context, PlayerState state) {
if (state == PlayerState.Initialized) {
print(state);
return Center(child: CircularProgressIndicator());
} else {
return Text("State Updated");
}
},
);
}
}
You may want to check out this https://github.com/felangel/bloc/issues/943#issuecomment-596220570

Related

Bad condition - Cubit

I'm trying to get to the RegistrationSendCode screen. But unfortunately I am getting a bad status error. Here is my provider and builder -
Provider -
class RegistrationSendCode extends StatelessWidget{
#override
Widget build(BuildContext context){
return BlocProvider<RegistrationSendCodeCubit>(
create: (context) => RegistrationSendCodeCubit(),
child: RegistrationSendCodeBuilder(),
);
}
}
Builder -
class RegistrationSendCodeBuilder extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<RegistrationSendCodeCubit, RegistrationSendCodeState>(
builder: (context, state) {
if(state is RegistrationSendCodeNotLoading) {
RegistrationSendCodeWidget();
} else if(state is RegistrationSendCodeLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator(),),
);
} else if(state is RegistrationSendCodeLoaded) {
return FullName();
} else if(state is RegistrationSendCodeError) {
return MyError();
}
throw StateError('err');
},
);
}
}
error -
The following StateError was thrown building BlocBuilder<RegistrationSendCodeCubit, RegistrationSendCodeState>(dirty, dependencies: [_InheritedProviderScope<RegistrationSendCodeCubit?>], state: _BlocBuilderBaseState<RegistrationSendCodeCubit, RegistrationSendCodeState>#cd448):
Bad state: err
The relevant error-causing widget was:
BlocBuilder<RegistrationSendCodeCubit, RegistrationSendCodeState> BlocBuilder: return BlocProvider<RegistrationSendCodeCubit>(
It is expected that when I go to this screen, I should immediately get into the RegistrationSendCodeNotLoading state, which is logical
You are throwing the error, But you are not catching it anywhere
Try removing the line.
class RegistrationSendCodeBuilder extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<RegistrationSendCodeCubit, RegistrationSendCodeState>(
builder: (context, state) {
if(state is RegistrationSendCodeNotLoading) {
return RegistrationSendCodeWidget(); 👈 add return
} else if(state is RegistrationSendCodeLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator(),),
);
} else if(state is RegistrationSendCodeLoaded) {
return FullName();
} else if(state is RegistrationSendCodeError) {
return MyError();
}
throw StateError('err'); // ❌ remove this line
},
);
}
}
Builder parameter must return a Widget to display for all states. In RegistrationSendCodeNotLoading state, you're not returning the widget.
It common approach to have else statement instead of else if for RegistrationSendCodeError state without exclusively checking it with condition.
Change code as below,
class RegistrationSendCodeBuilder extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<RegistrationSendCodeCubit, RegistrationSendCodeState>(
builder: (context, state) {
if(state is RegistrationSendCodeNotLoading) {
return RegistrationSendCodeWidget();
} else if(state is RegistrationSendCodeLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator(),),
);
} else if(state is RegistrationSendCodeLoaded) {
return FullName();
} else {
return MyError();
}
},
);
}
}

Run bloc only when list is empty

Why my function in initState - the loop doesnt work?
I mean that I just wanna run function
_newsBloc.add(GetCompanyList());
Only in first time app run when list of users is empty.
I just wanna make it because when I'm changing activities to other I have to wait again to fetch data from my api, but i dont want it.
If it ask Api 1 time I do not need it anymore.
Then I'm checkig length of list in init state it always return 0.
Any ideas?
This is my code:
class _CompanyActivityState extends State<CompanyActivity> with SingleTickerProviderStateMixin {
List<Company> _users = [];
final CompanyBloc _newsBloc = CompanyBloc();
#override
void initState() {
super.initState();
if(_users.length < 0 ) {
_newsBloc.add(GetCompanyList());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: NavigationDrawerWidget(),
appBar: buildAppBar(),
floatingActionButton: Stack(
children: <Widget>[
Stack(
body: createListViewWidgets());
}
Widget createListViewWidgets() {
return Container(
margin: EdgeInsets.all(8.0),
child: BlocProvider(
create: (_) => _newsBloc,
child: BlocListener<CompanyBloc, CompanyState>(
listener: (context, state) {
if (state is CompanyError) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(state.message),
));
}
},
child: BlocBuilder<CompanyBloc, CompanyState>(
builder: (context, state) {
if (state is CompanyInitial) {
return _buildLoading();
} else if (state is CompanyLoading) {
return _buildLoading();
} else if (state is CompanyLoaded) {
_users = state.company;
return _listViewCard(context, state.company);
} else if (state is CompanyError) {
return Container();
} else {
return Container();
}
},
),
),
),
);
}
}
Bloc which works;
class CompanyBloc extends Bloc<CompanyEvent, CompanyState> {
List<Company> mList = [];
final ApiRepository _apiRepository = ApiRepository();
CompanyBloc() : super(CompanyInitial()) {
on<GetCompanyList>((event, emit) async {
try {
emit(CompanyLoading());
mList.addAll(await _apiRepository.fetchCompanyList());
emit(CompanyLoaded(mList));
if (mList.length == 0) {
emit(CompanyError('List is empty'));
}
} on NetworkError {
emit(CompanyError("Failed to fetch data. is your device online?"));
}
});
}
}
And state which is used to create _users list:
class CompanyLoaded extends CompanyState {
final List<Company> company;
const CompanyLoaded(this.company);
#override
List<Object> get props => throw UnimplementedError();
}

Implement verification user exist

I would like to check if the user has already filled in the registration form:
Here is my code for the connectionState:
class LandingPage extends StatelessWidget {
// final Geolocator _geolocator = Geolocator()..forceAndroidLocationManager;
#override
Widget build(BuildContext context) {
final auth = Provider.of<AuthBase>(context, listen: false);
return StreamBuilder<User>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
User user = snapshot.data;
if (user == null) {
return SignInPage();
} else {
// _geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best)
MatchEngine.instance.initialise(user.uid);
return Chat();
}
} else {
return Scaffold(
body: MyAppsCircularProgressIndicator(title: "MyApp",),
);
}
},
);
}
}
this code works fine for connectionstate.
I would like to add in the first code:
if (not signed in) {
show sign in page
} else {
if (not registered)
show register page
else
show home page
}
or
StreamBuilder(
stream: auth.authStateChanges()
builder: (_, snapshot) {
// check connectionState too
if (snapshot.hasData) {
StreamBuilder(
stream: database.userData() // this is a stream you create that reads from `userData/$uid` or similar
builder: (_, snapshot) {
if (snapshot.hasData) {
return HomePage()
} else {
return RegisterPage()
}
}
)
} else {
return SignInPage()
}
}
)
I would like to add the last code to the previous one to have my connectionstate + my redirection to RegisterPage.
I tried everything but to no avail ... could someone help me? Thank you
You could use the provider package and then create a seperate file which has the following code. I personally use this and it works well.
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user == null) {
return SignIn();
} else {
return Dashboard();
}
}
}
and in your main.dart file where you are building the material app. Put the wrapper (or whatever you name it) widget instead such as the following.
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return FutureBuilder(
// Initialize FlutterFire
future: Firebase.initializeApp(),
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return ErrorPage();
}
// Show Application
if (snapshot.connectionState == ConnectionState.done) {
return StreamProvider<Help4YouUser>.value(
value: AuthService().user,
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Wrapper(),
),
);
}
// Initialization
return LoadingWidget();
},
);
}
}
Any clarification needed please comment

Checking bloc state with blocbuilder

I'm fetching a list of items from an API and building them using the BlocBuilder. It works and the widget list is built but when I print out to check which part of the state is being executed, I get as shown below. Why does 'Nothing' appeared ?
ProductInitial
fetching product //from bloc when fetching api
Nothing
fetching complete //from bloc after fetching api
ProductSuccess
Main
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<ProductBloc>(
create: (BuildContext context) => ProductBloc()..add(FetchProduct())
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: MainScreen(),
)
);
}
}
List Screen
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: EdgeInsets.all(10.0),
child: BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
if(state is ProductInitial){
print('ProductInitial');
return buildLoadingWidget();
}
if(state is ProductSuccess){
print('ProductSuccess');
return _buildProductListWidget(state.products);
}
if(state is ProductFailed){
print('ProductFailed');
return Center(
child: Text('Something went wrong'),
);
}
print('Nothing');
return buildLoadingWidget();
}
)
)
);
}
Update
Added the bloc code for reference.
Bloc
class ProductBloc extends Bloc<ProductEvent, ProductState> {
ProductBloc() : super(ProductInitial());
ProductRepository _repository = ProductRepository();
#override
Stream<ProductState> mapEventToState(ProductEvent event,) async* {
if(event is FetchProduct){
yield ProductLoading();
try{
print('fetching product');
final List<ProductModel> products = await _repository.getProducts();
yield ProductSuccess(products);
print('fetching complete');
}catch(e){
yield ProductFailed();
print(e);
print('fetching failed');
}
}
}
}
Update your bloc with the below code:
BLOC
class ProductBloc extends Bloc<ProductEvent, ProductState> {
ProductBloc() : super(ProductInitial());
ProductRepository _repository = ProductRepository();
#override
Stream<ProductState> mapEventToState(ProductEvent event,) async* {
if(event is FetchProduct){
yield ProductInitial();
try{
print('fetching product');
final List<ProductModel> products = await _repository.getProducts();
yield ProductSuccess(products);
print('fetching complete');
}catch(e){
yield ProductFailed();
print(e);
print('fetching failed');
}
}
}
}
As in your code, you are yield ProductLoading state but didn't handle that state in your BlocBuilder that's why it bypasses all if statement and prints Nothing.
So another way is handle ProductLoading in your block build as shown below
List Screen
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: EdgeInsets.all(10.0),
child: BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
if(state is ProductInitial){
print('ProductInitial');
return buildLoadingWidget();
}
if(state is ProductLoading){
print('ProductLoading');
return buildLoadingWidget();
}
if(state is ProductSuccess){
print('ProductSuccess');
return _buildProductListWidget(state.products);
}
if(state is ProductFailed){
print('ProductFailed');
return Center(
child: Text('Something went wrong'),
);
}
print('Nothing');
return buildLoadingWidget();
}
)
)
);
}

Create stream builder

Get this errors:
1.A value of type 'Stream' can't be returned from function 'stream' because it has a return type of 'Stream'. (return_of_invalid_type at blahblah)
2.The argument type 'Stream' can't be assigned to the parameter type 'Stream'. (argument_type_not_assignable at blahblah)
why?
here is code to create stream builder base on this video from flutter team
void main() {
runApp(MyApp());
//listen to subscribe to stream
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Center(
child: Stream(),
),
),
);
}
}
class Stream extends StatefulWidget {
#override
_StreamState createState() => _StreamState();
}
class _StreamState extends State<Stream> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
//Error number 2
stream: NumberCreator().stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('done');
} else if (snapshot.hasError) {
return Text('Error!');
} else {
return Text(snapshot.data);
}
},
);
}
}
class NumberCreator {
NumberCreator() {
Timer.periodic(Duration(seconds: 1), (timer) {
//add count to stream
_controller.sink.add(_count);
_count++;
});
}
var _count = 1;
final _controller = StreamController<int>();
//error number 1
Stream<int> get stream => _controller.stream;
dispose() {
_controller.close();
}
}
and Do we need statefulWidget to create stramBuilder?
The name of your widget class is causing problem because
class Stream extends StatefulWidget
is causing conflict with the name used by StreamBuildWidget parameter
stream: NumberCreator().stream,
So Try Changing your widgetName to something like StreamPage etc.
and also change
return Text(snapshot.data);
to
return Text(snapshot.data.toString());
even this is throwing error Text widget expects the String not int
Corrected Version of code
class StreamBuilderPage extends StatefulWidget {
#override
_StreamBuilderPageState createState() => _StreamBuilderPageState();
}
class _StreamBuilderPageState extends State<StreamBuilderPage> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
//Error number 2
stream: NumberCreator().stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('done');
} else if (snapshot.hasError) {
return Text('Error!');
} else {
return Text(snapshot.data.toString());
}
},
);
}
}