I've follow the instructions of the Example Weather App for the Pull to Refresh feature, and the RefreshIndicator is not hidden after its does the function and request the data.
Here is a video of the behavior:
Video of the app with RefreshIndicator
As you can see My Code is something similar to the example of the WeatherApp in the part of the Refresh.
What am I doing wrong?
Thanks for the help...
Here is my Bloc.
import 'package:rxdart/rxdart.dart';
import 'package:bloc/bloc.dart';
import 'listing_event.dart';
import 'listing_state.dart';
import '../../repository/listing_repository.dart';
class ListingBloc extends Bloc<ListingEvent, ListingState> {
final ListingRepository _listingRepository = ListingRepository();
@override
Stream<ListingState> transform(
Stream<ListingEvent> events,
Stream<ListingState> Function(ListingEvent event) next,
) {
return super.transform(
(events as Observable<ListingEvent>).debounceTime(
Duration(milliseconds: 500),
),
next,
);
}
@override
get initialState => ListingUninitialized();
@override
Stream<ListingState> mapEventToState(ListingEvent event) async* {
if (event is Refresh) {
try {
final result = await _listingRepository.fetchListingByCityAndCategory(shouldReload: true, category: event.category, city: event.location);
yield ListingLoaded(items: result.listings, hasReachedMax: false);
} catch (_) {
print(_.toString());
yield ListingError();
}
}
if (event is Fetch && !_hasReachedMax(currentState)) {
try {
if (currentState is ListingUninitialized) {
final result = await _listingRepository.fetchListingByCityAndCategory(category: event.category, city: event.location);
yield ListingLoaded(items: result.listings, hasReachedMax: false);
}
if (currentState is ListingLoaded) {
final result =
await _listingRepository.fetchNextPageListingByCityAndCategory(category: event.category, city: event.location, start: (currentState as ListingLoaded).items.length);
yield result.listings.isEmpty
? (currentState as ListingLoaded).copyWith(hasReachedMax: true)
: ListingLoaded(
items: (currentState as ListingLoaded).items + result.listings,
hasReachedMax: false,
);
}
} catch (_) {
print(_.toString());
yield ListingError();
}
}
}
bool _hasReachedMax(ListingState state) =>
state is ListingLoaded && state.hasReachedMax;
}
here my provider and my repository
class ListingProvider {
Client client = Client();
Future<Response> fetchListingsByCityAndCategory({location, category, start}) async {
final url = new Uri(
scheme: 'https',
host: 'clisgo.com',
path: '/wp-json/wp/v2/listing/',
queryParameters: {
'offset': '$start',
'_embed': '1',
'location': '$location',
'listing-category': '$category'
}
);
final response = await client.get(url);
print(response.headers.toString());
if (response.statusCode == 200) {
return response;
} else {
throw Exception("Failed to Load the Listings");
}
}
}
class ListingRepository {
final listingProvider = ListingProvider();
final cache = Cache();
Future<Listings> fetchListingByCityAndCategory({ city, category, int start = 0, bool shouldReload = false }) async {
String listingKey = "listing_$city$category$start";
final removed = shouldReload ? await cache.remove(listingKey) : false;
final containCache = await cache.check(key: listingKey);
if (containCache) {
final listingCache = await cache.get(key: listingKey);
final decoded = json.decode(listingCache);
final jsonResponse = json.decode(decoded['json']);
return Listings.fromJSON(
json: jsonResponse,
totalItem: decoded['totalItem'],
totalPages: decoded['totalPages']
);
} else {
final result = await listingProvider.fetchListingsByCityAndCategory(category: category, location: city, start: start);
var jsonResponse = json.decode(result.body);
final toBeCached = {
'json': result.body,
'totalItem': result.headers["X-WP-Total"],
'totalPages': result.headers["X-WP-TotalPages"]
};
final cached = await cache.save(key: listingKey, value: json.encode(toBeCached));
return Listings.fromJSON(
json:jsonResponse,
totalItem: result.headers["X-WP-Total"],
totalPages: result.headers["X-WP-TotalPages"]
);
}
}
}
here my View
import 'dart:async';
import 'package:Clisgo/blocs/listing/listing_bloc.dart';
import 'package:Clisgo/blocs/listing/listing_event.dart';
import 'package:Clisgo/blocs/listing/listing_state.dart';
import 'package:Clisgo/ui/screens/detail_listing_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// import 'package:flutter_advanced_networkimage/provider.dart';
import '../widgets/listing_card.dart';
// import '../screens/category_screen.dart';
class ListingsScreen extends StatefulWidget {
ListingsScreen({this.locationId, this.categoryId, this.nameCategory});
final String locationId;
final String categoryId;
final String nameCategory;
@override
_ListingsScreenState createState() => _ListingsScreenState();
}
class _ListingsScreenState extends State<ListingsScreen> {
Completer<void> _refreshCompleter;
ListingBloc _listingBloc;
final _scrollController = ScrollController();
final _scrollThreshold = 200.0;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
_refreshCompleter = Completer<void>();
_listingBloc = ListingBloc();
_listingBloc.dispatch(Fetch(location: widget.locationId, category: widget.categoryId));
}
@override
void dispose() {
_listingBloc.dispose();
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.nameCategory),
),
body: BlocBuilder<ListingBloc, ListingState>(
bloc: _listingBloc,
builder: (BuildContext context, ListingState state) {
if (state is ListingError) {
return Center(
child: Text('No se pudieron cargar los Servicios, intenta conectarte a internet.'),
);
}
if (state is ListingLoaded) {
if (state.items.isEmpty) {
return Center(
child: Text('No hay Servicios'),
);
}
return RefreshIndicator(
onRefresh: () {
_listingBloc.dispatch(
Refresh(category: widget.categoryId, location: widget.locationId)
);
return _refreshCompleter.future;
},
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
// shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return index >= state.items.length
? Container(
alignment: Alignment.center,
child: Center(
child: CircularProgressIndicator(),
),
)
: InkWell(
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) => ListingDetailScreen(listing: state.items[index])
));
},
child: ListingCard(item: state.items[index])
);
},
itemCount: state.hasReachedMax
? state.items.length
: state.items.length + 1,
controller: _scrollController,
),
);
}
return Center(
child: CircularProgressIndicator(),
);
},
)
);
}
void _onScroll() {
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels;
if (maxScroll - currentScroll <= _scrollThreshold) {
_listingBloc.dispatch(Fetch(category: widget.categoryId, location: widget.locationId));
}
}
}
Hi @iCueto 馃憢
Thanks for opening an issue!
I think you are missing completing the Completer. In the weather example, we use BlocListener to detect when the bloc has finished and then complete and reset the Completer.
BlocListener<WeatherBloc, WeatherState>(
listener: (context, state) {
if (state is WeatherLoaded) {
BlocProvider.of<ThemeBloc>(context).dispatch(
WeatherChanged(condition: state.weather.condition),
);
_refreshCompleter?.complete();
_refreshCompleter = Completer();
}
},
...
Hope that helps 馃憤
Thank a lot!!! @felangel... It's Work!!!
I have missing that part... Thanks...
Most helpful comment
Hi @iCueto 馃憢
Thanks for opening an issue!
I think you are missing completing the
Completer. In the weather example, we useBlocListenerto detect when the bloc has finished and then complete and reset theCompleter.Weather Example Source
Hope that helps 馃憤