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();
}
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.
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.
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
I have a Listview.builder inside a FutureBuilder which taking some data from an http request.i have a bool closed i want to prevent some items from refreshing if status bool is true
how can I do that
You can achieve this by placing your call in initState. In this way you can make sure that it will get the data only once.
example:
class FutureSample extends StatefulWidget {
// Create instance variable
#override
_FutureSampleState createState() => _FutureSampleState();
}
class _FutureSampleState extends State<FutureSample> {
Future myFuture;
Future<String> _fetchData() async {
await Future.delayed(Duration(seconds: 10));
return 'DATA';
}
#override
void initState() {
// assign this variable your Future
myFuture = _fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FutureBuilder(
future: myFuture,
builder: (ctx, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.toString());
}
return CircularProgressIndicator();
},
),
),
);
}
}
In that way you don't need a bool value. There are also different ways to achieve or extend your request. You can check this article for more informations: https://medium.com/flutterworld/why-future-builder-called-multiple-times-9efeeaf38ba2
I am fetching data from firebase and show in listview. I store data in list and and then notify it but still list length is null.
I study all related question but still not get solution.
i want whenever I add some new data in firebase It appears same time in listview. So for that I am using provider here. please also give me some idea for this.
Thank you in advance
Here is Code
class Home121 extends StatefulWidget {
#override
_Home121State createState() => _Home121State();
}
final fb = FirebaseDatabase.instance.reference().child("Data");
}
#override
Widget build(BuildContext context) {
Future<void> StoreData() async {
dynamic key=CreateCryptoRandomString(32);
fb.child(key).set({
"id": key,
"link": "EasyCoding",
}).then((value) {
ShowToastNow();
});
}
return ChangeNotifierProvider<TaskManager>(
create: (context) => TaskManager(),
child: Scaffold(
appBar: AppBar(
title: Text("Kkkk"),
),
body: Container(
margin: EdgeInsets.all(
8,
),
child: Consumer<TaskManager>(
builder: (context, myModel, child) {
return myModel.list.length==0? Text("zero"):ListView.builder(
itemCount:myModel.list.length,
itemBuilder: (context,index){
return Text(
myModel.list[index].Name,
style: TextStyle(
fontSize: 20,
),
);
},
);
}
)
),
)
);
}
#override
void initState() {
TaskManager task=TaskManager();
task.GetData();
}
}
class TaskManager extends ChangeNotifier{
List<Model> list=new List();
GetData(){
//get data here
}
AddNewTask(){
// add data to databse
list.add(model);
notifyListeners();
}
}
Edit:
class MyClass extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<TaskManager>(
create: (context) => TaskManager(),
child: Home121());
}
}
This is because in initState() you are creating another instance of TaskManager and calling the method GetData() on that instance.
So the instance of TaskManager which the Consumer widgets use is different and so the list inside that one always remain empty.
Instead you need to do this inside initState()..
void initState() {
TaskManager task = Provider.of<TaskManager>(context);
task.GetData();
}
By doing this, you call the GetData() method on the same instance which is used by the Consumer widgets.