Flutter : scrollController.isAttached is always false - flutter

How can I scroll to a special widget in a ListView? For example, I want to automatically scroll to some container in ListView if I press a certain button on a previous screen. I will pass to the next screen an Id (from id I will know the index) and when I navigate to the next screen I want to automatically scroll to this widget.
the code in main screen : Navigator.push(context, MaterialPageRoute(builder: (_) => CreatedEstatesScreen(estateId: id)));
the code in the next screen :
class RecentEstateOrdersScreen extends StatefulWidget {
static const String id = "RecentEstateOrdersScreen";
String? estateId;
RecentEstateOrdersScreen({Key? key, this.estateId}) : super(key: key);
#override
_RecentEstateOrdersScreenState createState() =>
_RecentEstateOrdersScreenState();
}
class _RecentEstateOrdersScreenState extends State<RecentEstateOrdersScreen> {
late RecentEstatesOrdersBloc _recentEstatesOrdersBloc;
late ItemScrollController scrollController;
late ItemPositionsListener itemPositionsListener;
String? userToken;
List<EstateOrder> orders = [];
#override
void initState() {
super.initState();
_recentEstatesOrdersBloc = RecentEstatesOrdersBloc(EstateOrderRepository());
_onRefresh();
User? user = BlocProvider.of<UserLoginBloc>(context).user;
if (user != null && user.token != null) {
userToken = user.token;
}
scrollController = ItemScrollController();
itemPositionsListener = ItemPositionsListener.create();
}
_onRefresh() {
if (BlocProvider.of<UserLoginBloc>(context).user!.token != null) {
_recentEstatesOrdersBloc.add(
RecentEstatesOrdersFetchStarted(
token: BlocProvider.of<UserLoginBloc>(context).user!.token!),
);
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(
AppLocalizations.of(context)!.recent_created_orders,
),
),
body: BlocConsumer<RecentEstatesOrdersBloc, RecentEstatesOrdersState>(
bloc: _recentEstatesOrdersBloc,
listener: (context, recentOrdersState) async {
if (recentOrdersState is RecentEstatesOrdersFetchError) {
var error = recentOrdersState.isConnectionError
? AppLocalizations.of(context)!.no_internet_connection
: recentOrdersState.error;
await showWonderfulAlertDialog(
context, AppLocalizations.of(context)!.error, error);
}
},
builder: (BuildContext context, recentOrdersState) {
if (recentOrdersState is RecentEstatesOrdersFetchProgress) {
return const ClientsOrdersShimmer();
}
if (recentOrdersState is! RecentEstatesOrdersFetchComplete) {
return Container();
}
orders = recentOrdersState.estateOrders;
if (orders.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
documentOutlineIconPath,
width: 0.5.sw,
height: 0.5.sw,
color: Theme.of(context)
.colorScheme
.onBackground
.withOpacity(0.42),
),
48.verticalSpace,
Text(
AppLocalizations.of(context)!.have_not_recent_orders,
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
if (widget.estateId != null) {
SchedulerBinding.instance!.addPostFrameCallback((_) {
jumpToOrder(orders);
});
}
return RefreshIndicator(
color: Theme.of(context).colorScheme.primary,
onRefresh: () async {
_onRefresh();
},
child: ListView.builder(
itemCount: orders.length,
itemBuilder: (_, index) {
return EstateOrderCard(
estateOrder: orders.elementAt(index),
);
}),
);
},
),
),
);
}
jumpToOrder(List<EstateOrder> orders) {
int index = getIndexFromId(orders);
if (index != -1) {
if (scrollController.isAttached) {
scrollController.scrollTo(
index: index,
duration: const Duration(seconds: 2),
curve: Curves.easeInOutCubic);
}
}
}
getIndexFromId(List<EstateOrder> orders) {
for (int i = 0; i < orders.length; i++) {
if (orders.elementAt(i).id == int.parse(widget.estateId!)) {
return i;
}
}
return -1;
}
}```

If you are using the library then you have to use ScrollablePositionedList.builder, not the normal ListView.builder.

Related

flutter lazyload loading page twice

so im trying to build a lazyload, from my backend i already made per page 5 items. but i dont understand why when i try in flutter the first page is always loaded twice?
for example if i scroll up to page 5, the order of the page will be 1,1,2,3,4 then when i try to refresh, it will show page 5, refresh again then it will show page 1, any idea why?
class ProgressScreen extends StatefulWidget {
#override
_ProgressScreenState createState() => _ProgressScreenState();
}
class _ProgressScreenState extends State<ProgressScreen> {
late MainProgressStore _mainProgressStore;
late UserStore _userStore;
int currentPage = 1;
late int totalPages;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
// initializing stores
_mainProgressStore = Provider.of<MainProgressStore>(context);
_userStore = Provider.of<UserStore>(context);
if (!_userStore.loading) {
_userStore.getUser();
}
if (!_mainProgressStore.loading) {
_mainProgressStore.postMainProgress(
'loading', 'all', 'desc', 3, 0, currentPage);
}
}
List<Datum> mainProgress = [];
final RefreshController refreshController =
RefreshController(initialRefresh: true);
Future<bool> getmainProgress({bool isRefresh = false}) async {
if (isRefresh) {
currentPage = 1;
} else {
if (currentPage >= totalPages) {
refreshController.loadNoData();
return false;
}
}
final response = await _mainProgressStore.postMainProgress(
'loading', 'all', 'desc', 3, 0, currentPage);
if (_mainProgressStore.success == true) {
final result = _mainProgressStore.mainProgress!.mainProgress;
if (isRefresh) {
mainProgress = result.response;
} else {
mainProgress.addAll(result.response);
}
currentPage++;
totalPages = 10;
print(result.response);
setState(() {});
return true;
} else {
return false;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppNavBar(),
drawer: DrawerNavBar(userStore: _userStore),
body: _buildMainContent(),
);
}
Widget _buildMainContent() {
return Observer(
builder: (context) {
return _mainProgressStore.success
? _buildRefresh(_mainProgressStore)
: CustomProgressIndicatorWidget();
},
);
}
Widget _buildRefresh(_mainProgressStore) {
return Platform.isIOS
? _buildIOSList(_mainProgressStore)
: _refreshView(_mainProgressStore);
}
Widget _refreshView(_mainProgressStore) {
return SmartRefresher(
controller: refreshController,
enablePullUp: true,
onRefresh: () async {
final result = await getmainProgress(isRefresh: true);
if (result) {
refreshController.refreshCompleted();
} else {
refreshController.refreshFailed();
}
},
onLoading: () async {
final result = await getmainProgress();
if (result) {
refreshController.loadComplete();
} else {
refreshController.loadFailed();
}
},
child: _buildBody(_mainProgressStore));
}
Widget _buildIOSList(_mainProgressStore) {
return Container(
child: CustomScrollView(
slivers: [
CupertinoSliverRefreshControl(
onRefresh: () async {
_mainProgressStore.getHomepage(currentPage);
},
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return _buildBody(_mainProgressStore);
}, childCount: _mainProgressStore.response.length))
],
),
);
}
Widget _buildBody(_mainProgressStore) {
return SingleChildScrollView(
child: Column(children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Container(
padding: EdgeInsets.only(
top: DeviceUtils.getScaledHeight(context, 0.03),
left: DeviceUtils.getScaledWidth(context, 0.06),
bottom: DeviceUtils.getScaledHeight(context, 0.03)),
child: Text(
'MY ORDERS',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: DeviceUtils.getScaledWidth(context, 0.035),
color: Colors.black),
),
),
),
SearchProgress(
currentPage: currentPage, mainProgressStore: _mainProgressStore),
OrderInfo(
progressData: mainProgress, mainProgressStore: _mainProgressStore),
]));
}
}

When I switch tabs, the content (StreamBuilder) of the previous tab is lost, leaving a blank page

I have two tabs, in each one the content is a StreamBuilder.
The StreamBuilder returns a column with a text field to search on a ListView and another StreamBuilder, the latter returns a ListView. When I move to another tab and subsequently return to the previous one, leaving a blank page.
When I move to another tab and subsequently return to the previous one, the content is lost, leaving a blank page. How can you avoid this?
Here is the relevant code:
class ListaCredencialesCapturistaPantalla extends StatefulWidget {
static const String id = "ListaCredencialesCapturistaPantalla";
const ListaCredencialesCapturistaPantalla({Key? key}) : super(key: key);
#override
_ListaCredencialesCapturistaPantallaState createState() => _ListaCredencialesCapturistaPantallaState();
}
class _ListaCredencialesCapturistaPantallaState
extends State<ListaCredencialesCapturistaPantalla>
with SingleTickerProviderStateMixin {
final List<CredencialModelo> _listaCredencialesActivos = <CredencialModelo>[];
final List<CredencialModelo> _listaCredencialesFinados = <CredencialModelo>[];
final StreamController<List<CredencialModelo>> _controladorStreamActivos = StreamController<List<CredencialModelo>>();
Stream<List<CredencialModelo>> get _streamActivos => _controladorStreamActivos.stream;
final StreamController<List<CredencialModelo>> _controladorStreamFinados = StreamController<List<CredencialModelo>>();
Stream<List<CredencialModelo>> get _streamFinados => _controladorStreamFinados.stream;
bool get wantKeepAlive => true;
#override
void initState() {
super.initState();
}
#override
void dispose() {
_controladorStreamActivos.close();
_controladorStreamFinados.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return _construyeInterfaz();
}
Widget _construyeInterfaz() {
return DefaultTabController(
length: 2,
child: Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
title: const Text('Listado de Credenciales'),
bottom: const TabBar(
tabs: <Widget>[
Tab(
icon: Constantes.ICONO_USUARIO_ACTIVO,
text: 'Activo',
),
Tab(
icon: Constantes.ICONO_USUARIO_FINADO,
text: 'Finado',
),
],
),
),
body: TabBarView(
children: <Widget>[
_construyeSeccionBusquedaListado(EstatusUsuario.activo),
_construyeSeccionBusquedaListado(EstatusUsuario.finado),
],
),
floatingActionButton: FloatingActionButton(
child: Constantes.ICONO_AGREGAR,
onPressed: () {
Navigator.pushNamed(context, AgregaCredencialPantalla.id);
},
),
),
);
}
Widget _construyeSeccionBusquedaListado(EstatusUsuario estatusUsuario) {
return StreamBuilder(
stream: Firestore.listaCredenciales(estatusUsuario),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.data!.docs.isEmpty) {
return Center(
child: Column(
children: const <Widget>[
Icon(FontAwesomeIcons.exclamationCircle),
Text('Sin credenciales'),
],
),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(color: Constantes.COLOR_INTERFAZ,),
);
}
List<CredencialModelo> listaCredenciales = <CredencialModelo>[];
for (QueryDocumentSnapshot<Map<String, dynamic>> elemento in snapshot.data!.docs) {
listaCredenciales.add(CredencialModelo.fromMapCredencial(elemento.data()));
}
if (estatusUsuario == EstatusUsuario.activo) {
_listaCredencialesActivos.clear();
_listaCredencialesActivos.addAll(listaCredenciales);
}
else {
_listaCredencialesFinados.clear();
_listaCredencialesFinados.addAll(listaCredenciales);
}
return _construyeBusquedaListado(estatusUsuario);
}
);
}
Widget _construyeBusquedaListado(EstatusUsuario estatusUsuario) {
return Column(
children: [
_construyeCampoBusqueda(estatusUsuario),
_construyeListaFiltrable(
estatusUsuario,
estatusUsuario == EstatusUsuario.activo ?
_listaCredencialesActivos :
_listaCredencialesFinados
),
],
);
}
Widget _construyeCampoBusqueda(EstatusUsuario estatusUsuario) {
return RoundedIconTextFormField(
textCapitalization: TextCapitalization.characters,
inputFormatters: [FormateoTextoMayusculasInput()],
labelText: 'Búsqueda',
prefixIcon: Constantes.DATO_ICONO_BUSQUEDA,
onChanged: (String busqueda) {
_busquedaCredencial(busqueda.toUpperCase(), estatusUsuario);
},
validator: (String? busqueda) {},
);
}
void _busquedaCredencial(String busqueda, EstatusUsuario estatusUsuario) {
if (busqueda.isNotEmpty) {
List<CredencialModelo> listaResultados =
(estatusUsuario == EstatusUsuario.activo ?
_listaCredencialesActivos :
_listaCredencialesFinados
).where(
(elemento) {
return elemento.curp!.contains(busqueda) || elemento.apePat!.contains(busqueda) ||
elemento.apeMat!.contains(busqueda) || elemento.nombre!.contains(busqueda) ||
elemento.municipio!.toString().contains(busqueda);
}
).toList();
(estatusUsuario == EstatusUsuario.activo ?
_controladorStreamActivos :
_controladorStreamFinados).sink.add(listaResultados);
}
else {
(estatusUsuario == EstatusUsuario.activo ?
_controladorStreamActivos :
_controladorStreamFinados
)
.sink
.add(estatusUsuario == EstatusUsuario.activo ?
_listaCredencialesActivos :
_listaCredencialesFinados
);
}
}
Widget _construyeListaFiltrable(EstatusUsuario estatusUsuario, List<CredencialModelo> listaCredenciales) {
return StreamBuilder<List<CredencialModelo>>(
key: ValueKey(listaCredenciales),
initialData: listaCredenciales,
stream: estatusUsuario == EstatusUsuario.activo ? _streamActivos : _streamFinados,
builder: (BuildContext context, AsyncSnapshot<List<CredencialModelo>> snapshot) {
if (snapshot.data!.isEmpty) {
return Center(
child: Column(
children: const <Widget>[
Icon(FontAwesomeIcons.exclamationCircle),
Text('Sin resultados'),
],
),
);
}
return _construyeListaCredenciales(snapshot.data!);
},
);
}
Widget _construyeListaCredenciales(List<CredencialModelo> listaCredenciales) {
return Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: listaCredenciales.length,
itemBuilder: (context, position) {
return _construyeCredencial(listaCredenciales[position]);
}
),
);
}
}
You can do it this way...
Create a StatefulWidget (it's technically a new page) instead of a simple Widget.
Something Like that:
class ConstruyeSeccionBusquedaListado extends StatefulWidget {
final EstatusUsuario estatusUsuario;
const ConstruyeSeccionBusquedaListado({Key? key, required this.estatusUsuario}) : super(key: key);
#override
State<ConstruyeSeccionBusquedaListado> createState() => _construyeSeccionBusquedaListado();
}
class _construyeSeccionBusquedaListado extends State<ConstruyeSeccionBusquedaListado> with AutomaticKeepAliveClientMixin<ConstruyeSeccionBusquedaListado> {
#override
Widget build(BuildContext context) {
return StreamBuilder<Object>(
stream: null,
builder: (context, snapshot) {
return Container();
}
);
}
#override
bool get wantKeepAlive => true;
}
And on your TabBarView do the following:
TabBarView(
children: [
ConstruyeSeccionBusquedaListado(estatusUsuario: EstatusUsuario.activo),
],
)
Don't forget to put the:
with AutomaticKeepAliveClientMixin<>
and the:
#override
bool get wantKeepAlive => true;
on your StatefulWidget.
This worked for me.
Source: https://github.com/flutter/flutter/issues/19116

how to add onscroll fetch data (pagination) in flutter & firebase realtime?

Hope Well,
I am using firebase realtime database on my flutter app (similar social media app). i have feed page and feed state page. i wanna show 10 posts first and after scroll bottom, load again 10 posts. i tried some methods but not working.
my codes
feed page code
Widget build(BuildContext context) {
var authstate = Provider.of<AuthState>(context, listen: false);
return Consumer<FeedState>(
builder: (context, state, child) {
final List<FeedModel> list = state.getPostList(authstate.userModel);
return CustomScrollView(
slivers: <Widget>[
child,
state.isBusy && list == null
? SliverToBoxAdapter(
child: Container(
height: fullHeight(context) - 135,
child: CustomScreenLoader(
height: double.infinity,
width: fullWidth(context),
backgroundColor: Colors.white,
),
),
)
: !state.isBusy && list == null
? SliverToBoxAdapter(
child: EmptyList(
'Follow someone',
subTitle:
'goto search page to find & follow Someone.\n When they added new post,\n they\'ll show up here.',
),
)
: SliverList(
delegate: SliverChildListDelegate(
list.map(
(model) {
return Container(
color: Colors.white,
child: Post(
model: model,
trailing: PostBottomSheet().PostOptionIcon(
context,
model: model,
type: PostType.Post,
scaffoldKey: scaffoldKey),
),
);
},
).toList(),
),
)
],
);
},
feed state code
List<FeedModel> get feedlist {
if (_feedlist == null) {
return null;
} else {
return List.from(_feedlist.reversed);
}
}
List<FeedModel> getPosttList(UserModel userModel) {
if (userModel == null) {
return null;
}
return feedlist;
}
I modified your code and use a ScrollController to load more data when the user reaches the end of the loaded data. (The data provider is hard-coded but you should be able to relate it to your scenario.) Note that I changed your code to use SliverChildBuilderDelegate which is more efficient.
import 'package:flutter/material.dart';
class ScrollTest extends StatefulWidget {
#override
_ScrollTestState createState() => _ScrollTestState();
}
class _ScrollTestState extends State<ScrollTest> {
bool isLoading = false;
bool isEnd = false;
final List<FeedModel> list = [];
ScrollController _controller;
_scrollListener() async {
var position = _controller.offset /
(_controller.position.maxScrollExtent -
_controller.position.minScrollExtent);
if (position > 0.5 && !_controller.position.outOfRange) {
await _getMoreData(list.length);
}
}
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(_scrollListener);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_getMoreData(list.length);
}
Future<void> _getMoreData(int index) async {
if (!isLoading) {
setState(() {
isLoading = true;
});
var tlist = await Feed.getPostList(index);
setState(() {
if (tlist.length == 0) {
isEnd = true;
} else {
list.addAll(tlist);
index = list.length;
}
isLoading = false;
});
}
}
#override
Widget build(BuildContext context) {
return CustomScrollView(
controller: _controller,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
color: Colors.white,
height: 300,
child: Text(list[index].text),
);
},
childCount: list.length,
),
),
SliverFillRemaining(
child: Center(
child: isEnd ? Text('End') : CircularProgressIndicator(),
)),
],
);
}
}
// Dummy FeedModel
class FeedModel {
final String text;
FeedModel(this.text);
}
// Dummy Feed provider
class Feed {
static final data = [
FeedModel('1'),FeedModel('2'),FeedModel('3'),FeedModel('4'),
FeedModel('5'),FeedModel('6'),FeedModel('7'),FeedModel('8'),
FeedModel('9'),FeedModel('10'),FeedModel('11'),FeedModel('12'),
FeedModel('13'),
];
static Future<List<FeedModel>> getPostList(int index) async {
List<FeedModel> l = [];
for (var i = index; i < index + 5 && i < data.length; i++) {
l.add(data[i]);
}
await Future.delayed(Duration(seconds: 1));
return l;
}
}

How to automatically scroll through all the ListTiles in the Listview.seperated in Flutter?

Scroll automatically (without any user interaction) through all the ListTiles in the Listview using a Timer in flutter. The below method makes only one ListTile to animate but I want to animate all the ListTiles from top to bottom one by one and again from bottom to top one by one.
The below is the Listview:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: FutureBuilder(
future: fetchNews(),
builder: (context, snap) {
if (snap.hasData) {
news = snap.data;
return ListView.separated(
//controller: _controller,
scrollDirection: scrollDirection,
controller: controller,
itemBuilder: (context, i) {
final NewsModel _item = news[i];
return AutoScrollTag(
key: ValueKey(i),
controller: controller,
index: i,
child: ListTile(
title: Text('${_item.title}'),
subtitle: Text(
'${_item.description}',
// maxLines: 1,
//overflow: TextOverflow.ellipsis,
),
),
);
},
separatorBuilder: (context, i) => Divider(),
itemCount: news.length,
);
} else if (snap.hasError) {
return Center(
child: Text(snap.error.toString()),
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
),
);
}
}
This is the automatic scrolling i have tried:
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 2), (Timer t) async {
await controller.scrollToIndex(1,
preferPosition: AutoScrollPosition.begin);
});
Here is a solution assuming that all your items in the ListView have the same itemExtent.
In this solution, I highlight the current Item as selected. You could also want to stop autoscrolling as soon as you reach the bottom of the list.
Full source code
import 'dart:async';
import 'package:faker/faker.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part '66455867.auto_scroll.freezed.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
Future<List<News>> _fetchNews() async => dummyData;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('News')),
body: FutureBuilder(
future: _fetchNews(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return NewsList(newsList: snapshot.data);
} else if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
} else {
return Center(child: CircularProgressIndicator());
}
},
),
);
}
}
class NewsList extends StatefulWidget {
final List<News> newsList;
const NewsList({
Key key,
this.newsList,
}) : super(key: key);
#override
_NewsListState createState() => _NewsListState();
}
class _NewsListState extends State<NewsList> {
ScrollController _scrollController = ScrollController();
Timer _timer;
double _itemExtent = 100.0;
Duration _scrollDuration = Duration(milliseconds: 300);
Curve _scrollCurve = Curves.easeInOut;
int _autoScrollIncrement = 1;
int _currentScrollIndex = 0;
#override
void initState() {
super.initState();
_timer = Timer.periodic(Duration(seconds: 2), (_) async {
_autoScrollIncrement = _currentScrollIndex == 0
? 1
: _currentScrollIndex == widget.newsList.length - 1
? -1
: _autoScrollIncrement;
_currentScrollIndex += _autoScrollIncrement;
_animateToIndex(_currentScrollIndex);
setState(() {});
});
}
void _animateToIndex(int index) {
_scrollController.animateTo(
index * _itemExtent,
duration: _scrollDuration,
curve: _scrollCurve,
);
}
#override
void dispose() {
_timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return ListView(
controller: _scrollController,
itemExtent: _itemExtent,
children: widget.newsList
.map((news) => ListTile(
title: Text(news.title),
subtitle: Text(
news.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
selected: widget.newsList[_currentScrollIndex].id == news.id,
selectedTileColor: Colors.amber.shade100,
))
.toList(),
);
}
}
#freezed
abstract class News with _$News {
const factory News({int id, String title, String description}) = _News;
}
final faker = Faker();
final dummyData = List.generate(
10,
(index) => News(
id: faker.randomGenerator.integer(99999999),
title: faker.sport.name(),
description: faker.lorem.sentence(),
),
);
Packages used in the solution:
freeze for the News Domain Class
build_runner to generate the freezed code
faker to generate the list of random news
UPDATE : Scroll only once
To stop the autoscrolling at the bottom of the listview, you just need to modify the initState method:
int _currentScrollIndex;
News _selectedNews;
#override
void initState() {
super.initState();
_currentScrollIndex = -1;
_timer = Timer.periodic(Duration(seconds: 2), (_) async {
setState(() {
if (_currentScrollIndex == widget.newsList.length - 1) {
_timer.cancel();
_selectedNews = null;
} else {
_selectedNews = widget.newsList[++_currentScrollIndex];
_animateToIndex(_currentScrollIndex);
}
});
});
}
We don't need the scroll direction defined as _autoScrollIncrement. However, I would introduce a new _selectedNews to easily unselect the last News item when we arrive at the bottom of the list. The selected flag of our ListTile would then become:
#override
Widget build(BuildContext context) {
return ListView(
[...]
children: widget.newsList
.map((news) => ListTile(
[...]
selected: _selectedNews?.id == news.id,
[...]
))
.toList(),
);
}

Flutter pagination loading the same data as in page one when scrolling

I'm building a list of news from an api that has next page results as in the image attached.
The api has only two pages with 10 list items each page.
Data is being passed to the widget. My problem is that when I scroll down the view, it loads the same 10 list items from page one.
This is the api I'm using enter link description here
Rest API
//newsModal.dart
class NewsNote {
String banner_image;
String title;
String text;
String sport;
NewsNote(this.banner_image, this.title, this.text, this.sport);
NewsNote.fromJson(Map<String, dynamic> json) {
banner_image = json['banner_image'];
title = json['title'];
text = json['text'];
sport = json['sport'];
}
}
//page news
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:jabboltapp/models/newsModal.dart';
class JabNews extends StatefulWidget {
#override
_JabNewsState createState() => _JabNewsState();
}
class _JabNewsState extends State<JabNews> {
ScrollController _scrollController = ScrollController();
bool isLoading = false;
String url = "https://jabbolt.com/api/news";
List<NewsNote> _newsNotes = List<NewsNote>();
Future<List<NewsNote>> fetchNewsNotes() async {
if (!isLoading) {
setState(() {
isLoading = true;
});
var response = await http.get(url);
var newsNotes = List<NewsNote>();
if (response.statusCode == 200) {
url = jsonDecode(response.body)['next'];
var newsNotesJson = json.decode(response.body)["results"];
for (var newsNoteJson in newsNotesJson) {
newsNotes.add(NewsNote.fromJson(newsNoteJson));
}
setState(() {
isLoading = false;
_newsNotes.addAll(newsNotes);
});
} else {
setState(() {
isLoading = false;
});
}
return newsNotes;
}
}
#override
void initState() {
fetchNewsNotes().then((value) {
setState(() {
_newsNotes.addAll(value);
});
});
this.fetchNewsNotes();
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
fetchNewsNotes();
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Widget _buildProgressIndicator() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Opacity(
opacity: isLoading ? 1.0 : 00,
child: CircularProgressIndicator(),
),
),
);
}
Widget _buildList() {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
if (index == _newsNotes.length) {
return _buildProgressIndicator();
} else {
return Padding(
padding: EdgeInsets.all(8.0),
child: Card(
child: ListTile(
title: Text((_newsNotes[index].title)),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(_newsNotes[index])));
},
),
),
);
}
},
controller: _scrollController,
itemCount: _newsNotes.length,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: dGrey,
appBar: AppBar(
title: Text(
"News",
style: TextStyle(
color: textGrey,
fontFamily: 'bison',
fontSize: 32.0,
letterSpacing: 1.2,
),
),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Container(
child: _buildList(),
),
);
}
}
You need to add the page number concatenation in the URL
https://jabbolt.com/api/news?page=2