我现在陷入了一些困惑。关于这个 Flutter 问题的任何帮助都会非常有帮助。非常感谢!
最近我爱上了Riverpod。我到处都在使用 Riverpod。Riverpod 非常强大,但我对使用 Riverpod 很菜鸟。我总是担心我是否正确使用它。
{
"status": 200,
"msg": "OK",
"description": "News Search Results",
"data": {
"per_page": 10,
"from": 1,
"to": 10,
"total": 19,
"current_page": 1,
"last_page": 2,
"prev_page_url": null,
"first_page_url": "http://xxxxxx/api/v1/search-news-all?page=1",
"next_page_url": "http://xxxxxx/api/v1/search-news-all?page=2",
"last_page_url": "http://xxxxxx/api/v1/search-news-all?page=2",
"posts": [
{
...
},
{
...
},
...
]
}
}
FutureEither<({List<NewsPreviewModel> newsData, String? nextPageKey})> searchNewsBykeyword({
required String query,
required int page,
}) async {
try {
final url = "$_baseAPIUrl/search-news-all?q=$query&page=$page";
final response = await sendRequest(
url,
'GET',
);
// checking api status code. if it's not 200, there is some network or req method related issue
if (response.statusCode == 200) {
/// [decoding response]
final decodedResponse = jsonDecode(response.body);
/// [checking response status. if it's not 200 this could be token related issue]
if (decodedResponse['status'] == 200) {
/// [converting to data model]
final List<NewsPreviewModel> data = List<NewsPreviewModel>.from(
(decodedResponse['data']['posts'] as List).map(
(e) => NewsPreviewModel.fromJson(e as Map<String, dynamic>),
),
).toList();
/// [returning data with with next page key]
return right(
(
newsData: data,
nextPageKey: decodedResponse['data']['next_page_url'] as String?,
),
);
} else {
/// [decodedResponse['status'] is not 200]
return left(
Failure(
message: decodedResponse['description'],
),
);
}
} else {
// response.statusCode is not 200
final res = getCustomHttpErrorMessage(response.statusCode);
throw HttpException(res);
}
} catch (e, st) {
LoggerManager.e("[searchNewsBykeyword] $e $st");
final res = getErrorMessage(e);
// return Failure
return left(
Failure(
message: res,
stackTrace: st,
),
);
}
}
这里我使用 infinite_scroll_pagination 4.0.0 包来显示分页数据。 我还使用 Riverpod FutureProvider 来处理未来的数据。
final keywordSearchResultNewsPaginationControllerProvider = FutureProvider.family((ref, String slug) {
/// [this is using for accessing api method]
final newsAPI = ref.watch(newsAPIProvider);
/// [defining controller]
/// [first page key is 1]
final controller = PagingController<int, NewsPreviewModel>(firstPageKey: 1);
/// [this part generates new data when user reach bottom of the screen]
/// i mean when new is needed this method will fetch the new page data
controller.addPageRequestListener((pageKey) {
newsAPI
.searchNewsBykeyword(
query: slug,
page: pageKey,
)
.then((apiData) {
apiData.fold(
(l) {
controller.error = l.message;
},
(r) {
/// [It will fetch data until the next pagekey is null] [see api response for nextpagekey]
final isLastPage = r.nextPageKey == null;
if (isLastPage) {
controller.appendLastPage(r.newsData);
} else {
final nextPageKey = pageKey + 1;
controller.appendPage(
r.newsData,
nextPageKey,
);
}
},
);
}).catchError((error) {
controller.error = error;
});
});
/// [returning controller which is a PagingController from infinite_scroll_pagination package]
return controller;
});
class KeywordSearchResultPage extends StatefulHookConsumerWidget {
const KeywordSearchResultPage({super.key, required this.keyword});
final String keyword;
@override
ConsumerState<ConsumerStatefulWidget> createState() => _KeywordSearchResultPageState();
}
class _KeywordSearchResultPageState extends ConsumerState<KeywordSearchResultPage> {
@override
Widget build(BuildContext context) {
/// [this is where i fetch paging controller data]
final pagingController = ref.watch(keywordSearchResultNewsPaginationControllerProvider(widget.keyword));
return Scaffold(
backgroundColor: isDark ? AppColorPallete.baseBlackColor : AppColorPallete.brandBlueLightColor,
appBar: AppBar(
title: const Text("Search Results"),
backgroundColor:
isDarkModeOn(context) ? AppColorPallete.elevetedBlackColor : AppColorPallete.elevetedWhiteColor,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(20),
),
),
),
body: pagingController.when(
/// [PagedListView is coming from infinite scroll pagination package]
/// [this will handle all kind of thing when user reach end of the screen]
data: (controller) => PagedListView<int, NewsPreviewModel>.separated(
/// [passing the controller which i got from FutureProvider]
pagingController: controller,
builderDelegate: PagedChildBuilderDelegate<NewsPreviewModel>(
firstPageProgressIndicatorBuilder: (context) => CustomLoader(),
newPageProgressIndicatorBuilder: (context) => CustomLoader(),
noItemsFoundIndicatorBuilder: (context) => NoDataFoundUi(msg: "No News Found for '${widget.keyword}'"),
noMoreItemsIndicatorBuilder: (context) => Text("(You've reached the end of the news list.)"),
firstPageErrorIndicatorBuilder: (context) => ErrorWidget(),
/// [rendering card based on response data, this data is given by this itemBuilder function]
itemBuilder: (context, item, index) => BigFeaturedNewsCard(news: item),
),
),
separatorBuilder: (context, index) => kVerticalSpaceM,
),
loading: () => PrimaryLoader(),
error: (error, stackTrace) => ErrorWidget(),
),
);
}
感谢您的阅读。有没有简单更好的方法来使用 Riverpod 来做到这一点?
我假设您要求使用 Riverpod 进行分页。
我不喜欢使用任何库进行分页,最好使用“NotificationListener”小部件来处理特定列表并通过 api 调用管理页面
NotificationListener(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels >=
scrollInfo.metrics.maxScrollExtent) {
ref
.read(
yourProvider(searchText)
.notifier)
.fetchNextData();
}
return true;
},
child: Container()
我们也可以使用ScrollController
final ScrollController scrollController = ScrollController();
void _scrollListener() {
if (scrollController.offset >= scrollController.position.maxScrollExtent &&
!scrollController.position.outOfRange) {
ref.read(yourProvider.notifier).fetchNextData();
}
}
@override
Widget build(BuildContext context) {
final state = ref.watch(yourProvider);
return state.when(data: (data) {
scrollController.addListener(() {
_scrollListener(data);
});
return ListView.builder(
controller: scrollController, itemBuilder: (context, index) {});
}, error: (error, stack) {
return Container();
}, loading: () {
return CircularProgressIndicator(
color: AppColors.white,
);
});
}
并在提供程序中使用从 api 响应收到的页码管理 api 调用
if (state.value != null && currentPage < lastPage) { //do api call for next page }
这里我使用了RiverPod,AsyncNotifierProvider。
我希望这对你有用。