How to change Future Dynamic into String? - flutter

I am following old tutorial, and I get an error on ImageUrl in last line of code:
"Future dynamic can't be assigned to argument type String".
How can I fix that?
class _MyAppState extends State<MyApp> {
static FirebaseStorage storage = FirebaseStorage(
storageBucket: 'gs://natapp-7d2db/storage/natapp-7d2db.appspot.com/files'
);
static StorageReference imageRef = storage.ref().child('cake.png');
final imageUrl = imageRef.getDownloadURL();
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(brightness: Brightness.dark),
home: Container(
child: Image.network(imageUrl),
),
);
}
}
EDIT: I used
child: Image.network(imageUrl.toString)

Replace Container with Use FutureBuilder, as FutureBuilder is a widget which used for an async callback which runs on Future
FutureBuilder<String>(
future: imageRef.getDownloadURL(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
child: Image.network(snapshot.data));
}
if (snapshot.hasError) return WidgetThatShowsError();
// by default show progress because operation is async and we need to wait for result
return CircularProgressIndicator();
},
);

Related

Flutter - "Expected a value of type 'Widget?', but got one of type 'String' "

Whenever I try fetching data from a REST API, I keep getting an error "Expected a value of type 'Widget?', but got one of type 'String'". There is nothing wrong with my code yet I keep getting the error.
This is the function for fetching items from the database.
Future<List<Map>> fetchItems() async {
List<Map> items = [];
//get data from API and assign to variable
http.Response response =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
if (response.statusCode == 200) {
//get data from the response
String jsonString = response.body;
items = jsonDecode(jsonString).cast<Map>();
}
return items;
}
This is my main.dart file
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PostList(),
);
}
}
class PostList extends StatelessWidget {
PostList({super.key});
final Future<List<Map>> _futurePosts = HTTPHelper().fetchItems();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Posts"),
),
body: FutureBuilder(
future: _futurePosts,
builder: ((context, snapshot) {
//check for error
if (snapshot.hasError) {
return Center(
child: Text("Some error has occured ${snapshot.error}"));
}
//has data
if (snapshot.hasData) {
List<Map> _posts = snapshot.data!;
return ListView.builder(
itemCount: _posts.length,
itemBuilder: ((context, index) {
Map _thisItem = _posts[index];
return ListTile(
title: _thisItem["title"],
subtitle: _thisItem["body"],
);
}));
}
//display a loader
return Center(child: CircularProgressIndicator());
}),
),
);
}
}
Any solution to this error?
The answer is pretty simple. You're assigning directly string value to the title(Which is expecting Widget).
You can try below code
ListView.builder(
itemCount: _posts.length,
itemBuilder: ((context, index) {
Map _thisItem = _posts[index];
return ListTile(
title: Text(_thisItem["title"].toString()),
subtitle: Text(_thisItem["body"].toString()),
);
}));
If this doesn't work. Please let me know.
ListTile(
title:NEED WIDGET HERE,
subtitle:NEED WIDGET HERE,)

Flutter setState vs snapshot

i would like to know whats the diffrence between using setState and snapshot when fetching apis for example
the way i fetch the apis is like the following
Widget text = Container;
Future<AnyClass> fetch() async{
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
var result = AnyClass.fromJson(jsonDecode(response.body));
setState(()=> text = result.title)
}
#override
Widget build(BuildContext context) {
return Contianer(child:text)
}
there is another way which uses the snapshot to featch the data instead of using state like the following
Future<Album> fetchAlbum() async { final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
return Album.fromJson(jsonDecode(response.body));
} #override void initState() {
super.initState();
futureAlbum = fetchAlbum(); } #override Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
),
); }
so i would like to know what are the diffrence between these two methods. Thanks
None, you can check the FutureBuilder source code to see that is does exactly the same: call setState when it has a response.
The advantage of FutureBuilder are:
Easy handling of the different state (loading, loaded, failure)
You don't have to create a StatefulWidget, which means less line of code

Why does my async method run twice in Flutter?

I want to load a static list data when entering indexScreen,but the list sometimes show twice the same content,sometimes not.
This is my list setting:List<ListClass> listItems=List<ListClass>();,ListClass is a simple class with on different attributes and a constructor.
I use home:IndexScreen() in main.dart to show Index page.
return MaterialApp(
home: IndexScreen(),
debugShowCheckedModeBanner: false,
onGenerateRoute: router.generator,
builder: EasyLoading.init(),
);
And before this page build,it will update listItems using:
Future<bool> initUserAndIndex() async{
if (curUserEmail==null) sharedGetData(USER_EMAIL).then((value) => curUserEmail=value.toString());
print(curUserEmail);
await UserTable().getUserInfo(curUserEmail).then((value){print("user ok");});
await CollectionTable().getIndexList().then((value){print("Collection ok");return true;});
return null;
}
buildPage:
#override
Widget build(BuildContext context) {
return FutureBuilder<Object>(
future: initUserAndIndex(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState==ConnectionState.waiting)
{
EasyLoading.show(status: 'loading...');
// avoid no return,this cause a whiteborad transition,I don't know how to solve it,too.
return Container();
}
else
{
EasyLoading.dismiss();
return SafeArea(
child: Scaffold(
// the listItems is used in Body()
body: Body(),
),
);
}
},
);
}
}
I run this app,and it prints twice user ok and Collection ok.But when I use ROUTER.NAVIGETE,it only prints once.
User Information is OK,but the list is such a great problem--the page shows twice content
I put my code at an order of relevance of this prblom,I think.Next I put my the two awaited funtion here:
User:
Future<bool> getUserInfo(String userEmail) async{
await userCollection.where({'userEmail':userEmail}).get().then((res) async {
//assign to the static variables
return true;
});
return null;
}
Collection:
Future<bool> getIndexList() async {
listItems.clear();
await listCollection.get().then((value){
var v = value.data;
for (var data in v) {
//get data and package them,add after the listItems list.
listItems.add(ListClass(header, content, userId, favorCount, wordCount));
}
return true;
});
}
You probably want to assign your future in your widget class, but not in the build method as the documentation show, otherwise, everytime your build method is triggered, it will call again your FutureBuilder.
final Future<String> _calculation = Future<String>.delayed(
const Duration(seconds: 2),
() => 'Data Loaded',
);
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
textAlign: TextAlign.center,
child: FutureBuilder<String>(
future: _calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
// ...
}
),
),
}

How can I pass a parameter to the future property function in FutureBuilder

My problem is that I don't know how to pass the argument of a route to my function which is inside the FutureBuilder.
Please see my code below.
class StudyDetailsArguments {
final String listid;
StudyDetailsArguments(this.listid);
}
// A widget that extracts the necessary arguments from the ModalRoute.
class ExtractStudyDetails extends StatelessWidget {
static const routeName = '/studydetails';
#override
Widget build(BuildContext context) => FutureBuilder(
// Here I want to pass in the args.listid but I cant figure out how to get it in there
future: getDetails("cc0e5c1f-02b0-4f4f-9f51-fa70ac7e9c08"),
builder: (context, snapshot) {
// here is fine to use the argument
final StudyDetailsArguments args = ModalRoute.of(context).settings.arguments;
if (snapshot.hasData) {
// Build the widget with data.
//return Text('hasData: ${snapshot.data}');
return Scaffold(
appBar: AppBar(
title: Text(snapshot.data),
),
body:
Center(
child:
Text('${snapshot.data}'))
);
} else {
// We can show the loading view until the data comes back.
return Scaffold(
appBar: AppBar(
title: Text("Loading..."),
),
body:
Center(
child:
Text('Loading...'))
);
}
},
);
}
Future<String> getDetails(listid) async {
var details = "";
await Firestore.instance
.collection('list')
.document(listid)
.get()
.then((DocumentSnapshot ds) {
print(ds.data["title"]);
// use ds as a snapshot
details = ds.data["title"];
return ds.data;
});
return details;
}
I want to use this line args.listid instead of cc0e5c1f-02b0-4f4f-9f51-fa70ac7e9c08 but I can't seem to figure out the way to pass the parameter. How I send the parameter value (which now is not used) to the widget is this way in a widget:
onTap: () => {
Navigator.pushNamed(context, ExtractStudyDetails.routeName,
arguments: StudyDetailsArguments(
study.listid,
))
},
#override
Widget build(BuildContext context) {
final StudyDetailsArguments args = ModalRoute.of(context).settings.arguments;
return FutureBuilder(
future: getDetails(args.listid),
[...]
)
}
Read more on docs

How can I update the value of the parameter with the value that come from API response?

I am trying to update the value of totalPricewith the value that comes from the response from API. I have created a currentTotal methods that contains setState(). Then passed snapshot.data.price.totalAmountvalue to currentTotal in order to update the value of totalPrice.But, it doesnt update the value. Can you help?
double totalPrice = 0;
#override
Widget build(BuildContext context) {
currentTotal(double x) {
setState(() {
totalPrice += x;
});
}
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: FutureBuilder<SearchResult>(
future: serviceOne.postCall(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data != null) {
return new Material(
child: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate([
ListTile(
title: new Text(totalPrice.toString()),
)
]),
),
]
)
}
currentTotal(snapshot.data.price.totalAmount);
else if (snapshot.hasError) {
return Text("error....${snapshot.error}");
}
There are many things needs to be fixed in your build.
1 - Your widget is StatefulWidget, to use FutureBuilder inside StatefulWidget read this:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Briefly, create Future future; instance field, then assign it inside the initState and use that future for FutureBuilder.
2 - your setState not inside a method, you have probably syntax error there. Create a void method and use setState inside it.
3 - You don't need to check twice like:
if (snapshot.hasData) {
if (snapshot.data != null) {
One of them enough, after the condition check, call your method includes setState, then display it.
Edit:
Here an example template for your solution:
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
Future<int> future;
int price = 0;
#override
void initState() {
future = fetchPrice();
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FutureBuilder<int>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(
child: Text(price.toString()),
);
}
return Center(child: CircularProgressIndicator());
},
),
),
);
}
Future<int> fetchPrice() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/posts/1');
final data = json.decode(response.body);
setState(() {
price = data['userId'];
});
return data['userId'];
}
}