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.
Related
I have CounterProvider mixin with ChangeNotifier and inside the class i have two counters (_counterOne and _counterTwo) when _counterOne is inremented all Consumers are executed hence Widgets that consume _counterOne and _counterTwo are executed. But I want only the _counterOne consumer widget to execute.
I couldn't found any salution to do that.
Thanks in advance.
Provider:
class CounterProvider with ChangeNotifier {
int _counterOne = 1;
int getCounterOne() => _counterOne;
void incrementCounterOne() {
_counterOne++;
notifyListeners();
}
int _counterTwo = 2;
int getCounterTwo() => _counterTwo;
void incrementCounterTwo() {
_counterTwo++;
notifyListeners();
}
}
View:
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
#override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
late CounterProvider _provider;
#override
void initState() {
_provider = Provider.of<CounterProvider>(context, listen: false);
super.initState();
}
#override
Widget build(BuildContext context) => Scaffold(
body: Column(
children: [
Consumer<CounterProvider>(builder: (context, value, child) {
print("Consumer of CounterOne executed");
return Text("CounterOne: ${value.getCounterOne()}");
}),
Consumer<CounterProvider>(builder: (context, value, child) {
print("Consumer of CounterTwo executed");
return Text("CounterTwo: ${value.getCounterTwo()}");
}),
// Buttons
ElevatedButton(
onPressed: () {
_provider.incrementCounterOne();
},
child: const Text("Increment CounterOne"),
),
ElevatedButton(
onPressed: () {
_provider.incrementCounterTwo();
},
child: const Text("Increment CounterTwo"),
),
],
));
}
according to how Provider works if you use Consumer on a Provider class it will listen to any changes on that class and update every time there is change, To solve this you can use Selector as explained in here.
But ValueListenableBuilder also works fine as suggested in the comments.
I am using ValueListenableBuilder to update my UI based on the data provided to it. I am initializing the ValueNotifier with value. But when I tried to read that value it returns nothing.
This is my Notifier class code:
class AppValueNotifier
{
ValueNotifier<List<Food>> valueNotifier = ValueNotifier([]);
void updateDealsList(List<Food> list)
{
valueNotifier.value=list;
print('DEAL LIST IN CLASS: ${ valueNotifier.value}');
}
List<Food> getDealList()
{
return valueNotifier.value;
}
}
In a separate widget I am initializing the value like this:
class HomeWidgetState extends State<HomeWidget> {
AppValueNotifier appValueNotifier = AppValueNotifier();
.
.
.
assignList(List<Food> dealList)
{
appValueNotifier.updateDealsList(dealList);
}
..
..
.
}
Now in another widget class I am building my UI with this data like this:
AppValueNotifier appValueNotifier = AppValueNotifier();
Widget buildList()
{
return ValueListenableBuilder(
valueListenable: appValueNotifier.valueNotifier,
builder: (context, List<Food> value, widget) {
print(
'DEAL LIST: ${appValueNotifier.getDealList()}');
return DealsWidget(
key: menuItemsKey,
updateList: (oldIndex, newIndex, newList) {},
currentMenu: value,
menuItemNodes: [],
changeCellColor: (color, catid) {},
);
},
);
}
But it is returning empty list instead. Not that list which is being initialized at the start.
Anyone help me what is the issue here:
Thanks in advance
You should be able to initialize your ValueNotifier list either in the constructor or based on an action (i.e. a button click, as shown below). Notice how I'm providing the AppValueNotifier service using the Provider pattern, and one widget triggers the action while a separate widget listens to the changes being made.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
Provider(
create: (context) => AppValueNotifier(),
child: MyApp()
)
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Column(
children: [
TriggerWidget(),
Expanded(
child: MyWidget(),
)
]
),
),
);
}
}
class Food {
final String name;
Food({ required this.name });
}
class AppValueNotifier
{
ValueNotifier<List<Food>> valueNotifier = ValueNotifier([]);
void updateDealsList(List<Food> list)
{
valueNotifier.value = list;
print('DEAL LIST IN CLASS: ${ valueNotifier.value}');
}
List<Food> getDealList()
{
return valueNotifier.value;
}
}
class TriggerWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
AppValueNotifier appValueNotifier = Provider.of<AppValueNotifier>(context, listen: false);
return TextButton(
child: const Text('Add Items!'),
onPressed: () {
appValueNotifier.updateDealsList([
Food(name: 'Food 1'),
Food(name: 'Food 2'),
]);
},
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
AppValueNotifier appValueNotifier = Provider.of<AppValueNotifier>(context, listen: false);
return ValueListenableBuilder(
valueListenable: appValueNotifier.valueNotifier,
builder: (context, List<Food> value, widget) {
var list = value;
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return Text(list[index].name);
}
);
},
);
}
}
You get this as output:
Also checkout this Gist you can run on DartPad.dev to check out how it works.
I am using futureprovider for getting response from api , but i want get the list from api and assign it to the stateprovider by using listprovider.state="data from api" , how to will it work ,
how to combine future provdier with the state provider .
UPDATED ANSWER
After a discussion with #31Carlton7, he's opened an issue on github, and after a discussion with Remi Rousselet (the creator of riverpod) we've reached a better solution for this problem.
(from the final solution on the issue)
Running the app:
main.dart
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const ProviderScope(
child: MaterialApp(
home: MyHomePage(),
),
);
}
}
Creating the foo class and provider:
foo.dart
part 'foo.g.dart';
class Foo {
final int bar;
int? baz;
Foo(
this.bar, {
this.baz,
});
}
#riverpod
class FooController extends _$FooController {
FooController(this.foo);
Foo foo;
#override
FutureOr<Foo> build() async {
foo = await getFoo();
return foo;
}
Future<Foo> getFoo() async {
await Future.delayed(const Duration(seconds: 1));
return Foo(1);
}
}
Implementation using Async capabilities:
home.dart
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Consumer(
builder: (context, ref, _) {
// Get the provider and watch it
final fooAsync = ref.watch(fooControllerProvider);
// Use .when to render UI from future
return fooAsync.when(
data: (foo) => Text('bar: ${foo.bar}, baz: ${foo.baz}'),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text(err.toString()),
);
},
),
);
}
}
Implementation using Notifier capabilities: home.dart
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Consumer(
builder: (context, ref, _) {
// Get Foo provider and set the state of it.
// Use it as if it were a State Provider.
ref.watch(fooControllerProvider.notifier).foo = Foo(3);
// Use Foo in UI (.requireValue is used to be able to listen to changes)
final foo = ref.watch(fooControllerProvider).requireValue;
// Use .when to render UI from future
return Text('bar: ${foo.bar}, baz: ${foo.baz}');
},
),
);
}
}
OLD ANSWER
This is a topic that I've been struggling with and thinking about a lot lately.
What I think is missing in Remi's answer, is the ability to convert the Future data to a maniputable data.
When you're recieving Future data using either a FutureProvider and implementing the ui using the when method OR using the FutureBuilder widget, they both will trigger a rebuild when the remote data is received, so if you try to assign the value to your StateProvider it will trigger a rebuild during another rebuild which will throw.
I currently have 2 workarounds for this, and I will be updating my answer as I get more info about this.
For this example, we'll have a future provider that will wait and then return a fake data:
final _futureCounterProv = FutureProvider(
(ref) async {
Future.delayed(
Duration(seconds: 3),
);
return Random().nextInt(100);
},
);
1. Future.microtask:
Future.microtask enables you to run an operation after the current rebuild ends.
You have to make sure that your StateProvider dependencies are in a Consumer below the Future.microtask call or the Future.microtask will be called on each state update, which will keep reseting the StateProvider's value to the future value
// this provider will provide the current value of the counter
final _counterProv = StateProvider((ref) => 0);
class Body extends ConsumerWidget {
const Body({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(_futureCounterProv).when(
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
error: (error, stackTrace) {
return Text(error.toString());
},
data: (data) {
Future.microtask(
() {
// Assigning the future value to the `StateProvider`
return ref.read(_counterProv.notifier).state = data;
},
);
return Consumer(
builder: (context, ref, _) {
final count = ref.watch(_counterProv);
return Column(
children: [
IconButton(
onPressed: () {
ref
.read(_counterProv.notifier)
.update((value) => value + 1);
},
icon: const Icon(Icons.add),
),
Text(
count.toString(),
),
],
);
},
);
},
);
}
}
2. ChangeNotifierProvider:
StateProvider has 2 options to update its value: the value setter and the update method, and they both trigger a rebuild. In this workaround we want to implement a state update that does not trigger rebuild. A way to do this is by using a ChangeNotifierProvider instead of StateProvider. By using a ChangeNotifierProvider we can control our own update actions and call notifyListeners (which will trigger a rebuild) whenever we want.
You have to make sure that your ChangeNotifierProvider dependencies are in a Consumer below the updateNoNotify call, or the ChangeNotifierProvider's will keep reseting to the future's value. Also you have to make sure that all the widgets that are consuming this ChangeNotifierProvider are in the widget tree below the updateNoNotify, or they will not be rebuilt as we're not triggering a rebuild
// the new `_counterProv`
final _counterProv = ChangeNotifierProvider(
(ref) => _CounterNotifier(),
);
class _CounterNotifier extends ChangeNotifier {
int _value = 0;
int get value => _value;
void update(int Function(int value) update) {
_value = update(_value);
// trigger a rebuild
notifyListeners();
}
void updateNoNotify(int Function(int value) update) {
_value = update(_value);
}
}
// the ui
class Body extends ConsumerWidget {
const Body({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(_futureCounterProv).when(
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
error: (error, stackTrace) {
return Text(error.toString());
},
data: (data) {
// calling `updateNoNotify` which does not trigger
// trigger rebuild as it does not call `notifyListeners`
ref.read(_counterProv.notifier).updateNoNotify(
(e) => data,
);
return Consumer(
builder: (context, ref, _) {
final count = ref.watch(_counterProv).value;
return Column(
children: [
IconButton(
onPressed: () {
ref.read(_counterProv.notifier).update(
(value) => value + 1,
);
},
icon: const Icon(Icons.add),
),
Text(
count.toString(),
),
],
);
},
);
},
);
}
}
These are not the safest workarounds, but they are workarounds, and I will be updating this answer once I find a safe way to do this.
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.
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