Related
I'm developing an app using movies database API. I want to read a list of the movies and then choose one and display the details to user.
MovieDetailsBloc code:
import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_recruitment_task/movie_details/models/movie_details.dart';
import 'package:flutter_recruitment_task/services/api_service.dart';
import 'package:stream_transform/stream_transform.dart';
part 'movie_details_event.dart';
part 'movie_details_state.dart';
const throttleDuration = Duration(milliseconds: 100);
EventTransformer<E> throttleDroppable<E>(Duration duration) {
return (events, mapper) {
return droppable<E>().call(events.throttle(duration), mapper);
};
}
class MovieDetailsBloc extends Bloc<MovieDetailsEvent, MovieDetailsState> {
MovieDetailsBloc({required this.apiService}) : super(MovieDetailsState()) {
on<MovieSelected>(
_onMovieSelected,
transformer: throttleDroppable(throttleDuration),
);
}
final ApiService apiService;
Future<void> _onMovieSelected(MovieSelected event, Emitter<MovieDetailsState> emit) async {
try{
final movie = await _fetchMovieDetails(event.query);
return emit(state.copyWith(
status: MovieDetailsStatus.success,
movie: movie,
));
} catch (_) {
emit(state.copyWith(status: MovieDetailsStatus.failure));
}
}
Future<MovieDetails> _fetchMovieDetails(String id) async {
final movie = apiService.fetchMovieDetails(id);
return movie;
}
String shouldIWatchIt() {
final movie = state.movie;
if(movie.revenue - movie.budget > 1000000 && DateTime.now().weekday == DateTime.sunday){
return 'Yes';
} else {
return 'No';
}
}
}
MovieListPage code:
import 'package:flutter/material.dart';
import 'package:flutter_recruitment_task/movie_details/bloc/movie_details_bloc.dart';
import 'package:flutter_recruitment_task/movies/bloc/movies_bloc.dart';
import 'package:flutter_recruitment_task/movies/models/movie.dart';
import 'package:flutter_recruitment_task/pages/movie_details/movie_details_page.dart';
import 'package:flutter_recruitment_task/pages/movie_list/movie_card.dart';
import 'package:flutter_recruitment_task/pages/movie_list/search_box.dart';
import 'package:flutter_recruitment_task/services/api_service.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MovieListPage extends StatefulWidget {
#override
_MovieListPage createState() => _MovieListPage();
}
class _MovieListPage extends State<MovieListPage> {
final ApiService apiService = ApiService();
late final MoviesBloc moviesBloc;
late final MovieDetailsBloc movieDetailsBloc;
void _onSearchBoxSubmitted(String text) {
moviesBloc.add(MoviesFetched(query: text));
}
#override
void initState() {
moviesBloc = MoviesBloc(apiService: apiService);
movieDetailsBloc = MovieDetailsBloc(apiService: apiService);
super.initState();
}
#override
void dispose() {
movieDetailsBloc.close();
moviesBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) => BlocProvider(
create: (_) => movieDetailsBloc,
child: Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.movie_creation_outlined),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => MovieDetailsPage(
movieDetailsBloc: movieDetailsBloc,
)
)
);
},
),
],
title: Text('Movie Browser'),
),
body: BlocProvider(
create: (_) => moviesBloc,
child: Column(
children: <Widget>[
SearchBox(onSubmitted: _onSearchBoxSubmitted),
Expanded(child: _buildContent()),
],
),
),
),
);
Widget _buildContent() => BlocBuilder<MoviesBloc, MoviesState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
switch (state.status) {
case MoviesStatus.failure:
return const Center(child: Text('failed to fetch movies'));
case MoviesStatus.success:
if (state.movies.isEmpty) {
return const Center(child: Text('no movies'));
}
return _buildMoviesList(state.movies);
case MoviesStatus.initial:
return const Center(child: CircularProgressIndicator());
}
});
Widget _buildMoviesList(List<Movie> movies) => ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1.0,
color: Colors.grey.shade300,
),
itemBuilder: (context, index) => BlocBuilder<MovieDetailsBloc, MovieDetailsState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
return MovieCard(
title: movies[index].title,
rating: '${(movies[index].voteAverage * 10).toInt()}%',
color: state.movie.title == movies[index].title ?
Colors.amberAccent : Colors.white,
onTap: () {
movieDetailsBloc.add(MovieSelected(query: movies[index].id.toString()));
},
);
}
),
itemCount: movies.length,
);
}
MovieDetailsPage code:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_recruitment_task/movie_details/bloc/movie_details_bloc.dart';
import 'package:flutter_recruitment_task/movie_details/models/movie_detail_placeholder.dart';
class MovieDetailsPage extends StatefulWidget {
final MovieDetailsBloc? movieDetailsBloc;
const MovieDetailsPage({super.key, this.movieDetailsBloc});
#override
_MovieDetailsPageState createState() => _MovieDetailsPageState();
}
class _MovieDetailsPageState extends State<MovieDetailsPage> {
var _details = [];
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) => BlocProvider(
create: (_) => widget.movieDetailsBloc!,
child: BlocBuilder<MovieDetailsBloc, MovieDetailsState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
_details = [
MovieDetailPlaceholder(title: 'Budget', content: '\$ ${state.movie.budget}'),
MovieDetailPlaceholder(title: 'Revenue', content: '\$ ${state.movie.revenue}'),
MovieDetailPlaceholder(title: 'Should I watch it today?', content: widget.movieDetailsBloc!.shouldIWatchIt()),
];
return Scaffold(
appBar: AppBar(
title: Text(state.movie.title),
),
body: ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1.0,
color: Colors.grey.shade300,
),
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
_details[index].title,
style: Theme.of(context).textTheme.headline5,
),
SizedBox(height: 8.0),
Text(
_details[index].content,
style: Theme.of(context).textTheme.subtitle1,
),
],
),
),
itemCount: _details.length,
),
);
}
),
);
}
I'm passing an instance of BLoC from MovieListPage to MovieDetailsPage because it holds the details of the movie to display. It works fine for one time, however when I go back to the MovieListPage I'm not able to choose a new movie to display the details. How I can make an instance of BLoC persist through navigation?
Create your bloc's intance after your bloc ends like
class MovieDetailsBloc extends Bloc<MovieDetailsEvent, MovieDetailsState> {
MovieDetailsBloc({required this.apiService}) : super(MovieDetailsState()) {
on<MovieSelected>(
_onMovieSelected,
transformer: throttleDroppable(throttleDuration),
);
}
final ApiService apiService;
Future<void> _onMovieSelected(MovieSelected event, Emitter<MovieDetailsState> emit) async {
try{
final movie = await _fetchMovieDetails(event.query);
return emit(state.copyWith(
status: MovieDetailsStatus.success,
movie: movie,
));
} catch (_) {
emit(state.copyWith(status: MovieDetailsStatus.failure));
}
}
Future<MovieDetails> _fetchMovieDetails(String id) async {
final movie = apiService.fetchMovieDetails(id);
return movie;
}
String shouldIWatchIt() {
final movie = state.movie;
if(movie.revenue - movie.budget > 1000000 && DateTime.now().weekday == DateTime.sunday){
return 'Yes';
} else {
return 'No';
}
}
}
MovieDetailsBloc movieDetailsBloc = MovieDetailsBloc();
and you can access it anywhere in your code with movieDetailsBloc.add or whatever you want.
I am trying to add a search function in my flutter app, the search bar is showing and there's not errors but its not working and it doesn't return any results.
the data list is from an API that I already called using the rest API
// ignore_for_file: use_key_in_widget_constructors, avoid_print, avoid_unnecessary_containers, curly_braces_in_flow_control_structures, prefer_const_constructors, non_constant_identifier_names, unnecessary_new, avoid_function_literals_in_foreach_calls, unused_import, avoid_types_as_parameter_names, unused_label
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:myapp2/Service_Request/SR.dart';
import 'package:myapp2/main.dart';
import 'package:myapp2/Service_Request/second.dart';
import '../Classes/demandes.dart';
import 'SR_details.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DataFromAPI(),
);
}
}
class DataFromAPI extends StatefulWidget {
#override
_DataFromAPIState createState() => _DataFromAPIState();
}
List<Attributes> _MyAllData = [];
var _srAttributes = [];
class _DataFromAPIState extends State<DataFromAPI> {
#override
void initState() {
loadData().then((value) {
setState(() {
_srAttributes.addAll(value);
});
});
super.initState();
}
Future<List<Sr>> loadData() async {
try {
var response = await http.get(Uri.parse(
'http://192.168.1.30:9080/maxrest/rest/mbo/sr/?_lid=azizl&_lpwd=max12345m&_format=json'));
if (response.statusCode == 200) {
final jsonBody = json.decode(response.body);
Demandes data = Demandes.fromJson(jsonBody);
final srAttributes = data.srMboSet.sr;
return srAttributes;
}
} catch (e) {
throw Exception(e.toString());
}
throw Exception("");
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text('Liste des Demandes'),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (context) => SR()))),
),
body: FutureBuilder<List<Sr>?>(
future: loadData(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
} else {
return new ListView.builder(
itemCount: snapshot.data?.length,
itemBuilder: ((_, index) {
return index == 0
? _searchbar()
: new ListTile(
title: new Card(
margin: new EdgeInsets.symmetric(
vertical: 2.0, horizontal: 8.0),
elevation: 10,
child: new ListTile(
title: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(padding: new EdgeInsets.all(2.0)),
new Text(
'Ticket ID : ${snapshot.data![index].attributes.ticketid.content}'),
new Text(
'status : ${snapshot.data![index].attributes.status.content}'),
new Text(
'description : ${snapshot.data![index].attributes.description?.content}'),
new Text(
'Reported by : ${snapshot.data![index].attributes.reportedby.content}'),
new Text(
'Reoprt date : ${snapshot.data![index].attributes.statusdate.content}'),
],
),
trailing: Icon(Icons.arrow_forward_ios_rounded),
),
),
onTap: () {
Navigator.of(context)
.push(
new MaterialPageRoute(
builder: (BuildContext context) =>
new SrDetailsScreen(
sr: snapshot.data![index]),
),
)
.then((data) {});
});
}),
);
}
},
),
),
);
}
_searchbar() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(hintText: "Search ..."),
onChanged: (text) {
text = text.toLowerCase();
setState(() {
_srAttributes = _MyAllData.where((srAttributes) {
var idticket = srAttributes.description!.content.toLowerCase();
return idticket.contains(text);
}).toList();
});
},
),
);
}
}
FutureBuilder loads values of current future. You are assigning a function result to FutureBuilder so its value always changes dynamically.
Create variable to keep Future's value.
Future<List<Sr>>? dataToLoad;
Whenever you want to load data from server ( for example, on text changed ):
setState((){
dataToLoad = loadData();
});
And use it in FutureBuilder:
FutureBuilder<List<Sr>?>(
future: dataToLoad,
i'm trying to set the login cookies to the rest of the get requests.
so i use http pachakge and store the login cookies with sharedPreferences and use it in the get request by adding an update function
but i have a problem that when i go to the page i get 400 response just refreshing the page and i get my data and response 200
is there any other solution for setting cookies in the others get requests headers ?
or is there a solution for my bug ?
codes images : [https://ibb.co/kD3dDc9]
[https://ibb.co/25p5fZr]
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:valomnia_reports/Screens/Superviseur%20Screens/SideBar.dart';
import 'user_model.dart';
class SellersPage extends StatefulWidget {
const SellersPage({Key? key}) : super(key: key);
#override
_SellersPage createState() => _SellersPage();
}
class _SellersPage extends State<SellersPage> {
String? finalEmail;
Future? _futureData;
String? rawCookie;
// ignore: must_call_super
void initState() {
getValidationData();
super.initState();
_futureData = getUserApi();
}
Future getValidationData() async {
final SharedPreferences sharedPreferences2 =
await SharedPreferences.getInstance();
var obtainedEmail2 = sharedPreferences2.getString("rawCookie");
setState(() {
rawCookie = obtainedEmail2;
print(rawCookie);
});
}
List<UserModel> userList = [];
Map<String, String> headers = {};
Future<List<UserModel>> getUserApi() async {
http.Response response = await http.get(
Uri.parse('https://valomnia.herokuapp.com/superviseur/getAllVendeurs'),
headers: headers);
response.headers['set-cookie'] = rawCookie!;
updateCookie(response);
var data = jsonDecode(response.body.toString());
String? cookies = response.headers['set-cookie'];
if (response.statusCode == 200) {
for (Map i in data) {
userList.add(UserModel.fromJson(i));
}
print("Cookie : $cookies");
print("200");
return userList;
} else {
print("400");
print(rawCookie);
print(cookies);
return userList;
}
}
void updateCookie(http.Response response) {
String? rawCookie2 = response.headers['set-cookie'];
if (rawCookie2 != null) {
int index = rawCookie2.indexOf(';');
headers['cookie'] =
(index == -1) ? rawCookie2 : rawCookie2.substring(0, index);
}
}
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
Future<Null> _refresh() {
return getUserApi().then((userList) {
setState(() => userList = userList);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: NavigationDrawerWidget(),
appBar: AppBar(
title: Text(
'Sellers list',
),
centerTitle: true,
backgroundColor: Colors.green,
),
body: Column(
children: [
Expanded(
child: FutureBuilder(
future: _futureData,
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
return RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: _refresh,
child: ListView.builder(
itemCount: userList.length,
itemBuilder: (context, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
ReusbaleRow(
title: 'Id',
value:
snapshot.data![index].id.toString()),
ReusbaleRow(
title: 'Name',
value: snapshot.data![index].name
.toString()),
ReusbaleRow(
title: 'Username',
value: snapshot.data![index].username
.toString()),
ReusbaleRow(
title: 'DateCreated',
value: snapshot.data![index].email
.toString()),
],
),
),
);
}),
);
}
},
),
)
],
),
);
}
}
// ignore: must_be_immutable
class ReusbaleRow extends StatelessWidget {
String title, value;
ReusbaleRow({Key? key, required this.title, required this.value})
: super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title),
Text(value),
],
),
);
}
} ```
This is the homepage code of the app I'm currently developing and I have to get all data from API. Therefore I've come up _getLatest that gets the data from the URL which is a list of maps and the data goes into _latest. And I implemented the future builder based on Flutter: Execute a function on Startup - wait for HTTP response parser to extract image URL but things are not quite done.
First of all there are two blue underlines: 1. Future<String> _getLatest() async { says
This function has a return type of 'FutureOr', but doesn't end with a return statement. 2. builder: (BuildContext context, AsyncSnapshot<String> snapshot) { says This function has a return type of 'Widget', but doesn't end with a return statement.
And the main problem is the homepage is that snapshot.connectionState doesn't change to done state so it's loading data eternally. And I'm pretty sure it's because of the code not the URL, the API works fine.
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';
import 'package:kzstats/common/AppBar.dart';
import 'package:kzstats/common/Drawer.dart';
class Homepage extends StatefulWidget {
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
final String currentPage = 'KZStats';
var _latest = [];
Future<String> _getLatest() async {
var url =
'https://kztimerglobal.com/api/v2.0/records/top/recent?stage=0&tickrate=128&modes_list_string=kz_timer&limit=3';
var httpClient = new HttpClient();
var result;
try {
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode == HttpStatus.ok) {
var json = await response.transform(utf8.decoder).join();
var data = jsonDecode(json);
result = data;
} else {}
} catch (exception) {}
setState(() {
_latest = result;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: HomepageAppBar(currentPage),
drawer: HomepageDrawer(),
body: FutureBuilder<String>(
future: _getLatest(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return new Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
color: Colors.white,
margin: const EdgeInsets.all(20),
child: Text('${_latest[index]}'),
);
},
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Loading data from API...'),
)
],
),
);
}
},
),
floatingActionButton: Builder(builder: (builderContext) {
return FloatingActionButton(onPressed: () {
_getLatest();
});
}),
);
}
}
First of all, you have done some wrong things,
if you are using FutureBuilder you can use a snapshot inside it so no need for _latest variable.
you can also use http package for easily requesting data.
inside your _getLatest() function you didn't returned the value and also it was not String.
also you can use Model class for easily assessing data after fetching json.
For your Problem my solution is
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';
import 'package:http/http.dart' as http;
class Homepage extends StatefulWidget {
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
final String currentPage = 'KZStats';
Future<List<KzTimer>> _getLatest() async {
var url =
'https://kztimerglobal.com/api/v2.0/records/top/recent?stage=0&tickrate=128&modes_list_string=kz_timer&limit=3';
List<KzTimer> result;
try {
var response = await http.get(Uri.parse(url));
if (response.statusCode == HttpStatus.ok) {
result = kzTimerFromJson(response.body);
} else {
print('Something went wrong!');
}
} catch (exception) {}
return result;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<KzTimer>>(
future: _getLatest(),
builder: (BuildContext context, AsyncSnapshot<List<KzTimer>> snapshot) {
return snapshot.hasData ?
ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return new Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
color: Colors.white,
margin: const EdgeInsets.all(20),
child: Text('${snapshot.data[index].playerName}'),
//_latest[index].playerName
);
},
) :
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Loading data from API...'),
)
],
),
) ;
},
),
floatingActionButton: Builder(builder: (builderContext) {
return FloatingActionButton(onPressed: () {
_getLatest();
});
}),
);
}
}
Method to convert the response data to your model class data
List<KzTimer> kzTimerFromJson(String str) => List<KzTimer>.from(json.decode(str).map((x) => KzTimer.fromJson(x)));
Model class
class KzTimer {
KzTimer({
this.id,
this.steamid64,
this.playerName,
this.steamId,
this.serverId,
this.mapId,
this.stage,
this.mode,
this.tickrate,
this.time,
this.teleports,
this.createdOn,
this.updatedOn,
this.updatedBy,
this.place,
this.top100,
this.top100Overall,
this.serverName,
this.mapName,
this.points,
this.recordFilterId,
this.replayId,
});
int id;
String steamid64;
String playerName;
String steamId;
int serverId;
int mapId;
int stage;
String mode;
int tickrate;
double time;
int teleports;
DateTime createdOn;
DateTime updatedOn;
int updatedBy;
int place;
int top100;
int top100Overall;
String serverName;
String mapName;
int points;
int recordFilterId;
int replayId;
factory KzTimer.fromJson(Map<String, dynamic> json) => KzTimer(
id: json["id"],
steamid64: json["steamid64"],
playerName: json["player_name"],
steamId: json["steam_id"],
serverId: json["server_id"],
mapId: json["map_id"],
stage: json["stage"],
mode: json["mode"],
tickrate: json["tickrate"],
time: json["time"].toDouble(),
teleports: json["teleports"],
createdOn: DateTime.parse(json["created_on"]),
updatedOn: DateTime.parse(json["updated_on"]),
updatedBy: json["updated_by"],
place: json["place"],
top100: json["top_100"],
top100Overall: json["top_100_overall"],
serverName: json["server_name"],
mapName: json["map_name"],
points: json["points"],
recordFilterId: json["record_filter_id"],
replayId: json["replay_id"],
);
}
Change return type by: Future<void>. You are returning nothing.
You have an if and an else if but you still need the default case when both conditions are false.
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';
import 'package:kzstats/common/AppBar.dart';
import 'package:kzstats/common/Drawer.dart';
class Homepage extends StatefulWidget {
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
final String currentPage = 'KZStats';
var _latest = [];
Future<String> _getLatest() async {
var url =
'https://kztimerglobal.com/api/v2.0/records/top/recent?stage=0&tickrate=128&modes_list_string=kz_timer&limit=3';
var httpClient = new HttpClient();
var result;
try {
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode == HttpStatus.ok) {
var json = await response.transform(utf8.decoder).join();
var data = jsonDecode(json);
result = data;
} else {}
} catch (exception) {}
setState(() {
_latest = result;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: HomepageAppBar(currentPage),
drawer: HomepageDrawer(),
body: FutureBuilder<String>(
future: _getLatest(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return new Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
color: Colors.white,
margin: const EdgeInsets.all(20),
child: Text('${_latest[index]}'),
);
},
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Loading data from API...'),
)
],
),
);
}
},
),
floatingActionButton: Builder(builder: (builderContext) {
return FloatingActionButton(onPressed: () =>
_getLatest;
}),
);
}
}
The Correct Version of Your Code
I am trying to work out how I can get the following data into data table, but I have really struggled, as it keeps repeating the header
import 'dart:async';
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class PlayerList extends StatefulWidget {
#override
_PlayerListState createState() => new _PlayerListState();
}
class _PlayerListState extends State<PlayerList> {
StreamController<List<Map<String, dynamic>>> _postsController;
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
List<Map<String, dynamic>> _list = List();
ScrollController _scrollController = new ScrollController();
int count = 0;
var url = '';
bool fetching = false, endReached = false;
List data;
Future<List<dynamic>> fetchPlayer() async {
url =
'https://supercoach.heraldsun.com.au/2020/api/afl/classic/v1/players-cf?embed=notes%2Codds%2Cplayer_stats%2Cpositions';
final response = await http.get(url);
if (response.statusCode == 200) {
print('fetching player');
return json.decode(response.body);
} else {
throw Exception('Failed to load players');
}
}
loadPlayers() async {
fetchPlayer().then((res) async {
res.forEach((model) => _list.add(model));
_postsController.add(_list);
return res;
});
}
Future<Null> _handleRefresh() async {
fetching = true;
fetchPlayer().then((res) async {
if (res != null) {
res.forEach((model) => _list.add(model));
_postsController.add(_list);
if (res.length < 10) {
endReached = true;
_list.add(Map());
_postsController.add(_list);
}
} else {
_postsController.add(null);
}
fetching = false;
return null;
});
}
#override
void initState() {
_postsController = new StreamController();
loadPlayers();
_scrollController
..addListener(() {
var triggerFetchMoreSize =
0.9 * _scrollController.position.maxScrollExtent;
if (_scrollController.position.pixels > triggerFetchMoreSize &&
!fetching &&
!endReached) {
_handleRefresh();
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text('Exploring Players'),
),
backgroundColor: Color.fromRGBO(246, 249, 255, 1),
body: Column(children: <Widget>[
Expanded(
child: StreamBuilder(
stream: _postsController.stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
print('Has error: ${snapshot.hasError}');
print('Has data: ${snapshot.hasData}');
// print('Snapshot Data ${snapshot.data}');
print('Connection State ${snapshot.connectionState}');
if (snapshot.hasError) {
return Text(snapshot.error);
}
if (snapshot.connectionState == ConnectionState.waiting ||
snapshot.hasData == false) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(
Colors.purple)),
// Loader Animation Widget
Padding(padding: const EdgeInsets.only(top: 20.0)),
],
),
);
}
if (snapshot.data == null || snapshot.data.length == 0) {
return Column(
children: <Widget>[
Center(child: Text("Unable to find any players"))
],
);
}
// if (snapshot.hasData == false) {
// return Scaffold(
// backgroundColor: Colors.white,
// body: new Stack(
// fit: StackFit.expand,
// children: <Widget>[
// // Render the Title widget, loader and messages below each other
// new Column(
// mainAxisAlignment: MainAxisAlignment.start,
// children: <Widget>[
// Expanded(
// flex: 1,
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: <Widget>[
// CircularProgressIndicator(
// valueColor: AlwaysStoppedAnimation<Color>(
// Colors.purple)),
// // Loader Animation Widget
// Padding(
// padding:
// const EdgeInsets.only(top: 20.0)),
// Text("Finding players"),
// ],
// ),
// ),
// ],
// ),
// ],
// ),
// );
// }
if (snapshot.hasData) {
return Column(
children: <Widget>[
Expanded(
child: Scrollbar(
child: ListView.builder(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
Map<String, dynamic> player =
snapshot.data[index];
print(snapshot.data[index]);
return DataTable(columns: [
DataColumn(label: Text('Photo')),
DataColumn(label: Text('Player')),
DataColumn(label: Text('Round Results')),
DataColumn(label: Text('Played')),
DataColumn(label: Text('Total Points')),
DataColumn(label: Text('Avg')),
DataColumn(label: Text('3 Rd Avg')),
DataColumn(label: Text('5 Rd Avg')),
], rows: [
DataRow(cells: [
// TableRow(children: [
DataCell(Image.network(
"https://s.afl.com.au/staticfile/AFL%20Tenant/AFL/Players/ChampIDImages/XLarge2020/${player['feed_id']}.png?i10c=img.resize(scale_height:0.2)",
height: 54,
width: 54,
fit: BoxFit.fitWidth)),
DataCell(Column(children: [
Text(
"${player['first_name']} ${player['last_name']}"),
Text(
"${player['positions'][0]['position']}"),
Text(
"${player['player_stats'][0]['price']}")
])),
DataCell(Text(
"${player['player_stats'][0]['points']}")),
DataCell(Text(
"${player['player_stats'][0]['total_games']}")),
DataCell(Text(
"${player['player_stats'][0]['total_points']}")),
DataCell(Text(
"${player['player_stats'][0]['avg']}")),
DataCell(Text(
"${player['player_stats'][0]['avg3']}")),
DataCell(Text(
"${player['player_stats'][0]['avg5']}")),
])
]);
}),
),
),
],
);
}
if (!snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
return Text('No Players');
}
if (snapshot.connectionState != ConnectionState.done) {
return Center(
child: CircularProgressIndicator(),
);
}
return Text('No Data');
},
),
)
]));
}
}
import 'dart:async';
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PlayerList(),
);
}
}
class PlayerList extends StatefulWidget {
#override
_PlayerListState createState() => _PlayerListState();
}
class _PlayerListState extends State<PlayerList> {
StreamController<List<Player>> _postsController;
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
List<Player> _list = <Player>[];
ScrollController _scrollController = ScrollController();
int count = 0;
var url = '';
bool fetching = false, endReached = false;
List data;
Future<List<dynamic>> fetchPlayer() async {
url =
'https://supercoach.heraldsun.com.au/2020/api/afl/classic/v1/players-cf?embed=notes%2Codds%2Cplayer_stats%2Cpositions';
final response = await http.get(url);
if (response.statusCode == 200) {
print('fetching player');
return json.decode(response.body);
} else {
throw Exception('Failed to load players');
}
}
Future<void> loadPlayers() async {
await fetchPlayer().then((res) async {
for (var model in res) {
_list.add(Player.fromMap(model));
}
_postsController.add(_list);
});
}
Future<void> _handleRefresh() async {
fetching = true;
await fetchPlayer().then((res) async {
if (res != null) {
for (var model in res) {
_list.add(Player.fromMap(model));
}
_postsController.add(_list);
if (res.length < 10) {
endReached = true;
_postsController.add(_list);
}
} else {
_postsController.add(null);
}
fetching = false;
});
}
#override
void initState() {
_postsController = StreamController();
loadPlayers();
_scrollController
..addListener(() {
var triggerFetchMoreSize =
0.9 * _scrollController.position.maxScrollExtent;
if (_scrollController.position.pixels > triggerFetchMoreSize &&
!fetching &&
!endReached) {
_handleRefresh();
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text('Exploring Players'),
),
backgroundColor: Color.fromRGBO(246, 249, 255, 1),
body: StreamBuilder<List<Player>>(
stream: _postsController.stream,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text(snapshot.error);
}
if (snapshot.connectionState == ConnectionState.waiting ||
snapshot.hasData == false) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.purple)),
// Loader Animation Widget
Padding(padding: const EdgeInsets.only(top: 20.0)),
],
),
);
}
if (snapshot.data == null || snapshot.data.length == 0) {
return Column(
children: <Widget>[
Center(child: Text("Unable to find any players"))
],
);
}
if (snapshot.hasData) {
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
physics: ClampingScrollPhysics(),
scrollDirection: Axis.horizontal,
child: DataTable(
columns: [
DataColumn(label: Text('Photo')),
DataColumn(label: Text('Player')),
DataColumn(label: Text('Round Results')),
DataColumn(label: Text('Played')),
DataColumn(label: Text('Total Points')),
DataColumn(label: Text('Avg')),
DataColumn(label: Text('3 Rd Avg')),
DataColumn(label: Text('5 Rd Avg')),
],
rows: snapshot.data
.map(
(player) => DataRow(cells: [
DataCell(Image.network(
"https://s.afl.com.au/staticfile/AFL%20Tenant/AFL/Players/ChampIDImages/XLarge2020/${player.feedId}.png?i10c=img.resize(scale_height:0.2)",
height: 54,
width: 54,
fit: BoxFit.fitWidth)),
DataCell(Column(
children: <Widget>[
Text('${player.firstName} ${player.lastName}'),
Text(player.position),
Text(player.price.toString()),
],
)),
DataCell(
Text(player.points.toString()),
),
DataCell(Text(player.totalGames.toString())),
DataCell(Text(player.totalPoints.toString())),
DataCell(Text(player.avg.toString())),
DataCell(Text(player.avg3.toString())),
DataCell(Text(player.avg5.toString())),
]),
)
.toList(),
),
),
);
}
if (!snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
return Text('No Players');
}
if (snapshot.connectionState != ConnectionState.done) {
return Center(
child: CircularProgressIndicator(),
);
}
return Text('No Data');
},
));
}
}
class Player {
final String firstName;
final String lastName;
final String position;
final int price;
final int points;
final String feedId;
final int totalGames;
final int totalPoints;
final num avg;
final num avg3;
final num avg5;
Player({
#required this.firstName,
#required this.lastName,
#required this.position,
#required this.price,
#required this.points,
#required this.feedId,
#required this.totalGames,
#required this.totalPoints,
#required this.avg,
#required this.avg3,
#required this.avg5,
});
factory Player.fromMap(Map<String, dynamic> player) {
return Player(
avg: player['player_stats'][0]['avg'],
avg3: player['player_stats'][0]['avg3'],
avg5: player['player_stats'][0]['avg5'],
feedId: player['feed_id'],
firstName: player['first_name'],
lastName: player['last_name'],
points: player['player_stats'][0]['points'],
position: player['positions'][0]['position'],
price: player['player_stats'][0]['price'],
totalGames: player['player_stats'][0]['total_games'],
totalPoints: player['player_stats'][0]['total_points'],
);
}
}