initializing variables before build in flutter? - flutter

I am trying to initialize a variable in the initstate. Before initializing null value is used in my scaffold. How can I wait for a variable then later load the build?I am using shared_preference plugin.
This is my initstate:
void initState() {
super.initState();
_createInterstitialAd();
Future.delayed(Duration.zero, () async {
prefs = await SharedPreferences.getInstance();
future = Provider.of<Titles>(context, listen: false).fetchAndSetPlaces();
identifier = await getimages();
});
Here I am checking if it is null and it is always null:
#override
Widget build(BuildContext context) {
print(prefs);

Why you want to initialize variable in init state .
Use Future Builder inside your context and fetch variable data first and then your build will execute.
class FutureDemoPage extends StatelessWidget {
Future<String> getData() {
return Future.delayed(Duration(seconds: 2), () {
return "I am data";
// throw Exception("Custom Error");
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('Future Demo Page'),
),
body: FutureBuilder(
builder: (ctx, snapshot) {
// Checking if future is resolved or not
if (snapshot.connectionState == ConnectionState.done) {
// If we got an error
if (snapshot.hasError) {
return Center(
child: Text(
'${snapshot.error} occured',
style: TextStyle(fontSize: 18),
),
);
// if we got our data
} else if (snapshot.hasData) {
// Extracting data from snapshot object
final data = snapshot.data as String;
return Center(
child: Text(
'$data',
style: TextStyle(fontSize: 18),
),
);
}
}
// Displaying LoadingSpinner to indicate waiting state
return Center(
child: CircularProgressIndicator(),
);
},
// Future that needs to be resolved
// inorder to display something on the Canvas
future: getData(),
),
),
);
}
}

If you want to initialize a variable later then we have late keyword for that purpose. You can use that like below when declaring a variable :
late String prefs;

First you have to understand the Life cycle of flutter.
I make it simple for you..
createState()
initState()
didChangeDependencies()
build()
didUpdateWidget()
setState()
dispose()
So Every override function have fixed functionality. For example initState can use only for normal initialisation. It can not hold/await the flow for several time. So that why any async method is not applicable inside the initState.
For Solution you have to use FutureBuilder for awaiting data or before navigating to the next page initialise the preferences then proceeded.

i assume you have 3 different condition :
first wait data for prefences,
get future data from that prefences,
and render result.
try this: bloc pattern
its basically a Stream to different State and Logic part of app ui

Related

Flutter - How to pass a future to a widget without executing it?

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.

How to use FAB with FutureBuilder

What I would like to achieve: show a FAB only if a webpage responds with status 200.
Here are the necessary parts of my code, I use the async method to check the webpage:
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
late Future<Widget> futureWidget;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
void initState() {
super.initState();
futureWidget = _getFAB();
}
Future<Widget> _getFAB() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// return something to create FAB
return const Text('something');
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load url');
}
}
And with the following FutureBuilder I am able to get the result if the snapshot has data:
body: Center(
child: FutureBuilder<Widget>(
future: futureWidget,
builder: (context, snapshot) {
if (snapshot.hasData) {
return FloatingActionButton(
backgroundColor: Colors.deepOrange[800],
child: Icon(Icons.add_shopping_cart),
onPressed:
null); // navigate to webview, will be created later
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
)
My problem is that I want to use it here, as a floatingActionButton widget:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
[further coding...]
),
body: // Indexed Stack to keep data
IndexedStack(
index: _selectedIndex,
children: _pages,
),
floatingActionButton: _getFAB(),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>
[further coding...]
But in this case Flutter is throwing the error
The argument type 'Future' can't be assigned to the parameter type 'Widget?'.
Sure, because I am not using the FutureBuilder this way. But when I use FutureBuilder like in the coding above then Flutter expects further positional arguments like column for example. This ends in a completely different view as the FAB is not placed over the indexedstack in the typical FAB position anymore.
I have searched for several hours for a similar question but found nothing. Maybe my code is too complicated but Flutter is still new to me. It would be great if someone could help me :)
You can use the just _getFAB() method to do it. You can't assign _getFab() method's return value to any widget since it has a return type Future. And also, when you are trying to return FAB from the FutureBuilder it will return FAB inside the Scaffold body.
So, I would suggest you fetch the data from the _getFAB() method and assign those data to a class level variable. It could be bool, map or model class etc. You have to place conditional statements in the widget tree to populate the state before the data fetching and after the data fetching. Then call setState((){}) and it will rebuild the widget tree with new data. Below is an simple example.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class FabFuture extends StatefulWidget {
const FabFuture({Key? key}) : super(key: key);
#override
State<FabFuture> createState() => _FabFutureState();
}
class _FabFutureState extends State<FabFuture> {
bool isDataLoaded = false;
#override
void initState() {
super.initState();
_getFAB();
}
Future<void> _getFAB() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
if (response.statusCode == 200) {
isDataLoaded = true;
setState(() {});
} else {
isDataLoaded = false;
//TODO: handle error
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: const Center(
child: Text('Implemet body here'),
),
floatingActionButton: isDataLoaded
? FloatingActionButton(
backgroundColor: Colors.deepOrange[800],
child: const Icon(Icons.add_shopping_cart),
onPressed: null)
: const SizedBox(),
);
}
}
Here I used a simple bool value to determine if I should show the FAB or not. The FAB will only show after the data is successfully fetched.
After practicing these ways and you get confident about them, I would like to suggest learning state management solutions to handle these types of works.

Non-nullable instance field ''" must be initialized

I am learning flutter and trying to integrate API with it and I am getting some kind of error can anyone look and told what I am doing wrong here.
class _HomePageState extends State<HomePage> {
Future<News> _newsModel; //getting error here 1*
#override
Future<void> initState() async {
super.initState();
var _newsModel = await API_Manager().getNews();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("News App"),
),
body: Container(
child: FutureBuilder(
future: _newsModel,
builder: (context, snapshot) {
if (snapshot.hasData) {
return (ListView.builder(itemBuilder: (context, index) {
return Container(
height: 100,
color: Colors.red,
);
}));
} else {
return CircularProgressIndicator();
}
},
),
),
);
}
}
1*) compiler is giving this error "Non-nullable instance field '_newsModel' must be initialized.
Try adding an initializer expression, or a generative constructor that initializes it, or mark it 'late'".
I have also tried by giving the late the error goes from the Editor but when the app runs it comes again. I am trying to get some news from the API and show them in the UI
The error indicates that the _newsModel field has not been initialized. if you want to initialize the field later you must mark the field with the late keyword.
From your code, you are not initialize the field, but you declare new variable named _newsModel on initState. Try removing var keyword
Future<void> initState() async {
super.initState();
_newsModel = await API_Manager().getNews();
}

Update List Item After Delete Action in Flutter

Hello I am new to flutter and I have a problem to update the list after executing a deleting an item from the database. Some said to use setState, but I still don't know how to implement it in my code. Tried to call seState right after the delete action, but still nothing happened. Still have some trouble to understand which component to update in Flutter. Thank you.
class ProfileView extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _ProfileViewState();
}
}
class _ProfileViewState extends State<ProfileView> {
late Future<List<Patient>> _patients;
late PatientService patientService;
#override
void initState() {
super.initState();
patientService = PatientService();
_patients = patientService.getPatient();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Profile')),
body: Column(
children: <Widget>[
Flexible(
child: SizedBox(
child: FutureBuilder<List<Patient>>(
future: _patients,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.hasError) {
print(snapshot);
return Center(
child: Text("Error"),
);
} else if (snapshot.hasData){
List<Patient> patients = snapshot.data;
return _buildListView(patients);
} else {
return Center(
child: Container(),
);
}
},
),
),
)
],
),
);
}
Widget _buildListView(List<Patient> patients) {
return ListView.separated(
separatorBuilder: (BuildContext context, int i) => Divider(color: Colors.grey[400]),
itemCount: patients.length,
itemBuilder: (context, index) {
Patient patient = patients[index];
return Row(
children: <Widget>[
Flexible(
child: SizedBox(
child: ListTile(
title: Text(patient.firstName),
subtitle: Text(patient.phone),
trailing: IconButton(
icon: new Icon(const IconData(0xf4c4, fontFamily: 'Roboto'), size: 48.0, color: Colors.red),
onPressed: () {
patientService.deletePatient(patient.id.toString());
}),
),
)
)
],
);
}
);
}
}
You can achieve that by removing the initialization of the future from the initState and simply give the future of patientService.getPatient() to the FutureBuilder, like this:
future: patientService.getPatient()
And call setState after making sure the patient have been successfully deleted.
The explanation behind doing that is when you delete your patient from the DB, yes it is removed from there, but the UI didn't get an update about the list of patients after the delete. And the reason why calling setState in your case doesn't make a change is because you are assigning the future in initState which is called once and only once when the widget is initialized. So when you call setState the future won't be called again hence no new data is fetched.
So what I did is just remove the initialization of the future from initState and give it to the FutureBuilder, which will be rebuild whenever you call setState.
Even though this works, it isn't the ideal solution. Because you are rebuilding your whole widget every time a delete is made which is kinda of heavy considering the FutureBuilder, so what I suggest is checking out some state mangement solutions like Bloc or Mobx or even the Provider package (which isn't a state mangement according to its creator).
Hope that makes clear !
Happy coding !
call setState() inside the onPressed method.
onPressed: () {
patientService.deletePatient(patient.id.toString());
setState((){});
}),
If you are not saving a local copy of the list from which you are deleting an item then this works
Although if the delete method deletes on from wherever you are fetching the items then you will need to call
_patients = patientService.getPatient();
before calling setState()
I think your deletePatient is asynchronous method. And you are calling this method without await and after this function setState is called thus widget is getting updated before delete is completed.
If deletePatient is asynchronous then add await before calling it and add setState after it.
onPressed: () async {
await patientService.deletePatient(patient.id.toString());
setState((){});
})

When to use FutureBuilder in Flutter

I would like to know when to use a FutureBuilder and how is it usefull, given the fact that a widget can be built multiple times during its life time and not only when we setState or update it, so instead of using below code:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool done = false;
#override
void initState() {
wait();
super.initState();
}
Future<void> wait() async {
await Future.delayed(Duration(seconds: 2));
setState(() {
done = true;
});
}
#override
Widget build(BuildContext context) {
print('is built');
return done ? Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: Icon(Icons.add),
),
) : Scaffold(body: CircularProgressIndicator(),);
}
}
In which cases would a FutureBuilder work for me instead of the above set up, given the fact that I would also want to optimize my app for less backend reads (in FutureBuilder I would read more than once). I am looking for a case where FutureBuilder would be more usefull and correct than the above setup.
FutureBuilder is used to handle "asynchronous" calls that return the data you want to display.
For example, let's say we have to get a List<String> from the server.
The API call :
Future<List<String>> getStringList() async {
try {
return Future.delayed(Duration(seconds: 1)).then(
(value) => ['data1', 'data2', 'data3', 'data4'],
);
} catch (e) {
throw Exception(e);
}
}
How can we handle the above API call status (load, data, error...etc) using FutureBuilder:
FutureBuilder<List<String>?>(
future: getStringList(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
if (snapshot.hasError)
return Text(snapshot.error.toString());
else
return ListView(
children: snapshot.data!.map((e) => Text(e)).toList(),
);
default:
return Text('Unhandle State');
}
},
),
As we can see, we don't have to create a state class with variable isLoading bool and String for error...etc. FutureBuilder saves us time.
BUT
Since FutureBuilder is a widget and lives in the widget tree, rebuilding can make FutureBuilder run again.
So I would say you can use FutureBuilder when you know there won't be a rebuild, however there are many ways (in the internet) to prevent FutureBuilder from being called again when the rebuild happens but it didn't work for me and leads to unexpected behavior.
Honestly I prefer handling the state in a different class with any state management solution than using FutureBuilder because it would be safer (rebuild wont effect it), more usable and easier to read (spreating business logic from UI).
FutureBuilder
Widget that builds itself based on the latest snapshot of interaction with a Future.
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder.
If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.A general guideline is to assume that every build method could get called every frame, and to treat omitted calls as an optimization.
Documentation is very great way to get started and understand what widget does what in what condition...
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Actually, you will never need to use FutureBuilder Widget if you don't want to. Your logic in your code do exactly what FutureBuilder Widget does if you optimise FutureBuilder Widget correctly.
This code is exactly same with yours:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool done = false;
late Future myFuture;
#override
void initState() {
myFuture = wait();
super.initState();
}
Future<bool> wait() async {
await Future.delayed(const Duration(seconds: 2));
return true;
}
#override
Widget build(BuildContext context) {
print('is built');
return FutureBuilder(
future: myFuture,
builder: (BuildContext context, snapshot) {
if(snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(body: CircularProgressIndicator(),);
} else {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
);
}
}
FutureBuilder is just a StatefulWidget whose state variable is _snapshot
Initial state is _snapshot = AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);
FutureBuilder is generally used to remove boilerplate code.
consider you want to fetch some data from the backend on page launch and show a loader until data comes.
It is subscribing to future which we send via the constructor and update the state based on that.
Tasks for FutureBuilder:
Give the async task in future of Future Builder
Based on connectionState, show message (loading, active(streams), done)
Based on data(snapshot.hasError), show view
Benefits of FutureBuilder
Does not use the two state variables and setState
Reactive programming (FutureBuilder will take care of updating the view on data arrival)
Example:
FutureBuilder<String>(
future: _fetchNetworkCall, // async work
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Text('Loading....');
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return Text('Result: ${snapshot.data}');
}
},
)
hence to conclude Future builder is wrapper/boilerplate of what we do typically, thus there should not be any performance impact.
FutureBuilder is a widget by Flutter which lets you easily determine the current state of the Future and choose what to show during that state.
you can refer this: https://walkingtree.tech/futurebuilder-flutter-widget/
One of the use cases is FutureBuilder update on a particular section inside the widget tree, not the full widget tree like you implement in the sample.
Future<void> wait() async {
return Future.delayed(Duration(seconds: 2));
}
Here setState() => update widget tree.
So same code in FutureBuilder you can change the UI value in a particular position in the widget tree.