I'm a beginner in Flutter and BLOC, i have a problem when trying to fetch Country List data.
Error was NoSuchMethodError: The getter 'bloc' was called on null.
below was my code. I'm just follow this tutorial https://www.youtube.com/watch?v=2DP3SEHucEk
this is Country List widget
class CountryList extends StatefulWidget {
#override
_CountryListState createState() => _CountryListState();
}
class _CountryListState extends State<CountryList> {
#override
void initState() {
Future.delayed(Duration.zero, () async {
final bloc = CountryRepo.of(context);
bloc.fetchCountry();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return CountryRepo(child: Builder(
builder: (context) {
final bloc = CountryRepo.of(context);
return StreamBuilder<List<CountryModel>>(
stream: bloc.countries,
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(child: CircularProgressIndicator());
return ListView.separated(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Row(
children: <Widget>[
Image.asset(snapshot.data[index].flag, width: 20),
SizedBox(width: 10),
Text(
"${snapshot.data[index].name} (+${snapshot.data[index].callingCodes})"),
],
),
onTap: () {
Navigator.pop(context);
},
);
},
separatorBuilder: (context, index) {
return Divider();
},
);
},
);
},
));
}
}
this is Country provider
import 'package:bpg/bloc/country_block.dart';
import 'package:flutter/material.dart';
class CountryRepo extends InheritedWidget {
final CountryBloc bloc;
CountryRepo({Key key, Widget child})
: bloc = CountryBloc(),
super(key: key, child: child);
static CountryBloc of(BuildContext context) {
return (context.dependOnInheritedWidgetOfExactType() as CountryRepo).bloc;
}
bool updateShouldNotify(_) => true;
}
and this Country BLOC
import 'package:bpg/model/country_model.dart';
import 'package:rxdart/rxdart.dart';
import 'package:http/http.dart' as http;
import 'dart:convert' as convert;
class CountryBloc {
final _countries = BehaviorSubject<List<CountryModel>>();
//Get Data
Stream<List<CountryModel>> get countries => _countries.stream;
//Set Data
Function(List<CountryModel>) get changeCountry => _countries.sink.add;
dispose() {
_countries.close();
}
Future<void> fetchCountry() async {
var response = await http.get("https://restcountries.eu/rest/v2/all");
var jsonResponse = convert.jsonDecode(response.body);
var countryJson = jsonResponse as List;
List<CountryModel> countries = List<CountryModel>();
countryJson
.forEach((country) => {countries.add(CountryModel.fromJson(country))});
changeCountry(countries);
countries.forEach((country) => print(country.name));
}
}
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.
can anybody show me how to replace the static list (_dataFromQuerySnapShot) with Firestore Firebase QuerySnapshot? Thank you!
`
import 'dart:async';
import 'package:flutter/material.dart';
class SearchWidget extends StatelessWidget {
const SearchWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
const TextField(onChanged: _filter),
StreamBuilder<List<User>>(
stream: _stream,
builder: (context, snapshot) {
return StreamBuilder<List<User>>(
key: ValueKey(snapshot.data),
initialData: snapshot.data,
stream: _stream,
builder: (context, snapshot) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data?.length,
itemBuilder: (BuildContext context, int index) {
final data = snapshot.data?[index]?.name;
return Text(data!);
},
);
},
);
},
)
],
),
);
}
}
StreamController<List<MyUser>> _streamController = StreamController<List<MyUser>>();
Stream<List<MyUser>> get _stream => _streamController.stream;
_filter(String searchQuery) async {
List<MyUser> _dataFromQuerySnapShot = await getData();
List<MyUser> _filteredList = _dataFromQuerySnapShot.where((MyUser user) => user.firstName!.toLowerCase().contains(searchQuery.toLowerCase())).toList();
_streamController.sink.add(_filteredList);
}
Future<List<MyUser>> getData() async {
List<MyUser> dataList = [];
CollectionReference myRef = FirebaseFirestore.instance.collection('users');
QuerySnapshot querySnapshot = await myRef.get();
dataList.addAll(querySnapshot.docs.map((d) => MyUser.fromJson(d.data() as Map<String, dynamic>)));
return dataList;
}
`
I tried to get data by Future<List> getData() {...} but List _filteredList = getData(); doesn't work as well as a lot of other tries. I only need to see a simple solution of my example working with firebase (StreamController a.s.o.) please. The best solution will include instead of searching in name searching in a String of 'firstName', 'lastName', and 'nickName'...
I'm trying to fetch api images into the card widgets in flutter. I have services, model and photos_page class. I tried lot's of ways but still doesn't work.
final photoItem = snapshot.data[index].previewURL;
I think problem is that snapshot can't get data
that's my photos_page:
import 'dart:convert';
import 'package:abcde/services/photos_model.dart';
import 'package:abcde/services/photos_service.dart';
import 'package:abcde/widgets/card_widget.dart';
import 'package:flutter/material.dart';
class PhotosPage extends StatefulWidget {
PhotosPage({Key? key}) : super(key: key);
static const String pathId = 'Photos page';
#override
State<PhotosPage> createState() => _PhotosPageState();
}
class _PhotosPageState extends State<PhotosPage> {
PhotosService photosService = PhotosService();
#override
void initState() {
// TODO: implement initState
super.initState();
photosService.getPhotos();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Photos'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(children: [
_getProductTypeList(),
]),
),
);
}
Widget _buildListItem(
BuildContext context, AsyncSnapshot<dynamic> snapshot, int index) {
final photoItem = snapshot.data[index].previewURL;
print('photoItem is $photoItem');
return photoCard(photoItem);
}
Widget _buildList(BuildContext context, AsyncSnapshot<dynamic> snapshot) {
var values = snapshot.data;
return ListView.builder(
// itemCount: snapshot.length,
itemBuilder: (context, index) {
return _buildListItem(context, snapshot, index);
},
);
/* return ListView(
children: snapshot!.map((data) => _buildListItem(context, data)).toList(),
);*/
}
//PhotosModel photosModel = PhotosModel();
Widget _getProductTypeList() {
return Expanded(
child: FutureBuilder(
future: photosService.getPhotos(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: LinearProgressIndicator(),
);
}
return _buildList(context, snapshot);
},
),
);
}
}
that's my model class:
class PhotosModel {
String previewURL;
PhotosModel({required this.previewURL});
/* factory PhotosModel.fromJson(Map<dynamic, dynamic> json) => PhotosModel(
previewURL: json['previewURL'] as String,
);
Map<String, dynamic> toJson() {
return <String, dynamic>{'previewURL': previewURL};
}*/
/* #override
String toString() {
return 'PhotosModel{url: $previewURL}';
}*/
/*factory PhotosModel.fromJson(Map<String, dynamic> json) {
return PhotosModel(url: json['url'] as String);
}*/
factory PhotosModel.fromJson(Map<dynamic, dynamic> json) =>
_commentFromJson(json);
Map<dynamic, dynamic> toJson() => photosModelToJson(this);
}
PhotosModel _commentFromJson(Map<dynamic, dynamic> json) {
return PhotosModel(
previewURL: json['previewURL'],
);
}
Map<dynamic, dynamic> photosModelToJson(PhotosModel instance) => <dynamic, dynamic>{
'previewURL': instance.previewURL,
};
and that's my service class. I make a list getPhotos() method but doesn't work anyway.
import 'dart:convert';
import 'package:abcde/services/photos_model.dart';
import 'package:http/http.dart';
const MY_API_KEY = '26711456-bde74f403cb42e77029bc1678';
const appUrl = 'https://pixabay.com/api/';
class PhotosService {
Future getData(String url) async {
print('Calling url: $url');
final response = await get(Uri.parse(url));
if (response.statusCode == 200) {
return response.body;
} else {
print(response.statusCode);
}
}
List<PhotosModel> dataList = [];
Future<List<PhotosModel>> getPhotos({String? query}) async {
final photosData = await getData(
'$appUrl?&key=$MY_API_KEY&q=$query');
var data =json.decode(photosData);
data.forEach((element) {
dataList.add(PhotosModel.fromJson(element));
});
print('this is photos data: $photosData');
return dataList;
}
}
that's the error it shows me
The following NoSuchMethodError was thrown building:
The method '[]' was called on null.
Receiver: null
Tried calling:
and that's my card widget
import 'package:flutter/material.dart';
Widget photoCard(String url) {
return Card(
child: Image(
image: NetworkImage(url),
),
);
}
for api I use this web-page:
https://pixabay.com/api/docs/#api_search_images
thank you very much in advance
Working Example
main.dart
===============
import 'package:demo_app/photo_page.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const PhotosPage(),
);
}
}
==============
photo_service.dart
====================
import 'dart:convert';
import 'package:http/http.dart';
const apiKey = '26711456-bde74f403cb42e77029bc1678';
const appUrl = 'https://pixabay.com/api/';
class PhotosService {
Future getData(String url) async {
print('Calling url: $url');
final response = await get(Uri.parse(url));
if (response.statusCode == 200) {
return response.body;
} else {
print(response.statusCode);
}
}
List<PhotosModel> dataList = [];
Future<List<PhotosModel>> getPhotos(String query) async {
final photosData = await getData('$appUrl?key=$apiKey&q=$query');
var data = json.decode(photosData);
var items = data["hits"];
items.forEach((element) {
dataList.add(PhotosModel.fromJson(element));
});
print('this is photos data: $photosData');
return dataList;
}
}
class PhotosModel {
String previewURL;
PhotosModel({required this.previewURL});
factory PhotosModel.fromJson(Map<dynamic, dynamic> json) =>
_commentFromJson(json);
Map<dynamic, dynamic> toJson() => photosModelToJson(this);
}
PhotosModel _commentFromJson(Map<dynamic, dynamic> json) {
return PhotosModel(
previewURL: json['previewURL'],
);
}
Map<dynamic, dynamic> photosModelToJson(PhotosModel instance) =>
<dynamic, dynamic>{
'previewURL': instance.previewURL,
};
=========================
photo_page.dart
=============
import 'package:demo_app/photo_service.dart';
import 'package:flutter/material.dart';
class PhotosPage extends StatefulWidget {
const PhotosPage({Key? key}) : super(key: key);
static const String pathId = 'Photos page';
#override
State<PhotosPage> createState() => _PhotosPageState();
}
class _PhotosPageState extends State<PhotosPage> {
PhotosService photosService = PhotosService();
#override
void initState() {
super.initState();
photosService.getPhotos("flower");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Photos'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(children: [
_getProductTypeList(),
]),
),
);
}
Widget _buildListItem(
BuildContext context, AsyncSnapshot<dynamic> snapshot, int index) {
final photoItem = snapshot.data[index].previewURL;
print('photoItem is $photoItem');
return photoCard(photoItem);
}
Widget _buildList(BuildContext context, AsyncSnapshot<dynamic> snapshot) {
var values = snapshot.data;
return ListView.builder(
itemCount: snapshot.hasData ? snapshot.data.length : 0,
itemBuilder: (context, index) {
return _buildListItem(context, snapshot, index);
},
);
}
Widget _getProductTypeList() {
return Expanded(
child: FutureBuilder(
future: photosService.getPhotos("flower"),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: LinearProgressIndicator(),
);
}
return _buildList(context, snapshot);
},
),
);
}
}
Widget photoCard(String url) {
return Card(
child: Image(
image: NetworkImage(url),
),
);
}
I am getting error The method '[]' can't be unconditionally invoked because the receiver can be 'null'.
Mydata inside _categoriesList future
[
{
"business_category_id": 1,
"business_category": "Manufacturer",
"business_category_image": "164423936662011a06c450d.png"
},
{
"business_category_id": 2,
"business_category": "Distributor",
"business_category_image": "164423937762011a11033aa.png"
},
{
"business_category_id": 3,
"business_category": "Wholesaler",
"business_category_image": "164423938762011a1bb2e3c.png"
},
{
"business_category_id": 4,
"business_category": "Retailer",
"business_category_image": "164423940062011a28189e5.png"
},
{
"business_category_id": 5,
"business_category": "Reseller",
"business_category_image": "164423941362011a3554148.png"
},
{
"business_category_id": 6,
"business_category": "Service Provider",
"business_category_image": "164423942462011a4096996.png"
}
]
my code inside futurebuilder. I am having problem accessing snapshot.data[index]
FutureBuilder(
future: _categoriesList,
builder: (_, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: 6,
itemBuilder: (_, index) {
return Text(snapshot.data[index]); // error The method '[]' can't be unconditionally invoked because the receiver can be 'null'
});
}
},
)
Here is the example for ListView.builder:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future <List<Data>> fetchData() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/albums');
if (response.statusCode == 200) {
List jsonResponse = json.decode(response.body);
return jsonResponse.map((data) => new Data.fromJson(data)).toList();
} else {
throw Exception('Unexpected error occured!');
}
}
class Data {
final int userId;
final int id;
final String title;
Data({this.userId, this.id, this.title});
factory Data.fromJson(Map<String, dynamic> json) {
return Data(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future <List<Data>> futureData;
#override
void initState() {
super.initState();
futureData = fetchData();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter API and ListView Example',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter ListView'),
),
body: Center(
child: FutureBuilder <List<Data>>(
future: futureData,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Data> data = snapshot.data;
return
ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 75,
color: Colors.white,
child: Center(child: Text(data[index].title),
),);
}
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default show a loading spinner.
return CircularProgressIndicator();
},
),
),
),
);
}
}
Specify your data-type for list in FutureBuilder first.
FutureBuilder<List<String>>()
//
Check if you are getting data. print the data using snapshot.data.
FutureBuilder<List<String>>(
future: _categoriesList,
builder: (_, snapshot) {
if (snapshot.hasData) {
print("snapshot:: data >>>> ${snapshot.data}");
return ListView.builder(
itemCount: 6,
itemBuilder: (_, index) {
return Text(snapshot.data[index]); // error The method '[]' can't be unconditionally invoked because the receiver can be 'null'
});
}
},
)
Assuming you have a Category class, with variables for the CategoryId,CategoryName,CategoryImage with all the needed methods as shown in https://stackoverflow.com/a/71029334/12371668
//create an instance of the category class before the build() method
final category = Category();
#override
Widget build(BuildContext context) {
// other code
FutureBuilder(
future: category.fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Category> categories = snapshot.data();
return ListView.builder(
itemCount: categories.length,
itemBuilder: (context, index) {
var category = categories[index];
return Text(category.CategoryName);
});
}
},
)
}
it take about 7 days trying make a working example for lazyload listview with provider in flutter with real world example and it's still not working because i think something is missing
As a note : the first load , works good and when i scroll it's print (scroll) but nothing happened it's still in the same page
if i try to return _todolist variable in the _onScrollUpdated it not change page correctly and after three times i see this error
E/flutter ( 7713): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)]
Unhandled Exception: type 'String' is not a subtype of type
'List' E/flutter ( 7713): #0 TodoService.fetchTodos
(package:flutter_todo_provider/services/todo_service.dart:32:21)
json example
https://jsonformatter.org/52c83e
todos_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_todo_provider/helpers/http_exception.dart';
import 'package:provider/provider.dart';
import 'package:flutter_todo_provider/.env.dart';
import 'package:flutter_todo_provider/services/todo_service.dart';
class TodosScreen extends StatefulWidget {
#override
_TodosScreenState createState() => _TodosScreenState();
}
class _TodosScreenState extends State<TodosScreen> {
ScrollController _controller;
List<dynamic> _todoList;
bool _isLoading ;
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(_onScrollUpdated);
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Configuration.AppName),
),
body: FutureBuilder(
future: _fetchListItems(),
builder: (context, snapshot){
if(snapshot.hasData){
return _listItems(snapshot.data);
}
return _buildProgressIndicator();
}
),
);
}
_fetchListItems() async {
try {
await Provider.of<TodoService>(context, listen: false).loadNextPage();
_todoList = Provider.of<TodoService>(context, listen: false).items;
} on HttpException catch (e) {
EasyLoading.showError(e.message);
}
return _todoList ;
}
Widget _listItems(data){
_isLoading = Provider.of<TodoService>(context, listen: false).isLoading ;
return ListView.builder(
controller: _controller,
itemCount: data.length ,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index].title),
subtitle:Text(data[index].content),
trailing: Icon(Icons.print),
);
},
);
}
Future<void> _onScrollUpdated() async {
print("Scroll11");
var maxScroll = _controller.position.maxScrollExtent;
var currentPosition = _controller.position.pixels;
if (currentPosition == maxScroll ) {
try {
await Provider.of<TodoService>(context, listen: false).loadNextPage();
_todoList = Provider.of<TodoService>(context, listen: false).items;
// return _todoList ; if use this line i see the error
} on HttpException catch (e) {
EasyLoading.showError(e.message);
}
}
}
Widget _buildProgressIndicator() {
_isLoading = Provider.of<TodoService>(context, listen: false).isLoading ;
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: _isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
}
}
todo_service.dart
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_todo_provider/.env.dart';
import 'package:flutter_todo_provider/models/todo.dart';
class TodoService with ChangeNotifier {
bool isLoading = false;
bool isFetching = false;
int currentPage = 1;
int totalRows = 10;
List<Todo> items = [];
loadNextPage() async {
await fetchTodos(currentPage);
currentPage++;
notifyListeners();
}
Future fetchTodos(int currentPage) async {
try {
//404
var options = Options(headers: {
HttpHeaders.authorizationHeader: 'Basic ${Configuration.authToken}'
});
Map<String, dynamic> qParams = {
'current_page': currentPage,
};
Response response = await Dio().get('${Configuration.ApiUrl}/todos/my_todos', options: options, queryParameters: qParams);
List<dynamic> responseBode = response.data["data"];
responseBode.forEach(( dynamic json) {
items.add(Todo.fromJson(json));
});
notifyListeners();
} on DioError catch (e) {
print("Error Message" + e.response.statusMessage);
return items=[];
}
}
}
Here is the code:
class TodoScreen extends StatefulWidget {
// Your service goes here
// (the class extending ChangeNotifier)
#override
_TodoScreenState createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final TodoService todoService = TodoService();
ScrollController _controller;
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(_onScrollUpdated);
loadNextPage();
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Configuration.AppName'),
),
body: ChangeNotifierProvider.value(
value: todoService,
child: Consumer<TodoService>(builder: (_, ctl, __) {
if (todoService.isLoading) {
return _buildProgressIndicator();
} else {
return _listItems(todoService.items);
}
}),
),
);
}
Widget _listItems(data) {
return ListView.builder(
controller: _controller,
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index].title),
subtitle: Text(data[index].content),
trailing: Icon(Icons.print),
);
},
);
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: CircularProgressIndicator(),
),
);
}
Future<void> _onScrollUpdated() async {
var maxScroll = _controller.position.maxScrollExtent;
var currentPosition = _controller.position.pixels;
if (currentPosition == maxScroll) {
todoService.loadNextPage();
}
}
}
Note that i didn't make changes to your service. The notifyListeners will do all the job for us.
When you are using Provider, the idea is to keep all your data inside the controller or service (the class that extends ChangeNitifier) and just use the variables with notifyListeners to change the behavior of your screen.
The screen needs to be listening for changes, for this we use the pair ChangeNotifierProvider.value with Consumer(builder: (_, ctl, __) {}).
Use ChangeNotifierProvider in some upper level of the widget tree and use Consumer only where you need the widget to be updated. You can even use more than one Consumer, they all just need to be under ChangeNotifierProvider.