Flutter List View Builder Using API - flutter

I am able to fetch data from API and I can show it in the List View, but i want to show the first card as the latest updated data.
I tried to use the Reverse property of List View Builder, it some how solve my problem but gave rise to another problem. Now i need to scroll upward in order to see latest updated data.
Help me through it
I just want to show the latest updated data at the top and then scroll down to see another data.
Please Help Me.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class PromotersDetails extends StatefulWidget {
final String url,title;
PromotersDetails({Key key, #required this.url, #required this.title}) : super(key: key);
#override
_PromotersDetailsState createState() => _PromotersDetailsState(url,title);
}
class _PromotersDetailsState extends State<PromotersDetails> {
fetchSelfies() async {
var url1;
url1 = await http.get(Uri.parse(
url));
var res = json.decode(url1.body);
print(res);
return json.decode(url1.body)['selfies'];
}
String url,title;
_PromotersDetailsState(this.url, this.title);
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width * 0.6;
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
centerTitle: false,
title: Text(
title,
style: TextStyle(fontSize: 25.0, color: Colors.white),
),
elevation: 0.0,
backgroundColor: Colors.green,
),
body: FutureBuilder(
future: fetchSelfies(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
}
if (snapshot.hasData) {
return ListView.builder( **// List View Builder**
reverse: false,
cacheExtent: 10000.00,
itemCount: snapshot.data.length,
padding: EdgeInsets.all(8),
itemBuilder: (BuildContext context, int index) {
return Row(
children: [
Container(
height: 120,
alignment: Alignment.center,
child: Container(
height: 120,
width: 120,
child: Card(
child: Image.network(snapshot.data[index]['image']),
),
),
),
Expanded(
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 10,
),
Row(
children: [
Text(
"Time: ",
style: TextStyle(color: Colors.black),
),
Text(
snapshot.data[index]['time'],
style: TextStyle(color: Color(0xff868597)),
),
],
),
Container(
height: 50,
child: Text(
snapshot.data[index]['location'],
style: TextStyle(color: Color(0xff868597)),
),
),
],
),
),
),
],
);
},
);
}
return Center(
child: CircularProgressIndicator(),
);
})
);
}
}

If you just need to sort the API response backwards, you can use the reverse property of ListView.
ListView.builder(
reverse: true,
...
),
If the way you're populating the ListView is by appending new data, then it's better to store the data on a List then do sort/reversed. Though the sorting should be best done at the backend instead of passing the burden to the client app.

Related

Error: type 'String' is not a subtype of type 'int' of 'index'

(Edit: I have 3 files
main , trending and text. Let me show them.) I am showing you 2 of them here - where the mainly important portions are.
I think the problem is within 'trending' page where trending[index] is not accepting as String. The api is from tmdb which is a list of maps and has 'poster path'. Donot know what the problem is.
Main =>
class _HomeState extends State<Home> {
#override
void initState() {
loadmovies();
super.initState();
}
List trendingmovies = [];
List topratedmovieslist = [];
List tvlist = [];
final String apikey = '974ecf335095695927b80cb92fbe6200';
final readaccesstoken = 'eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI5NzRlY2YzMzUwOTU2OTU5MjdiODBjYjkyZmJlNjIwMCIsInN1YiI6IjYzMzAwZmJkYWJkYWZjMDA3Y2Q5OWRkZCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.LZkUXKdyN5YCWJcfV6wYLVejGvYp22BzAQowBUWe5Ys';
loadmovies() async{
final tmdb = TMDB(
ApiKeys(apikey, readaccesstoken),
logConfig: const ConfigLogger(
showLogs: true,//must be true than only all other logs will be shown
showErrorLogs: true,
),);
Map result = await tmdb.v3.trending.getTrending();
Map topratedmovies = await tmdb.v3.movies.getTopRated();
Map tv = await tmdb.v3.tv.getTopRated();
setState(() {
trendingmovies = result['results'];
topratedmovieslist = topratedmovies['results'];
tvlist = tv['results'];
});
print(trendingmovies);
print(topratedmovieslist);
print(tvlist);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const modified_text(text:'Film Library', color: Colors.white70, size: 20,
),
backgroundColor: Colors.transparent,
),
body: ListView(
children: [
TrendingMovies(trending: [trendingmovies],)
],
),
);
}
}
Edit: This is the trending file. I have noted that initialized list doesn't contain poster_path like with tmdb but the main file has extracted it to a list.
Trending ->
import 'package:film_library/utils/text.dart';
import 'package:flutter/material.dart';
class TrendingMovies extends StatelessWidget {
const TrendingMovies({Key? key, required this.trending}) : super(key: key);
final List trending;
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const modified_text (text:'Trending Movies', size: 30, color: Colors.white,),
SizedBox(height: 300,
child: ListView.builder(itemCount: trending.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context,index){
return InkWell(
onTap: (){
},
child: SizedBox(
width: 140,
child: Column(
children: [
Container(
height: 200,
decoration: BoxDecoration(
image: DecorationImage(image: NetworkImage(
'http://image.tmdb.org/t/p/w500' + trending[index] ['poster_path']))
)
),
modified_text(text: trending[index]['title'], color: Colors.brown, size: 20,)
],
),
),
);
}),
)
],
),
);
}
}
Edit: There is text file as well but it is nothing but just used as some Font class. I have created model class like - List trending but it too says that - Error: A value of type 'List' can't be assigned to a variable of type 'Trending Movies', and shows error on same poster_path. Maybe the syntax is wrong with calling it as #jacksparrow, did while it doesnt show the error here with [index] ['poster_path'] but that one of the title.
I would suggest that please create model class from json response of your Trending movies API and then create list of trending movies using that model like as below :
List<TrendingMovies> trending ;
It is better to fetch data from API by making model class and fromJson method of that particular json response. It makes more easier to understand use data in it.
import 'package:film_library/utils/text.dart';
import 'package:flutter/material.dart';
class TrendingMovies extends StatelessWidget {
const TrendingMovies({Key? key, required this.trending}) : super(key: key);
List<TrendingMovies> trendingMoviesList ;
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const modified_text (text:'Trending Movies', size: 30, color: Colors.white,),
SizedBox(
height: 300,
child: ListView.builder(
itemCount: trending.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final trendingMovie= trendingMoviesList[index];
return InkWell(
onTap: () {},
child: SizedBox(
width: 140,
child: Column(
children: [
Container(
height: 200,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
"http://image.tmdb.org/t/p/w500${trendingMovie.poster_path}"),
),
),
),
modified_text(text: trendingMovie.title, color: Colors.brown, size: 20,)
],
),
),
);
}),
)
],
),
);
}
}

How to fix Image overflow in Flutter PageView?

I'm building an app where it shows the title, author name, number of upvotes, and an image from the subreddit in a page view. Everything is working fine but for some images, the page view is overflowing, how do I fix this?
Here's the overflow error:
Here's my code
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class Stardew extends StatefulWidget {
const Stardew({ Key? key }) : super(key: key);
#override
State<Stardew> createState() => _StardewState();
}
class _StardewState extends State<Stardew> {
List data = [];
Future<String> getData() async {
List temp_data = [];
var response = await http.get(
Uri.parse("https://m...content-available-to-author-only...p.com/gimme/stardewvalley/100")
);
return response.body;
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.data == null){
return Center(child: CircularProgressIndicator(color: Color(0xff008b00)));
}
var jsonData = jsonDecode(snapshot.data);
jsonData = jsonData["memes"];
return PageView.builder(
//scrollDirection: Axis.vertical,
itemCount: jsonData.length,
itemBuilder: (BuildContext context, int index){
return Center(
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
getImgCard(
jsonData[index]["title"],
//jsonData[index]["preview"][2],//preview image
jsonData[index]["url"], //original image
jsonData[index]["author"],
(jsonData[index]["ups"]).toString()
)
],
),
),
);
},
);
}
);
}
Widget getImage(String imgUrl){
return Container(
child: Image.network(
imgUrl,
fit: BoxFit.scaleDown,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded/loadingProgress.expectedTotalBytes! : null,
color: Color(0xff008b00),
),
);
},
),
);
}
Widget getImgCard(String title, String imgUrl, String author, String ups){
return Card(
color: Color(0xff000000),
clipBehavior: Clip.antiAlias,
child: Column(
children: [
ListTile(
leading: RichText(
text: TextSpan(
children: [
TextSpan(
text: ups,
),
const WidgetSpan(
child: Icon(Icons.arrow_upward, size: 18, color: Color(0xff008b00),),
)
],
),
),
title: Text(title, style: TextStyle(color: Colors.white),),
subtitle: Text(
"Posted by u/${author}",
style: TextStyle(color: Colors.white.withOpacity(0.6)),
),
),
getImage(imgUrl),
Padding(padding: EdgeInsets.only(bottom: 8))
],
),
);
}
}
How do I fix this? I have tried changing the box fit and it did not work. Then I used expaned and flexible widgets and still can't find the answer to this solution. please help me.
Wrap getImage(imgUrl) inside Expanded widget.
I found the answer myself, removing the parent column and wrapping it with SingleChildScrollView fixed the error.
return PageView.builder(
//scrollDirection: Axis.vertical,
itemCount: jsonData.length,
itemBuilder: (BuildContext context, int index){
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(1.0),
child: getImgCard(
jsonData[index]["title"],
//jsonData[index]["preview"][2],//preview image
jsonData[index]["url"], //original image
jsonData[index]["author"],
(jsonData[index]["ups"]).toString()
),
)
);
},
);

Problem in showing data in ui in ListView Builder

I am sending data from my controller to my ui part i can't understand what the problem is and how to solve it. I tried every answer given on stackoverflow and many other websites but nothing seems to work for me:( i am sure my login part has no problem but my ui is not able to render the data According to my needs.
Code:
final singleViewController = Get.put(SingleViewController());
class SingleView extends StatelessWidget {
final List stanza;
final String paragraphId;
const SingleView({
Key? key,
required this.stanza,
required this.paragraphId,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(
stanza.toString().trim(),
style: const TextStyle(
fontSize: 25,
color: Colors.red,
),
),
),
body: Container(
height: 800,
color: Colors.amber,
child: Column(
children: [
GetBuilder<SingleViewController>(builder: (controller) {
return SizedBox(
height:600,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: singleViewController.foundedparagraph.length,
itemBuilder: (ctx, index) {
return Text(
singleViewController.foundedparagraph[index]["stanza"]
.toString(),
);
},
),
);
}),
],
),
),
),
);
}
}
this is the error i am receiving:
Error Image

Disabling FloatingActionButton after list view and showing a message that the limit is exceeded

How can I disable a button, in this case a FAB(Floating Action Button), and prevent the user adding more items to the list because he is exceeding the limit and showing a message? I'm using a FutureBuilder and a ListView.builder, after the user adds 3 item to the list, I want to disable the FAB and prevent the user to add more items to the list and showing some sort of notification/message/floating over button message to the user that he is exceeding the limit of 3 items per list. Here is the code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'screens/add_items_screen.dart';
import 'components/items_list.dart';
class ItemScreen extends StatelessWidget {
static const String id = 'item_screen';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
iconTheme: IconThemeData(
color: Colors.grey[600],
),
),
/// I want to disable this button after the limit of 3 items in the list view
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.grey,
child: Icon(Icons.add),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: AddItemScreen(),
)));
},
),
body: SafeArea(
child: SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(20.0),
color: Colors.grey[100],
child: Container(
height: 800,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Item List Display',
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.w700,
),
),
SizedBox(
height: 20.0,
),
Expanded(
child: Column(
children: [
/// Here is where the items are rendered as a list
Expanded(child: ItemList()),
],
),
),
],
),
),
),
),
),
);
}
}
The Item list component:
import 'package:flutter/material.dart';
import 'common/api/api_caller.dart';
import 'utilities/item_api_helper.dart';
import 'models/item.dart';
import 'stores/item_store.dart';
import 'dart:async';
import 'package:provider/provider.dart';
class ItemList extends StatefulWidget {
#override
_ItemList createState() => _ItemList();
}
class _ItemList extends State<ItemList> {
Future<HTTPResponse<List<ItemData>>> _getItemList() async {
var _itemData = await APICaller.instance.getItems();
var provider = Provider.of<ItemDatStore>(context, listen: false);
provider.setItemList(_itemData.data, notify: false);
return _itemData;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: FutureBuilder<HTTPResponse<List<ItemData>>>(
future: _getItemList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Consumer<ItemDataStore>(
builder: (context, itemData, child) {
return ListView.builder(
itemCount: itemData.itemList.length,
itemBuilder: (context, index) {
ItemData items =
itemData.getItemByIndex(index);
return Card(
child: Container(
padding: EdgeInsets.all(10.0),
child: Text('items.title'),
),
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Container();
},
),
),
);
}
}
I've tried using an if statement where I can define if(itemData.itemList.length <3) it should return those three items, else {Text('Item limit of 3 is exceeded')} but I'm not sure if this is the right way or if I defined it right since I put it in the Item List component and I need to put it in the Item Screen. Thanks in advance for you help!
You can wrap your Floating action button with Visibility it hides and not woking anymore :)
Visibility(
visible: items.length > 3,
child : FAB here,
),
You can disable the FAB like this
onPressed: items.length < 3? addItemToList : null;
and addItemToList is
void addItemToList(){
// Do Something here
}

How to save downloaded data from an API to RAM in Flutter?

I'm writing a really simple app in Flutter, but I have a problem with state management.
Here's the video of what I have. Link: https://streamable.com/ir3ztr
The video shows my application, but when I switch a screen using Bottom Navigation Bar, the data loads again and again from the API. I don't want that. I want the once downloaded data to be saved in RAM and not being downloaded again from the API. Is that possible? I heard about Provider, but I don't know how to use that in my case.
Is there anyone who can help me?
My code:
World
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_placeholder_textlines/flutter_placeholder_textlines.dart';
import '../../models/world.dart';
import '../../data/world_service.dart';
class WorldScreenAndroid extends StatefulWidget {
#override
_WorldScreenAndroidState createState() => _WorldScreenAndroidState();
}
class _WorldScreenAndroidState extends State<WorldScreenAndroid> {
Future<World> futureWorld;
#override
void initState() {
super.initState();
futureWorld = fetchWorld();
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8.0),
child: FutureBuilder<World> (
future: futureWorld,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: [
Card(
child: ListTile(
leading: Icon(Icons.public),
title: Text('coronavirus_cases').tr(context: context),
subtitle: Text(NumberFormat('#,###,###', 'en_US').format(snapshot.data.cases).toString())
),
),
Card(
child: ListTile(
leading: Icon(Icons.public),
title: Text('deaths').tr(context: context),
subtitle: Text(NumberFormat('#,###,###', 'en_US').format(snapshot.data.deaths).toString())
),
),
Card(
child: ListTile(
leading: Icon(Icons.public),
title: Text('recovered').tr(context: context),
subtitle: Text(NumberFormat('#,###,###', 'en_US').format(snapshot.data.recovered).toString())
),
)
],
);
}
return ListView(
children: [
Card(
child: ListTile(
leading: Icon(Icons.public),
title: Text('coronavirus_cases').tr(context: context),
subtitle: PlaceholderLines(
count: 1,
animate: true,
color: Colors.grey,
minWidth: 0.10,
maxWidth: 0.50,
),
),
),
Card(
child: ListTile(
leading: Icon(Icons.public),
title: Text('deaths').tr(context: context),
subtitle: PlaceholderLines(
count: 1,
animate: true,
color: Colors.grey,
minWidth: 0.10,
maxWidth: 0.50,
),
),
),
Card(
child: ListTile(
leading: Icon(Icons.public),
title: Text('recovered').tr(context: context),
subtitle: PlaceholderLines(
count: 1,
animate: true,
color: Colors.grey,
minWidth: 0.10,
maxWidth: 0.50,
),
),
)
],
);
},
)
);
}
}
Countries
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import '../../models/country.dart';
import '../../data/countries_service.dart';
class CountriesScreenAndroid extends StatefulWidget {
#override
_CountriesScreenAndroidState createState() => _CountriesScreenAndroidState();
}
class _CountriesScreenAndroidState extends State<CountriesScreenAndroid> {
Future<List<Country>> futureCountries;
#override
void initState() {
super.initState();
futureCountries = fetchCountries();
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8.0),
child: FutureBuilder(
future: futureCountries,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: [
TextField(),
SizedBox(height: 10.0),
Expanded(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
final List<String> _countriesAllArgs = [
NumberFormat('#,###,###', 'en_US').format(snapshot.data[index].cases),
NumberFormat('#,###,###', 'en_US').format(snapshot.data[index].todayCases),
NumberFormat('#,###,###', 'en_US').format(snapshot.data[index].active),
NumberFormat('#,###,###', 'en_US').format(snapshot.data[index].deaths),
NumberFormat('#,###,###', 'en_US').format(snapshot.data[index].todayDeaths),
NumberFormat('#,###,###', 'en_US').format(snapshot.data[index].recovered),
NumberFormat('#,###,###', 'en_US').format(snapshot.data[index].critical)
];
return Card(
child: Padding(
padding: EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
snapshot.data[index].country,
style: TextStyle(
fontSize: 18.0,
color: Colors.grey[600],
),
),
SizedBox(height: 6.0),
Text(
'countries_all',
style: TextStyle(
fontSize: 14.0,
color: Colors.grey[800],
),
).tr(args: _countriesAllArgs),
],
),
)
);
}
),
)
],
);
}
return Center(
child: CircularProgressIndicator(),
);
},
)
);
}
}
Make sure to keep the loaded data in a parent widget, not in the widget your switching.
Have you considered using the BLOC framework?
In that case, you could nicely sepearte Business Logic from UI code and keep the loaded data in the bloc.
In detail have a blocProvider on top, wrapping your app.
https://bloclibrary.dev/#/flutterbloccoreconcepts?id=blocprovider
Alternatively, but be carefull with the handling though, you can just create an object, anywhere outside of any widget, and access it from anywhere.
class Data {
List<String> stringsLoadedFromWeb;
}
Data data = Data();
// from anywhere else, where you import the above file
data.stringsLoadedFromWeb = ...