Why BlocBuilder is stuck in the initial state while using get_it? - flutter

I'm using flutter_bloc to manage the states of my app, and get_it to inject the needed dependencies following the idea suggested by the Reso Coder's Flutter Clean Architecture Proposal.
Everything is working fine except that the bloc is not changing its state (it's stuck in the initial state)
Here is the code of the involved classes:
The States
abstract class PaintingsState extends Equatable {
final properties = const <dynamic>[];
PaintingsState([properties]);
#override
List<Object> get props => [properties];
}
class PaintingsLoading extends PaintingsState {}
class PaintingsLoaded extends PaintingsState {
final PaintingCardItems cardItems;
PaintingsLoaded({#required this.cardItems}) : super([cardItems]);
}
class Error extends PaintingsState {
final String message;
Error({#required this.message}) : super([message]);
}
The Events
abstract class PaintingsEvent extends Equatable {
const PaintingsEvent();
#override
List<Object> get props => [];
}
/// Tells the bloc that it needs to load the paintings from the PaintingsRepository
class GetPaintings extends PaintingsEvent {}
The Bloc
const String FILE_NOT_FOUND_MESSAGE = 'FileNotFound Failure';
class PaintingsBloc extends Bloc<PaintingsEvent, PaintingsState> {
final GetPaintingCardItems getCardItems;
PaintingsBloc({#required this.getCardItems}) : super(PaintingsLoading());
#override
Stream<PaintingsState> mapEventToState(PaintingsEvent event) async* {
if (event is GetPaintings) {
yield* _mapGetPaintingsToState();
}
}
Stream<PaintingsState> _mapGetPaintingsToState() async* {
yield PaintingsLoading();
final failureOrPaintingCardItems = await getCardItems(NoParams());
yield failureOrPaintingCardItems.fold(
(failure) => Error(message: _mapFailureToMessage(failure)),
(paintingCardItems) => PaintingsLoaded(cardItems: paintingCardItems));
}
String _mapFailureToMessage(Failure failure) {
switch (failure.runtimeType) {
case FileNotFound:
return FILE_NOT_FOUND_MESSAGE;
default:
return 'Unexpected error';
}
}
}
Dependencies injection
/// Ambient variable to access the service locator
final sl = GetIt.instance;
/// Set up all the objects you want to access later through the service locator [sl]
void setUpServiceLocator() {
initFeatures();
}
void initFeatures() {
//! Features - Paintings
// Bloc
sl.registerLazySingleton<PaintingsBloc>(() => PaintingsBloc(getCardItems: sl<GetPaintingCardItems>()));
// Use cases
sl.registerLazySingleton<GetPaintingCardItems>(() => GetPaintingCardItems(sl<PaintingsRepository>()));
// Repository
sl.registerLazySingleton<PaintingsRepository>(
() => PaintingsRepositoryImpl(dataSource: sl<PaintingsDataSource>()));
// Data sources
sl.registerLazySingleton<PaintingsDataSource>(() => PaintingsDataSourceImpl());
}
main.dart
void main() {
// dependencies injection
setUpServiceLocator();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<PaintingsBloc>(
create: (_) => sl<PaintingsBloc>(),
child: MaterialApp(
title: 'My Paintings',
theme: appTheme,
initialRoute: '/',
onGenerateRoute: RouteGenerator.generateRoute,
),
);
}
}
Page where I use BlocBuilder
class PaintingsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
...
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Stack(
children: <Widget>[
SafeArea(
child: Column(
...
BlocBuilder<PaintingsBloc, PaintingsState>(
builder: (context, state) {
if(state is PaintingsLoading) {
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
} else if(state is PaintingsLoaded) {
List<PaintingCardItem> _list = state.cardItems.paintingCardItems;
return Expanded(
child: SizedBox(
child: _list.length != 0
? ListCardView(
cardItems: _list)
: Container(
child: Center(child: Text('Empty list'))),
),
);
} else if(state is Error){
return Container(
child: Center(child: Text(state.message)));
} else {
return Container(
child: Center(child: Text('Unknown Error')));
}
}
)
],
))
],
),
),
);
}
}
So, somehow the state of the bloc does not change from PaintingsLoading to either PaintingsLoaded or Error.
If someone can give me some idea to solve this problem, I will really appreciate it.

I solved it, I just needed to add the event to the bloc. So, my solution was to create another state called PaintingsInitialState like so:
The States
...
class PaintingsInitialState extends PaintingsState {}
...
Then in the Bloc, I just changed the constructor of the bloc.
PaintingsBloc({#required this.getCardItems}) : super(PaintingsInitialState());`
Finally, I added the following condition inside the builder parameter of the BlocBuilder.
if (state is PaintingsInitialState) {
_paintingsBloc.add(GetPaintings());
}
I think that the information provided in the offitial site of the bloc library can be useful to understand how to use bloc pattern and libraries properly - particularly Flutter Wheather Tutorial.

Related

How to create a singleton Stream that can be listened by a Widget which is used in Multiple areas

Here I'm using GetIt. I created a class contains stream and also mark this class as singleton, I created a widget that uses this stream. Problem is whenever I used this in multiple location it causes Bad state: Stream has already been listened to
Problem is
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
MaterialButton(
onPressed: () {
Dog().bark();
},
child: Text('Add'),
),
Lopez(),
Lopez(),
],
),
),
);
}
}
class Lopez extends StatelessWidget {
const Lopez({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: StreamBuilder(
stream: Dog().onBark,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.active:
{
if (snapshot.hasData) {
return Text(snapshot.data.toString());
}
}
}
},
),
);
}
}
class Dog {
var _barkController = StreamController();
static Dog _dog = Dog._();
factory Dog() {
return _dog;
}
Dog._();
Stream get onBark => _barkController.stream.asBroadcastStream();
void bark() {
_barkController.add("woof " + DateTime.now().toString());
}
}
Created Singleton of Dog class. Dog Singleton's stream is used in a Widget named Lopez, when I used this widget twice first call works but remaining calls got Bad State Error.
Edit: Used TSR's example for recreation of problem.
Make the stream broadcast mode.
This example will guide you
class Dog{
var _barkController = StreamController();
Stream get onBark => _barkController.stream.asBroadcastStream();
void bark(){
_barkController.add("woof");
}
}

UpdateValue in List Cubit Flutter

In app i am using Cubit. ItemData fetch from firestore. Everything works, but after added item in list and update value(name) in firestore, in list still old value. How to solve it?
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
BlocBuilder<ItemCubit, ItemState>(
cubit: ItemCubit(DataBase())..getItemData(item),
builder: (context, state) {
if (state is ItemData) {
return Column(
children: [
Text(state.item.name),
RaisedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TestPage1(
item: state.item,
)));
},
child: Text('showPage'),
),
RaisedButton(
onPressed: () {
context.bloc<TestCubit>().add(item);
},
)
],
);
}
return Container(
child: Text('error'),
);
},
)
],
),
);
}
}
for add item in list i am uisng another cubit
code:
class AddCubit extends Cubit<AddState> {
AddCubit() : super(AddInitial());
List<Item> items = List<Item>();
void addItem(Item item) {
items.add(item);
emit(LoadList(items));
}
}
This is bloc for retrieve list of items in TestPage1:
BlocBuilder<AddCubit, AddState>(builder: (context, state) {
if (state is LoadList) {
return Column(
children: state.items.toSet().map((item) {
return Card(
child: Text(item.name),
);
}).toList(),
);
}
})
state code:
class LoadList extends AddState {
final List<Item> items;
LoadList(this.items);
}
In flutter when you compare two objects of the same class, you will have always equality even if the values of them are different. Unless you will use equality method in your class.
Class code with equality method
import 'package:equatable/equatable.dart';
class LoadList extends AddState {
final List<Item> items;
LoadList(this.items);
#override
List<Object> get props => [items];
}
Second thing is the fact that u should use copy with and don't create new state for new value. It will come handy later and reduce the number of possible errors later on.
Whole code for state class
import 'package:equatable/equatable.dart';
class LoadList extends AddState {
final List<Item> items;
LoadList(this.items);
LoadList copyWith({
List<Item> items,
}) {
return LoadList(
items: items?? this.items,
);
}
#override
List<Object> get props => [items];
}
And then for your void function you should use:
void addItem(Item item) {
items.add(item);
emit(state.copyWith(items: items);
}
just FYI.
The above answer will not work 100% if you are trying to update your widget because List.add will update the state immediately (but won't call Bloc or Cubit due to how Equatable is comparing values).
So if you want to use List.add or List.remove, You simply need to execute setState somewhere.

Flutter BLoc pass parameters

I'm trying to pass parameters to a bloc event following the bloc pattern, I have found this article however my dart document couldn't find the dispatch (event) method.
Flutter BLoC - How to pass parameter to event?
How do I pass parameters to something like this
main.dart
this works
_counterBloc.add(Counter.increment);
But this doesn't
_counterBloc.add(Counter.increment(3));
bloc.dart
import 'package:bloc/bloc.dart';
enum CounterEvents { increment }
class CounterBloc extends Bloc<CounterEvents, int> {
#override
int get initialState => 0;
#override
Stream<int> mapEventToState(CounterEvents event) async* {
switch (event) {
case CounterEvents.increment:
print(event);
yield state + 1;
break;
}
}
}
you should write CounterEvent like below:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
enum EventStatus { INCREMENT, DECREMENT }
class CounterEvent {
final int value;
final EventStatus status;
const CounterEvent({#required this.value, #required this.status});
}
class CounterBLoC extends Bloc<CounterEvent, int> {
#override
int get initialState => 0;
#override
Stream<int> mapEventToState(event) async* {
if (event.status == EventStatus.INCREMENT) {
yield state + event.value;
} else if (event.status == EventStatus.DECREMENT) {
yield state - event.value;
}
}
}
and use them in the widget like below:
#override
Widget build(BuildContext context) {
final counterBloc = BlocProvider.of<CounterBLoC>(context);
return Scaffold(
body: Center(
child: BlocBuilder<CounterBLoC, int>(
builder: (ctx, state) {
return Text(
'count: $state',
style: TextStyle(fontSize: 28),
);
},
),
),
floatingActionButton: Align(
alignment: Alignment.bottomRight,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
onPressed: () {
counterBloc
.add(CounterEvent(value: 5, status: EventStatus.INCREMENT));
},
child: Icon(Icons.add_circle),
),
FloatingActionButton(
onPressed: () {
counterBloc
.add(CounterEvent(value: 5, status: EventStatus.DECREMENT));
},
child: Icon(Icons.remove_circle),
),
],
),
),
);
}
make sure to init your bloc in the main :
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: BlocProvider<CounterBLoC>(
create: (ctx) => CounterBLoC(),
child: TestBlocWidget(),
),
);
}
}
If you are trying to rebuild the Counter App using bloc patter,
Go through this article this may help.
https://bloclibrary.dev/#/fluttercountertutorial?id=counter-app
Consider making a custom event. Your solution should be something like this (haven't checked for bugs, but the paradigm is correct):
abstract class CounterEvent {}
class Increment extends CounterEvent {
int amount;
Increment({this.amount});
}
Then in your bloc:
class CounterBloc extends Bloc<CounterEvent, int> {
#override
int get initialState => 0;
#override
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) {
yield state + event.amount;
}
}
}

Unnecessary Widget Rebuilds While Using Selector (Provider) inside StreamBuilder

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

type 'Future<dynamic>' is not a subtype of type 'Widget'

I'm a new in Flutter.
I have a problem with a calling future method in constructor. I create method, that return a classes with widgets depends of selected item. The problem is that I need to call this method several times, the first time to build the body, the second time to update the body on tap. But I see error "type 'Future' is not a subtype of type 'Widget'" If I add the type of void instead Future, it will be executed once to create a body.
Code snippets:
class DataPageState extends State<DataPage> {
....
_tables() async {
if (selectedValue == "a") {
return DataA();
}
if (selectedValue == "b") {
return DataB();
}
if (selectedValue == "c") {
return DataC();
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(...
body: new Stack(children: <Widget>[
_tables(), //errors this //I need to call method this
... new Stack(children: <Widget>[
AnimatedContainer(...),
InkWell(onTap: () => setState(
() {
_tables(); //and this
},
),)])...}
You _tables() function should return some Widget. If you want to build Widget using some async call you can use FutureBuilder.
_tables() can not be async. you have to return Widget instead of Future<widget>.
Here is the demo of of how to add widget on click.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Home(),
);
}
}
class Home extends StatefulWidget {
Home({Key key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
Widget _add = Container();
test() {
_add = Text("datcdsvcdsvsvdjvkjdsvsa");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: Container(
child: Stack(
children: <Widget>[
RaisedButton(
color: Colors.amber,
child: Text("Press"),
onPressed: () {
setState(() {
test();
});
},
),
_add,
],
),
),
);
}
}
You probably should just edit the function _tables to make it synchronous.
like this:
Widget _tables() {
if (selectedValue == "a") {
return DataA();
}
if (selectedValue == "b") {
return DataB();
}
if (selectedValue == "c") {
return DataC();
}
}
Nowever, If you have some reason to make _tables asyncronous, then do this:
Tables is a type Future. You need a `futureBuilder` for this.
Stack(children: <Widget>[
FutureBuilder<Widget>(
future: _tables(),
builder: (BuildContext _, snapshot) {
if(snapshot.hasError) { // Error
return const MyErrorWidget(); // You will have to create this widget
} else if(!(snapshot.hasData)) { // Loading
return CircularProgressIndicator();
}/ Loaded without any errors
return snapshot.data; // The widget that was returned;
},
),
// the rest of the widgets in the Stack
]);
Now this won't solve the problem. You will have to add a return type to _tables().
so do this
Future<Widget> _tables() async {