BlocBuilder not updating after cubit emit - flutter

UPDATE After finding the onChange override method it appears that the updated state is not being emitted #confused
UPDATE 2 Further debugging revealed that the StreamController appears to be closed when the updated state attempts to emit.
For some reason 1 of my BlocBuilders in my app refuses to redraw after a Cubit emit and for the life of me I cannot figure out why, there are no errors when running or debugging, the state data is being updated and passed into the emit.
Currently, it is a hook widget, but I have tried making it Stateless, Stateful, calling the cubit method in an effect, on context.bloc.
I have tried making it a consumer, nothing makes it into the listen, even tried messing with listenWhen and buildWhen and nothing is giving any indication as to why this is not building.
It renders the loading state and that is where it ends.
Widget:
class LandingView extends HookWidget {
#override
Widget build(BuildContext context) {
final hasError = useState<bool>(false);
return Scaffold(
key: const Key(LANDING_VIEW_KEY),
body: BlocProvider<CoreCubit>(
create: (_) => sl<CoreCubit>()..fetchDomainOptions(),
child: BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
switch (state.status) {
case CoreStatus.loaded:
return LandingViewLoaded();
case CoreStatus.error:
return LandingViewError();
case CoreStatus.loading:
default:
return AppIcon();
}
},
),
),
);
}
}
Cubit method:
Future<void> fetchDomainOptions() async {
final inputEither = await getDomainOptions();
return inputEither.fold(
_handleFailure,
(options) {
emit(state.copyWith(
domainOptions: options,
status: CoreStatus.loaded,
));
},
);
}
I have a few other widgets that work off freezed data classes and work of the same status key logic without any issues, I even went as far as trying to add a lastUpdated timestamp key onto it to make even more data change, but in the initial state domainOptions is null and status is CoreStatus.loading, which should already be enough to trigger a UI update.
TIA

I haven't figured out yet why exactly this happens but I believe we're dealing with some kind of race condition here.
Also, I don't know the proper solution to work around that but I have found that delaying calling emit() for a short amount of time prevents this issue from occurring.
void load() async {
try {
emit(LoadingState());
final vats = await provider.all();
await Future<void>.delayed(const Duration(milliseconds: 50));
emit(LoadedState(vats));
} catch (e) {
print(e.toString());
emit(ErrorState());
}
}
Add await Future<void>.delayed(const Duration(milliseconds: [whatever is needed])); before emitting your new state.
I came to this conclusion after reading about this issue on cubit's GitHub: Emitted state not received by CubitBuilder

i resolved my problem, i was using getIt.
i aded the parameter bloc.
my problem was not recibing the events (emit) of the cubit (bloc)
child: BlocBuilder<CoreCubit, CoreState>(
bloc: getIt<CoreCubit>(), // <- this
builder: (context, state) {
},
),

Related

Flutter bloc snackbar and yielding state twice

How to achieve showing snackbar without losing bloc state?
Suppose i have this widget class
Scaffold(
body: LoadingOverlay(
child: Container(
child: BlocConsumer<PostingBloc, PostingState>(
bloc: newBloc,
listener: (context, state) async {
if (state is PostingSuccess) {
//Show Snackbar
}
builder : (context, state){return Container(child:Text(state.text))}
}
then the bloc state is like this
class PostingState extends Equatable{
String text;
}
class PostingSuccess extends JobPostingState{
}
and the bloc mapEventToState is like this
PostingStateget initialState => PostingState();
Stream<PostingState> mapEventToState(
PostingEvent event,
) async* {
if (event is SuccessPost) {
yield PostingSuccess();
yield state;
}
Is it the correct way to implement snackbar on the bloc state management do i really need to yield twice to make the text still shown without the need to adding new variable like boolean showSuccess etc on my PostingState class.
Or is it the wrong way since there will be a time when the text is gone which is when the Posting Success emitted? I am quite confused on how to do this on bloc, any suggestion is really appreciated. thankyou
You can add your String inside your event and your state. After that:
if (event is SuccessPost) {
yield PostingSuccess(text: event.text);
}

Flutter/Dart - Photo changed but not the URL, therefore Provider's Consumer is not Refreshed

I'm using Provider with ChangeNotifier to alert a Consumer once a new photo is uploaded to a server replacing an old photo. The problem is that the URL stays the same as the photo is merely overwritten and keeps the same name. Hence the Consumer doesn't recognize that anything has changed and doesn't refresh the old photo with the new one.
How can I trick the ChangeNotifier into refreshing the URL? Heres' the Consumer in my build;
Consumer<SocialProvider>(
builder: (context, socialProvider, child) {
return Image.network(socialProvider.currentavatar,
);
}),
Here's where the image is chosen in the Gallery and uploaded to overwrite the old image on the server.
GestureDetector(
onTap: () async {
await socialProvider.loadCurrentUserId();
await _listener.openGallery(socialProvider.currentuserid);
String updatedavatar = "http://example.com/same_photo.jpg";
socialProvider.updateAvatar(updatedavatar);
},
And here's the code in the Provider with ChangeNotifier;
Future<void> updateAvatar(String avatar) async {
var box = await Hive.openBox('currentuser');
box.put('currentavatar', avatar);
currentavatar = avatar;
notifyListeners();
}
Any ideas how to trick Consumer into believing the url has changed so that it is refreshed?
I believe the Consumer will rebuild when someone call notifyListeners(). Your issue may be in Image.network(socialProvider.currentavatar,) that flutter reuse same render object when everything is not change. You can try to add key:UniqueLey() to force rebuild the widget every time it build.
Update with some assumption.
Here is the simple code I try to rebuild your environment:
class SimpleProvider with ChangeNotifier {
Future<void> reload() async => notifyListeners();
}
class TestConsumer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider(
create: (context) => SimpleProvider(),
child: Consumer<SimpleProvider>(
builder: (_, simpleProvider, child) {
print('Consumer build');
return child; // Here is your Image?
},
child: Builder(
builder: (context) {
print('Builder build');
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<SimpleProvider>().reload();
},
),
body: Container(),
);
},
),
),
),
);
}
}
Every time I click the button, SimpleProvider will call the notifyListeners()
(Future is not necessary) and the builder function in Consumer will be called. Here come the questions:
Is the builder really called when you use notifyListeners?
I assume the builder function is called. You can still double check this part. Otherwise you must provide how you call it.
If builder is called, why the Image Widget inside it is not rebuilt?
It is part of flutter design that it reuse as much as it could, for better performance. Sometimes it confuse people that the rebuild is not happen. That's where I guess your problem is here. You can check the basic mechanism from video here:
https://www.youtube.com/watch?v=996ZgFRENMs&ab_channel=Flutter

How to create a dependency for ChangeNotifierProvider and make it wait to complete?

I have ChangeNotifierProvider object that uses data stored sqflite asset database which need to be loaded at the beginning as future. The problem is that ChangeNotifierProvider doesn't wait for future operation to complete. I tried to add a mechanism to make ChangeNotifierProvider wait but couldn't succeed. (tried FutureBuilder, FutureProvider, using all together etc...)
Note : FutureProvider solves waiting problem but it doesn't listen the object as ChangeNotifierProvider does. When I use them in multiprovider I had two different object instances...
All solutions that I found in StackOverflow or other sites don't give a general solution or approach for this particular problem. (or I couldn't find) I believe there must be a very basic solution/approach and decided to ask for your help. How can I implement a future to this code or how can I make ChangeNotifierProvider wait for future?
Here is my summary code;
class DataSource with ChangeNotifier {
int _myId;
List _myList;
int get myId => _myId;
List get myList => _myList;
void setMyId(int changeMyId) {
_myId = changeMyId;
notifyListeners();
}
.... same setter code for myList object.
DataSource(){initDatabase();}
Future<bool> initDatabase() {
.... fetching data from asset database. (this code works properly)
return true;
}
}
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<DataSource>(
create: (context) => DataSource(),
child: MaterialApp(
home: HomePage(),
),
);
}
}
Following code and widgets has this code part (it works fine)
return Consumer<DataSource>(
builder: (context, myDataSource, child) {.......
There are multiple ways that you can achieve. The main point of it is that you should stick to reactive principle rather than trying to await the change. Say for example, you could change the state of boolean value inside the DataSource class when the ajax request changes
class DataSource extends ChangeNotifier{
bool isDone = false;
Future<bool> initDatabase(){
//Do Whatever
isDone = true;
notifyListeners();
}
}
Then you could listen to this change in the build method like so
Widget build(BuildContext ctx){
bool isDone = Provider.of<DataSource>(context).isDone;
if(isDone){
// render result
}else{
// maybe render loading
}
}

Flutter Bloc: BlocBuilder not getting called after an update, ListView still displays old data

I'm using flutter_bloc for state management and landed on this issue. When updating a field and saving it, the BlocBuilder is not refreshing the page. It is working fine when Adding or Deleting. I'm not sure what I'm doing wrong here.
Even if I go to a different screen and returning to this screen it still displays the old data even though the file was updated.
I spent more than 2 hours trying to debug this to no avail. I tried initializing the updatedTodos = [] then adding each todo one by one, to see if that does something, but that didn't work either.
Any help here would be appreciated.
TodosBloc.dart:
Stream<TodosState> _mapUpdateTodoToState(
TodosLoaded currentState,
UpdateTodo event,
) async* {
if (currentState is TodosLoaded) {
final index = currentState.Todos
.indexWhere((todo) => event.todo.id == todo.id);
final List<TodoModel> updatedTodos =
List.from(currentState.todos)
..removeAt(index)
..insert(index, event.todo);
yield TodosLoaded(updatedTodos);
_saveTodos(updatedTodos);
}
}
todos_screen.dart:
...
Widget build(BuildContext context) {
return BlocBuilder(
bloc: _todosBloc,
builder: (BuildContext context, TodosState state) {
List<TodoModel> todos = const [];
String _strings = "";
if (state is TodosLoaded) {
todos = state.todos;
}
return Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (BuildContext ctnx, int index) {
return Dismissible(
key: Key(todo.toString()),
child: DetailCard(
todo: todos[index],
),
);
},
),
);
...
I'm expecting when the BlocBuilder to be called and refreshed the ListView.
I was able to resolve this with the help of Felix Angelov on github.
The problem is that I'm extending Equatable but not passing the props to the super class in the TodoModel class. I had to update the constructor of the TodoModel with a super([]).
This is the way i solved the issue , even though it could not be the best solution but i'll share it , when you are on the other screen where you are supposed to show data or something , upon pressing back button call dispose as shown below
#override
void initState() {
super.initState();
print("id" + widget.teamID);
BlocProvider.of<FootBallCubit>(context).getCurrentTeamInfo(widget.teamID);
}
// what i noticed upon closing this instance of screen , it deletes old data
#override
void dispose() {
super.dispose();
Navigator.pop(context);
}

Flutter StreamBuilder vs FutureBuilder

What is the main difference between StreamBuilder and FutureBuilder.
What to use and when to use?
What are the tasks they are intended to perform?
How each of them listens to changes in a dynamic list?
Both StreamBuilder and FutureBuilder have the same behavior: They listen to changes on their respective object. And trigger a new build when they are notified
of a new value.
So in the end, their differences are how the object they listen to works.
Future is like Promise in JS or Task in c#. They are the representation of an asynchronous request. Futures have one and only one response. A common usage of Future is to handle HTTP calls. What you can listen to on a Future is its state. Whether it's done, finished with success, or had an error. But that's it.
Stream on the other hand is like async Iterator in JS. This can be assimilated to a value that can change over time. It usually is the representation of web-sockets or events (such as clicks). By listening to a Stream you'll get each new value and also if the Stream had an error or completed.
How each of them listens to changes in a dynamic list?
A Future can't listen to a variable change. It's a one-time response. Instead, you'll need to use a Stream.
FutureBuilder is used for one time response, like taking an image from Camera, getting data once from native platform (like fetching device battery), getting file reference, making an http request etc.
On the other hand, StreamBuilder is used for fetching some data more than once, like listening for location update, playing a music, stopwatch, etc.
Here is full example mentioning both cases.
FutureBuilder solves a square value and returns the result after 5 seconds, till then we show progress indicator to the user.
StreamBuilder shows a stopwatch, incrementing _count value by 1 every second.
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _count = 0; // used by StreamBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildFutureBuilder(),
SizedBox(height: 24),
_buildStreamBuilder(),
],
),
);
}
// constructing FutureBuilder
Widget _buildFutureBuilder() {
return Center(
child: FutureBuilder<int>(
future: _calculateSquare(10),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done)
return Text("Square = ${snapshot.data}");
return CircularProgressIndicator();
},
),
);
}
// used by FutureBuilder
Future<int> _calculateSquare(int num) async {
await Future.delayed(Duration(seconds: 5));
return num * num;
}
// constructing StreamBuilder
Widget _buildStreamBuilder() {
return Center(
child: StreamBuilder<int>(
stream: _stopwatch(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active)
return Text("Stopwatch = ${snapshot.data}");
return CircularProgressIndicator();
},
),
);
}
// used by StreamBuilder
Stream<int> _stopwatch() async* {
while (true) {
await Future.delayed(Duration(seconds: 1));
yield _count++;
}
}
}
I find that sometimes real-world analogies work well for explaining / remembering concepts. Here's one - it's not perfect but it helps me.
Think that you are at one of those modern sushi restaurants where you have a belt going around the room with sushi boats on it. You just sit down and wait till one goes by, grab it and eat. But they also allow you to order carry out.
A Future is like the token with a number on it that they give you when you order takeout; you made the request, but the result is not yet ready but you have a placeholder. And when the result is ready, you get a callback (the digital board above the takeout counter shows your number or they shout it out) - you can now go in and grab your food (the result) to take out.
A Stream is like that belt carrying little sushi bowls. By sitting down at that table, you've "subscribed" to the stream. You don't know when the next sushi boat will arrive - but when the chef (message source) places it in the stream (belt), then the subscribers will receive it. The important thing to note is that they arrive asynchronously (you have no idea when the next boat/message will come) but they will arrive in sequence (i.e., if the chef puts three types of sushi on the belt, in some order -- you will see them come by you in that same order)
From a coding perspective -- both Futures and Streams help you deal with asynchrony (where things don't happen instantly, and you don't know when you will get a result after you make a request).
The difference is that Futures are about one-shot request/response (I ask, there is a delay, I get a notification that my Future is ready to collect, and I'm done!) whereas Streams are a continuous series of responses to a single request (I ask, there is a delay, then I keep getting responses until the stream dries up or I decide to close it and walk away).
Hope that helps.
FutureBuilder and StreamBuilder behave similarly: they listen for changes in their respective objects. In response to changing value notifications, a new build is triggered.
Ultimately, the difference lies in how they listen to async calls.
FutureBuilder
There is only one response to it. Futures are commonly used in http calls. The Future can be used to listen to the state, e.g., when it has completed fetching the data or had an error.
like as example link here.
StreamBuilder
As opposed to streams, which are iterators that can assimilate different values, which will change over time. Each new value is returned by Stream along with an error message or success message if it has any.
like as example link here.
Conclusion
The following data might help you understand the above better:
If your use case is to just get the data, and display it, like Total number of courses from a class from API. Then you can use FutureBuilder.
What if, the data updates every second or minute, while you use the app, like upcoming posts in a blog or increase comments on the blog or increase in likes on the blog. It updates asynchronously at certain interval, in that case StreamBuilder is the best option.
Bases upon the use case, you decide which one to use. Both of them are good in their own way.
Here is a full example mentioning both cases.
FutureBuilder solves a square value and returns the result after 5 seconds, till then we show a progress indicator to the user.
StreamBuilder shows a stopwatch, incrementing _count value by 1 every second.
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _count = 0; // used by StreamBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildFutureBuilder(),
SizedBox(height: 24),
_buildStreamBuilder(),
],
),
);
}
// constructing FutureBuilder
Widget _buildFutureBuilder() {
return Center(
child: FutureBuilder<int>(
future: _calculateSquare(10),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done)
return Text("Square = ${snapshot.data}");
return CircularProgressIndicator();
},
),
);
}
// used by FutureBuilder
Future<int> _calculateSquare(int num) async {
await Future.delayed(Duration(seconds: 5));
return num * num;
}
// constructing StreamBuilder
Widget _buildStreamBuilder() {
return Center(
child: StreamBuilder<int>(
stream: _stopwatch(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active)
return Text("Stopwatch = ${snapshot.data}");
return CircularProgressIndicator();
},
),
);
}
// used by StreamBuilder
Stream<int> _stopwatch() async* {
while (true) {
await Future.delayed(Duration(seconds: 1));
yield _count++;
}
}
}
Both StreamBuilder and FutureBuilder widgets in Flutter allow you to build reactive UIs that respond to asynchronous data changes. However, they have some differences in terms of their usage and the type of data they work with.
FutureBuilder widget is used when you want to asynchronously retrieve a single piece of data that will not change over time, such as a network request for user information. It expects a Future as its data source, and when the Future completes, it rebuilds the widget tree with the resulting data.
StreamBuilder widget, on the other hand, is used when you want to display data that can change over time, such as a real-time chat application. It expects a Stream as its data source, and whenever new data is available, it rebuilds the widget tree with the updated data.
Here are some other differences:
FutureBuilder has a single AsyncSnapshot that represents the current state of the Future, while StreamBuilder has multiple AsyncSnapshots, each representing a new piece of data emitted by the Stream.
FutureBuilder will execute the Future every time the widget is rebuilt, while StreamBuilder will only subscribe to the Stream once when the widget is mounted, and unsubscribe when the widget is disposed.
Here's an example of using FutureBuilder:
FutureBuilder<String>(
future: fetchData(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Text(snapshot.data);
} else {
return CircularProgressIndicator();
}
},
);
And here's an example of using StreamBuilder:
StreamBuilder<int>(
stream: countStream(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
return Text('Count: ${snapshot.data}');
} else {
return CircularProgressIndicator();
}
},
);
In summary, FutureBuilder is used for one-time asynchronous data retrieval, while StreamBuilder is used for displaying continuously updating data.