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.
Related
I am using FutureBuilder in one of my widgets and it requires a future. I pass the future to the widget through its constructor. The problem is that while passing the future to the widget it gets automatically executed. Since the FutureBuilder accepts only a Future and not a Future Function() i am forced to initialize a variable which in turn calls the async function. But i don't know how to pass the Future without it getting executed.
Here is the complete working example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final icecreamSource = DataService.getIcecream();
final pizzaSource = DataService.getPizza();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Column(
children: [
MenuButton(label: 'Ice Cream', dataSource: icecreamSource),
MenuButton(label: 'Pizza', dataSource: pizzaSource),
]
),
),
),
);
}
}
class MenuButton extends StatelessWidget {
final String label;
final Future<String> dataSource;
const MenuButton({required this.label, required this.dataSource});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: ElevatedButton(
child: Text(label),
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (context) => AnotherPage(label: label, dataSource: dataSource)))
),
);
}
}
// Mock service to simulate async data sources
class DataService {
static Future<String> getIcecream() async {
print('Trying to get ice cream...');
return await Future.delayed(const Duration(seconds: 3), () => 'You got Ice Cream!');
}
static Future<String> getPizza() async {
print('Trying to get pizza...');
return await Future.delayed(const Duration(seconds: 2), () => 'Yay! You got Pizza!');
}
}
class AnotherPage extends StatefulWidget {
final String label;
final Future<String> dataSource;
const AnotherPage({required this.label, required this.dataSource});
#override
State<AnotherPage> createState() => _AnotherPageState();
}
class _AnotherPageState extends State<AnotherPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.label)),
body: Center(
child: FutureBuilder<String>(
future: widget.dataSource,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if(snapshot.hasData) {
return Text('${snapshot.data}');
} else if(snapshot.hasError) {
return Text('Error occurred ${snapshot.error}');
} else {
return Text('Fetching ${widget.label}, please wait...');
}
}
),
),
);
}
}
The intended behaviour is that when i press the "Ice Cream" or "Pizza" button on the main page, the widget/screen named "Another Page" should appear and the async request should get executed during which the loading message should be displayed. However, what is happening is that on loading the homepage, even before pressing any of the buttons, both the async requests are getting executed. On pressing any of the buttons, the loading message does not appear as the request is already completed so it directly shows the result, which is totally undesirable. I am now totally confused about Futures and Future Functions. Someone please help me out.
Instead of passing the Future you could pass the function itself which returns the Future. You can try this example here on DartPad.
You have to modify MyApp like this:
final icecreamSource = DataService.getIcecream; // No () as we want to store the function
final pizzaSource = DataService.getPizza; // Here aswell
In MenuButton and in AnotherPage we need:
final Future<String> Function() dataSource; // Instead of Future<String> dataSource
No we could pass the future directly to the FutureBuilder but it's bad practice to let the FutureBuilder execute the future directly as the build method gets called multiple times. Instead we have this:
class _AnotherPageState extends State<AnotherPage> {
late final Future<String> dataSource = widget.dataSource(); // Gets executed right here
...
}
Now we can pass this future to the future builder.
instead passing Future function, why you dont try pass a string ?
Remove all final Future<String> dataSource;. You dont need it.
you can use the label only.
.....
body: Center(
child: FutureBuilder<String>(
future: widget.label == 'Pizza'
? DataService.getPizza()
: DataService.getIcecream(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
....
i just test it in https://dartpad.dev . its work fine.
you dont have to make complex, if you can achive with simple way.
The used Getx Arguments are cleared after the showDialog method is executed.
_someMethod (BuildContext context) async {
print(Get.arguments['myVariable'].toString()); // Value is available at this stage
await showDialog(
context: context,
builder: (context) => new AlertDialog(
//Simple logic to select between two buttons
); // get some Confirmation to execute some logic
print(Get.arguments['myVariable'].toString()); // Variable is lost and an error is thrown
Also I would like to know how to use Getx to show snackbars without losing the previous arguments as above.
One way to do this is to duplicate the data into a variable inside the controller and make a use from it instead of directly using it from the Get.arguments, so when the widget tree rebuild, the state are kept.
Example
class MyController extends GetxController {
final myArgument = ''.obs;
#override
void onInit() {
myArgument(Get.arguments['myVariable'] as String);
super.onInit();
}
}
class MyView extends GetView<MyController> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: Center(child: Obx(() => Text(controller.myArgument()))),
),
);
}
}
UPDATE
Since you are looking for solution without page transition, another way to achieve that is to make a function in the Controller or directly assign in from the UI. Like so...
class MyController extends GetxController {
final myArgument = 'empty'.obs;
}
class MyView extends GetView<MyController> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: ElevatedButton(
onPressed: () => _someMethod(context),
child: Obx(() => Text(controller.myArgument())),
),
),
);
}
void _someMethod(BuildContext context) async {
// store it in the state.
controller.myArgument(Get.arguments['myVariable'] as String);
await showDialog(
context: context,
builder: (context) => new AlertDialog(...),
);
print(controller.myArgument()); // This should work
}
}
UPDATE 2 (If you don't use GetView)
class MyController extends GetxController {
final myArgument = 'empty'.obs;
}
class MyView extends StatelessWidget {
final controller = Get.put(MyController());
#override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: ElevatedButton(
onPressed: () => _someMethod(context),
child: Obx(() => Text(controller.myArgument())),
),
),
);
}
void _someMethod(BuildContext context) async {
// store it in the state.
controller.myArgument(Get.arguments['myVariable'] as String);
await showDialog(
context: context,
builder: (context) => new AlertDialog(...),
);
print(controller.myArgument()); // This should work
}
}
UPDATE 3 (NOT RECOMMENDED)
If you really really really want to avoid using Controller at any cost, you can assign it to a normal variable in a StatefulWidget, although I do not recommend this approach since it was considered bad practice and violates the goal of the framework itself and might confuse your team in the future.
class MyPage extends StatefulWidget {
const MyPage({ Key? key }) : super(key: key);
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
String _myArgument = 'empty';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Expanded(
child: ElevatedButton(
onPressed: () => _someMethod(context),
child: Text(_myArgument),
),
),
);
}
void _someMethod(BuildContext context) async {
// store it in the state.
setState(() {
_myArgument = Get.arguments['myVariable'] as String;
});
await showDialog(
context: context,
builder: (context) => new AlertDialog(...),
);
print(_myArgument); // This should work
}
}
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
I recognize FutureProvider can be used when a model will be given by a Future. It is needed to initialize the model by its init() async method, so I tried FutureProvider. However FutureProvider didn't give an instance of the model. It had given null.
Additionally while using ChangeNotifierProvider successfully updates UI, FutureProvider seems to not update UI when the notifyListeners() is called. Consumer gives same result. Is there anything to be fixed? Thank you.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MyModel extends ChangeNotifier {
int _point = 0;
int get point => _point;
set point(int value) {
if (_point != value) {
_point = value;
notifyListeners();
}
}
Future<void> init() async { /* Use await doSomething(); */ }
}
void main() => runApp(
FutureProvider<MyModel>(
create: (context) async {
final model = MyModel();
await model.init();
return Future.value(model);
},
lazy: false,
updateShouldNotify: (_, __) => true,
child: MyHomePage(),
),
);
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
final model = Provider.of<MyModel>(context);
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
'${model.point}',
style: Theme.of(context).textTheme.display1,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
model.point++;
},
child: Icon(Icons.add),
),
),
);
}
}
You can use ChangeNotifierProvider. It will update your UI whenever notifyListeners is called.
ChangeNotifierProvider(
create: (context) => MyModel()..init(),
child: MyHomePage(),
)
There is actually no need to call init if you are not doing anything inside your init method.
And if your MyModel class is only there for a single int value, you don't even need to use it. You can do something like:
ChangeNotifierProvider<ValueNotifier<int>>(
create: (context) => ValueNotifier<int>(0),
child: MyHomePage(),
);
And inside your HomePage
final counter = Provider.of<ValueNotifier<int>>(context);
You can use it inside your Text widget like:
Text('${counter.value}')
I created this code, what i want to happen is when i press on the button i want the piechart to re-render with the new values (which should be old values but the food value increased by 1)
I am using a piechart from pie_chart: 0.8.0 package.
Deposit is nothing but a pojo (String category and int deposit)
the bloc.dart contains a global instance of the bloc, a getter for the stream and initialization of a stream of type
Here's my code:
import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart';
import 'bloc.dart';
import 'Deposit.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'bloc Chart',
theme: ThemeData(
primarySwatch: Colors.blueGrey,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
Map<String, double> datamap = new Map();
#override
Widget build(BuildContext context) {
datamap.putIfAbsent("Food", () => 5);
datamap.putIfAbsent("transportation", () => 3);
return Scaffold(
appBar: AppBar(
title: Text("PieChart using blocs"),
),
body: Column(
children: <Widget>[
StreamBuilder<Deposit>(
stream: bloc.data, //A stream of Deposit data
builder: (context, snapshot) {
addDeposit(Deposit("Food", 1), datamap);
debugPrint("Value of food in map is: ${datamap["Food"]}");
return PieChart(dataMap: datamap);
}),
SizedBox.fromSize(
size: Size(20, 10),
),
RaisedButton(
onPressed: () {
bloc.add(Deposit("Food", 1)); //returns the stream.add
},
child: Icon(Icons.add),
),
],
),
);
}
void addDeposit(Deposit dep, Map<String, double> map) {
if (map.containsKey(dep.category)) {
map.update(dep.category, (value) => value + dep.price);
} else
map.putIfAbsent(dep.category, () => dep.price);
}
}
I think your problem is that the stream doesn't trigger new events. You don't have to close the stream to rebuild. I can't see anywhere in your code where you are triggering new events for the stream. Check below code to see a simple way how you can update a StatelessWidget using a StreamBuilder.
class CustomWidgetWithStream extends StatelessWidget {
final CustomBlock block = CustomBlock();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
StreamBuilder(
stream: block.stream,
builder: (context, stream) {
return Text("${stream.data.toString()}");
}),
RaisedButton(
onPressed: () {
block.incrementNumber();
},
child: Text("Increment"),
)
],
);
}
}
class CustomBlock {
num counter = 10;
final StreamController<num> _controller = StreamController();
Stream<num> get stream => _controller.stream;
CustomBlock() {
_controller.onListen = () {
_controller.add(counter); // triggered when the first subscriber is added
};
}
void incrementNumber() {
counter += 1;
_controller.add(counter); // ADD NEW EVENT TO THE STREAM
}
dispose() {
_controller.close();
}
}
Although this is a working code snippet, I would strongly suggest to change your widget from StatelessWidget to StatefulWidget, for two reasons:
* if you go "by the book", if a widget changes the content by itself, then it's not a StatelessWidget, a stateless widget only displays data that is given to it. In your case, the widget is handling the tap and then decides what to do next and how to update itself.
* if you are using streams, in a stateful widget you can safely close the stream, as you can see in the above code, there's no safe way to close the stream. If you don't close the stream, there might be unwanted behaviour or even crashes.
This is my bloc file
import 'package:rxdart/rxdart.dart';
import 'package:testing/Deposit.dart';
class Bloc{
final _data = new BehaviorSubject<Deposit>();
Stream<Deposit> get data => _data.stream;
Function(Deposit) get add => _data.sink.add;
void dispose(){
_data.close();
}
}
Bloc bloc = new Bloc();