Api call when open screen using Provider in Flutter - flutter

I am confusing when using provider. Some developers use FutureBuilder to fetch API when open app's screen as following
var provider = locator<AppIntroProvider>(); //GetIt Dependancy Infection
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: provider.getAppIntroItems(),
builder: (context, AsyncSnapshot<dynamic> snapshot) => snapshot
.data ==
null
? Container(
color: Colors.red,
) : MyDataListWidget(),
),
);
}
But I implemented API call in InitState and listen data using Consumer, Like this.
var provider = locator<AppIntroProvider>(); //GetIt DI
#override
void initState() {
provider.getAppIntroItems();
super.initState();
}
#override
Widget build(BuildContext context) {
//provider = Provider.of<AppIntroProvider>(context); //If I don't use GetIt DI
return Scaffold(
body: Consumer<AppIntroProvider>(
builder: (context, appInfoProvider, child) => appInfoProvider
.appIntroItemsDao ==
null
? Container(
color: Colors.red,
)
: MyDataListWidget(),
),
);
}
My Question is
What is the purpose of using FutureBuilder instead of using Consumer?
What is the different?
What is right and more efficient way to implement API call?

FutureBuilder is just a simple widget that handles some of the operations such as fetching the future in init state in the end you can use both of them.
I usually use FutureBuilder if I want to deal with different states such as Loading, Error, HasData it makes it easier to control them in the builder function
Consumer just allows you to access your state management data.
Also if you don't use FutureBuilder you have to maintain a loading state such as setting it to loading when you first start the request and then setting it to false after it's done. This can be useful if you want to use the loading state in somewhere

Related

How to call api once in futurebuilder

My application have different routes and I would like to know how to call my api with cubit just once when the user come for the first time on the screen and also not to re-call the api every time he returns to the screen already initialized.
my structure use bloC
and this is my profile page initialization class
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final user = context.read<AuthCubit>().state;
final bloc = context.read<ProfileCubit>();
return Scaffold(
body: FutureBuilder(
future: bloc.updateProfilePicture(user!.id),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return BlocBuilder<ProfileCubit, ProfilePicture?>(
buildWhen: (prev, curr) => prev != curr,
builder: (context, picture) {
return picture != null
? Profil(profilePicture: picture, updateIndex: updateIndex)
: Profil(updateIndex: updateIndex);
},
);
}
return Center(
child: CircularProgressIndicator(
color: Colors.orange,
),
);
},
),
);
}
There are many ways to solve this problem
1- easy (but not clean code) is to use boolean global varibal
like isApiReqursted with default value (false) and when call the api set it to true
2- you can cache the response in the repoistory or bloc and make the api method frst check if there are data if there isit does not need to make http request

Flutter - how to correctly create a button widget that has a spinner inside of it to isolate the setState call?

I have a reuable stateful widget that returns a button layout. The button text changes to a loading spinner when the network call is in progress and back to text when network request is completed.
I can pass a parameter showSpinner from outside the widget, but that requires to call setState outside of the widget, what leads to rebuilding of other widgets.
So I need to call setState from inside the button widget.
I am also passing a callback as a parameter into the button widget. Is there any way to isolate the spinner change state setting to inside of such a widget, so that it still is reusable?
The simplest and most concise solution does not require an additional library. Just use a ValueNotifier and a ValueListenableBuilder. This will also allow you to make the reusable button widget stateless and only rebuild the button's child (loading indicator/text).
In the buttons' parent instantiate the isLoading ValueNotifier and pass to your button widget's constructor.
final isLoading = ValueNotifier(false);
Then in your button widget, use a ValueListenableBuilder.
// disable the button while waiting for the network request
onPressed: isLoading.value
? null
: () async {
// updating the state is super easy!!
isLoading.value = true;
// TODO: make network request here
isLoading.value = false;
},
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: isLoading,
builder: (context, value, child) {
if (value) {
return CircularProgressIndicator();
} else {
return Text('Load Data');
}
},
);
}
You can use StreamBuilder to solve this problem.
First, we need to create a stream. Create a new file to store it, we'll name it banana_stream.dart, for example ;).
class BananaStream{
final _streamController = StreamController<bool>();
Stream<bool> get stream => _streamController.stream;
void dispose(){
_streamController.close();
}
void add(bool isLoading){
_streamController.sink.add(isLoading);
}
}
To access this, you should use Provider, so add a Provider as parent of the Widget that contain your reusable button.
Provider<BananaStream>(
create: (context) => BananaStream(),
dispose: (context, bloc) => bloc.dispose(),
child: YourWidget(),
),
Then add the StreamBuilder to your button widget:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<bool>(
stream: Provider.of<BananaStream>(context, listen:false),
initialData: false,
builder: (context, snapshot){
final isLoading = snapshot.data;
if(isLoading == false){
return YourButtonWithNoSpinner();
} else{
return YourButtonWithSpinner();
}
}
);
}
}
And to change isLoading outside, you can use this code:
final provider = Provider.of<BananaStream>(context, listen:false);
provider.add(true); //here is where you change the isLoading value
That's it!
Alternatively, you can use ValueNotifier or ChangeNotifier but i find it hard to implement.
I found the perfect solution for this and it is using the bloc pattern. With this package https://pub.dev/packages/flutter_bloc
The idea is that you create a BLOC or a CUBIT class. Cubit is just a simplified version of BLOC. (BLOC = business logic component).
Then you use the bloc class with BlocBuilder that streams out a Widget depending on what input you pass into it. And that leads to rebuilding only the needed button widget and not the all tree.
simplified examples in the flutter counter app:
// input is done like this
onPressed: () {
context.read<CounterCubit>().decrement();
}
// the widget that builds a widget depending on input
_counterTextBuilder() {
return BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
if (state.counterValue < 0){
return Text("negative value!",);
} else if (state.counterValue < 5){
return Text("OK: ${state.counterValue}",
);
} else {
return ElevatedButton(onPressed: (){}, child: const Text("RESET NOW!!!"));
}
},
);
}

How can I use Future builder with provider?

My main objective is to show a CircularProgressIndicator before I get a location address placename but I keep getting this error The getter 'placeName' was called on null..I did try to check the null value in futureBuilder but I believe my implementation is wrong. Could you please take a look ?
This is my AppData class
class AppData extends ChangeNotifier {
Address pickUpLocation;
void updatePickUpLocationAddress(Address pickUpAddress) {
pickUpLocation = pickUpAddress;
notifyListeners();
}
}
and this is the Address class
class Address {
String placeFormattedAddress;
dynamic placeName;
String placeId;
double latitude;
double longitude;
Address(
{this.placeFormattedAddress,
this.placeName,
this.placeId,
this.latitude,
this.longitude});
}
Now in my MainScreen I am using it like this but the error persisting.
#override
Widget build(BuildContext context) {
\\\
body: Stack(
\\\
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FutureBuilder (
future: Provider.of < AppData > (context)
.pickUpLocation
.placeName,
builder: (context, snapshot) {
if (snapshot.data == null) {
return CircularProgressIndicator();
} else {
return Text(
Provider.of < AppData > (context)
.pickUpLocation
.placeName,
style: TextStyle(fontSize: 12.0),
overflow: TextOverflow.ellipsis,
);
}
}),
],
),
)
There are a few things to be aware of.
Your Future... isn't really a Future. You're just evaluating a synchronous property of your AppData Object provided by your Provider... and that's really it. Therefore the FutureBuilder should immediately evaluate its expression without giving that "loading" experience (it should render the "loaded" widget after a first rebuild).
Say you're using a Provider to return the Future you're looking for (and that's ok), which is something close to your implementation: something like that is erroneous since it would "stress" your widget tree with handling two state changes (the provider changing and/or the future being completed). Furthermore, your future gets fired every time you rebuild this Widget: that's not good (usually, firing a Future multiple times means asking your backend for the same data multiple times).
To fix your situation you have to:
Let your getter properly return and handle a Future;
Initialize the call to your getter (i.e. the Future call) before you call the build method.
Here's how I'd change your provider model/class:
class AppData extends ChangeNotifier {
Address? pickUpLocation;
Future<void> updatePickUpLocationAddress() async {
// TODO: Maybe add error handling?
var pickUpLocation = await Future.delayed(Duration(seconds: 5)); // Request mock
notifyListeners();
}
}
Now, to initialize the Future you either do so in the above Provider (in the Constructor of AppData), or you have to change your Widget to be Stateful so that we can access to the initState() method, in which we can initialize the Future without worrying about multiple calls. In your (now Stateful) Widget:
var myFuture;
// ...
void initState() {
myFuture = Provider.of<AppData>(context).updatePickUpLocationAddress();
}
// ...
Widget build (BuildContext context) {
return // ...
FutureBuilder(
future: myFuture, // already initialized, won't re-initalize when build() is called
builder: (ctx, snapshot) => // ...
}
That should be it for this Provider + Future pattern, although a final consideration should be mentioned.
Take this made up example and look what happens (i.e. read the console) when you fire the tap some times: if ChildWidget1 is disposed, then myFuture will be fired again when it gets re-loaded in the tree. This happens because ChildWidget1 is entirely removed from the tree and then re-added again to it, i.e. it's disposed and re-initialized.
This is useful to remember because having a StatefulWidget doesn't mean we are "immune" from side effects like this one.
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (Provider.of < AppData > (context)
.pickUpLocation ==
null)
CircularProgressIndicator(),
if (Provider.of < AppData > (context)
.pickUpLocation !=
null)
Text(
Provider.of < AppData > (context)
.pickUpLocation
.placeName,
style: TextStyle(fontSize: 12.0),
overflow: TextOverflow.ellipsis,
),
),
)
For anyone who is facing this problem
You can easily click on Options+enter on Mac and click on "wrap with Builder"
then just pass the context to your future function
child: Builder(
builder: (context) {
return FutureBuilder(
future: futureFunctionHere(context),
builder: (context, AsyncSnapshot snapshot) {

Issue With Nested BlocBuilder() Calls

My Flutter app has multiple BloCs (via the bloc and flutter_bloc packages) which has caused some technical difficulties that I solved using a workaround, but I was wondering if there was a better solution out there.
I am using BlocBuilder() when listening to a bloc where each bloc has its own BlocBuilder() call. At first, I was nesting the BlocBuilder() calls as follows:
Widget build(BuildContext context) {
return BlocBuilder (
bloc: bloc1,
builder: (ctx, snapshot1) {
do_something1(snapshot1);
return BlocBuilder(ctx2, snapshot2) {
bloc: bloc2,
builder: (ctx2, snapshot2) {
do_something2(snapshot2);
return renderWidget();
}
}
}
);
}
The issue I have with this nested BlocBuilder() calls is that if data comes in for bloc1, the BlocBuilder() for bloc2 will be re-called, causing the current state for bloc2 to be re-read() and causing difficulties for do_something2() which ideally should be called only when there is new data for bloc2.
So what I did was to create a separate widget for each BlocBuilder() call, resulting with the following new build():
Widget build(BuildContext context) {
return Column(
children: [
WidgetBlocBuilder1(),
WidgetBlocBuilder2(),
renderWidget(),
],
);
}
What this does is that any data coming in for either bloc1 or bloc2 would be localized in WidgetBlocBuilder1() or WidgetBlocBuilder2() respectively and more importantly, incoming bloc data would NOT cause the BlocBuilder() for the other bloc to be re-called() as was the case in my nested BlocBuilder() approach.
Here is the build() for WidgetBlocBuilder1():

Widget build(BuildContext context) {
return BlocBuilder(
bloc: bloc,
builder: (ctx, snapshot) {
if (snapshot is CommandEditorSaveFile) {
_saveDocumentFile(ctx);
}
return Visibility(
child: Text('test'),
visible: false,
);
},
);
}
Note that WidgetBlocBuilder1() is an invisible widget as shown by the Visibility( visible:false) wrapper. This is because the widget itself should not render anything on the screen; the widget is simply a wrapper for the BlocBuilder() call. If incoming bloc data is supposed to change the visible state of the parent widget, then logic needs to be written to implement that.
This workaround seems to work, but I was wondering if there was a better solution out there.
Any suggestions would be greatly appreciated.
/Joselito
As per a suggestion from pskink, another solution is to use one StreamBuilder() to listen to multiple blocs. To do this, I used a package called multiple_streambuilder.
Here is a sample build() using this package:
Widget build(BuildContext context) {
return StreamBuilder2<int, int>(
streams: Tuple2(bloc1!.stream, bloc2!.stream),
builder: (contex, snapshot) {
if (snapshot.item1.hasData) {
print(snapshot.item1.data);
}
if (snapshot.item2.hasData) {
print(snapshot.item2.data);
}
return Scaffold(
appBar: AppBar(title: Text("test title")),
body: WidgetTest(),
);
},
);
}
In the build() above, I was listening to 2 blocs, both returning an int hence the call to StreamBuilder2<int,int>(). To find out which bloc has emitted data, you call snapshot.item1.hasdata or snapshot.item2.hasdata.

setState vs StreamProvider

I'm a Flutter newbie and I have a basic understanding question:
I built a google_maps widget where I get the location of the user and also would like to show the current location in a TextWidget.
So I'm calling a function in initState querying the stream of the geolocator package:
class _SimpleMapState extends State<SimpleMap> {
Position userPosStreamOutput;
void initPos() async {
userPosStream = geoService.getStreamLocation();
userPosStream.listen((event) {
print('event:$event');
setState(() {
userPosStreamOutput = event;
});
});
setState(() {});
}
#override
void initState() {
initPos();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold( //(very simplified)
body: Text(userPosStreamOutput.toString()),
This works just fine. But would it make sense to use a Streamprovider instead? Why?
Thanks
Joerg
An advantage to use StreamProvider is to optimize the process of re-rendering. As setState is called to update userPosStreamOutput value, the build method will be called each time your stream yields an event.
With a StreamProvider, you can apply the same logic of initializing a stream but then, you will use a Consumer which will listen to new events incoming and provide updated data to children widgets without triggering other build method calls.
Using this approach, the build method will be called once and it also makes code more readable in my opinion.
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamProvider(
create: (_) => geoService.getStreamLocation(),
initialData: null,
child: Consumer<Position>(
builder: (context, userPosStreamOutput, _) {
return Text(userPosStreamOutput.toString());
},
),
),
);
}