如何在Stateful Widget中实现Riverpod状态(API)?
在Stateful Widget中使用Riverpod的FutureProvider展示WordPress新闻数据
核心修改步骤
- 将
HomeScreen从普通StatefulWidget改为ConsumerStatefulWidget,对应State改为ConsumerState,让State类能获取WidgetRef监听Provider - 移除
initState中手动初始化_postsFuture的代码,直接通过Riverpod监听newsProvider获取数据 - 用Riverpod的
AsyncValue处理加载、错误、数据就绪三种状态,替代原FutureBuilder
修改后的完整代码
news_provider.dart(保持原代码不变)
// news_provider.dart import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; final String baseUrl = 'https://thewordpress.com/wp-json/wp/v2'; final newsProvider = FutureProvider<List<Berita>> ((ref) async { // final response = await http.get(Uri.parse('$baseUrl/posts?_embed')); //ini asli final response = await http.get(Uri.parse('$baseUrl/posts?per_page=20&_embed')); if (response.statusCode == 200) { List<dynamic> jsonResponse = json.decode(response.body); return jsonResponse.map((post) => Berita.fromJson(post)).toList(); } else { throw Exception('GAGAL MENGAMBIL BERITA TERBARU'); } }); class Berita { final int id; final String title; final String content; final String excerpt; final String featuredImageUrl; final DateTime date; final String link; final List<dynamic> categories; Berita({ required this.id, required this.title, required this.content, required this.excerpt, required this.featuredImageUrl, required this.date, required this.link, required this.categories }); factory Berita.fromJson(Map<String, dynamic> json) { return Berita( id: json['id'], title: json['title']['rendered'], content: json['content']['rendered'], excerpt: json['excerpt']['rendered'], featuredImageUrl: json['_embedded']['wp:featuredmedia'] != null ? json['_embedded']['wp:featuredmedia'][0]['source_url'] : 'https://via.placeholder.com/400x200', // Replace with default image URL date: DateTime.parse(json['date']), link: json['link'], categories: json['categories'], ); } }
修改后的HomeScreen代码
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({super.key}); @override ConsumerState<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends ConsumerState<HomeScreen> { late Future<List<Category>> categories; @override void initState() { super.initState(); categories = ApiService().fetchCategories(); } @override Widget build(BuildContext context) { // 监听newsProvider,获取异步数据状态 final newsAsyncValue = ref.watch(newsProvider); return Scaffold( appBar: AppBar( title: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Aplikasi'), Text( 'Berita', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold), ), ], ), actions: [ IconButton( icon: const Icon(Icons.search, color: Colors.red), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const SearchNews()), ); }, ), ], centerTitle: true, elevation: 0.0, ), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text( 'Kategori', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), _buildCategory(), const Padding( padding: EdgeInsets.only(top: 50.0, bottom: 20, left: 8), child: Text( 'Berita Utama', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.redAccent, ), ), ), _buildCarousel(), GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const SearchNews()), ); }, child: Row( children: [ const Padding( padding: EdgeInsets.only( top: 40.0, bottom: 20, left: 8, right: 8, ), child: Text( 'Berita Terbaru', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.orange, ), ), ), const Spacer(), GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const BeritaTerbaru()), ); }, child: const Padding( padding: EdgeInsets.only( top: 40.0, bottom: 20, left: 8, right: 8, ), child: Text( "Lihat", style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: Colors.black45, ), ), ), ), ], ), ), // 处理新闻数据展示 newsAsyncValue.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) => Center(child: Text('加载失败: $error')), data: (beritaList) => _buildLatestNews(beritaList), ), ], ), ), ); } Widget _buildCategory() { return SizedBox( height: 35, child: FutureBuilder<List<Category>>( future: categories, builder: (context, snapshot) { if (snapshot.hasData) { return SizedBox( height: 15, width: MediaQuery.of(context).size.width, child: ListView.builder( shrinkWrap: true, scrollDirection: Axis.horizontal, itemCount: snapshot.data!.length, itemBuilder: (context, index) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ListNewsScreen( categoryId: snapshot.data![index].id, categoryName: snapshot.data![index].name, ), ), ); }, child: Container( margin: const EdgeInsets.only(left: 10, right: 10), height: 250, width: MediaQuery.of(context).size.width / 3.5, decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(10), ), child: Center( child: Text( snapshot.data![index].name, style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ); }, ), ); } else if (snapshot.hasError) { return Center(child: Text('${snapshot.error}')); } return const Center(child: CircularProgressIndicator()); }, ), ); } // 接收Berita列表参数,构建新闻列表 Widget _buildLatestNews(List<Berita> beritaList) { return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: beritaList.length, itemBuilder: (context, index) { final berita = beritaList[index]; return Card( margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Image.network(berita.featuredImageUrl, fit: BoxFit.cover), Padding( padding: const EdgeInsets.all(8.0), child: Text( berita.title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Text( berita.excerpt.replaceAll(RegExp(r'<[^>]*>'), ''), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), ); }, ); } // 补充轮播图实现逻辑 Widget _buildCarousel() { return const SizedBox(height: 200, child: Center(child: Text('轮播图区域'))); } } // 补充原代码中用到的未定义类 class Category { final int id; final String name; Category({required this.id, required this.name}); } class SearchNews extends StatelessWidget { const SearchNews({super.key}); @override Widget build(BuildContext context) { return Scaffold(appBar: AppBar(title: const Text('搜索')),); } } class BeritaTerbaru extends StatelessWidget { const BeritaTerbaru({super.key}); @override Widget build(BuildContext context) { return Scaffold(appBar: AppBar(title: const Text('所有最新新闻')),); } } class ListNewsScreen extends StatelessWidget { final int categoryId; final String categoryName; const ListNewsScreen({super.key, required this.categoryId, required this.categoryName}); @override Widget build(BuildContext context) { return Scaffold(appBar: AppBar(title: Text(categoryName)),); } } class ApiService { Future<List<Category>> fetchCategories() async { return Future.value([Category(id: 1, name: '科技'), Category(id: 2, name: '体育')]); } }
关键说明
- ConsumerStatefulWidget:Riverpod为StatefulWidget提供的适配类,让State类可通过
ref监听Provider状态变化 - AsyncValue.when:Riverpod内置的状态处理方法,统一管理加载、错误、数据就绪三种状态,替代传统FutureBuilder
- 自动数据管理:无需手动维护Future对象,Riverpod会自动缓存数据,当Provider更新时UI自动重建
内容的提问来源于stack exchange,提问作者Roly Audi
相关产品推荐
相关产品推荐

