使用API和Provider构建的Meme App
启动屏幕
主页 添加商品 购物车页面
下载图片 购物车为空
录音
memeapp.mp4
使用的包
详细的实现说明
[i]. 从API获取数据并渲染UI
[ii]. 用于状态管理的Provider
[iii]. 将图片保存到本地存储
[i]. 从API获取数据并渲染UI
Memes API : MemesAPI
不知道如何解析API的JSON数据? 详细解释 在此
代码在此 : home_page.dart
解析memesAPI中的数据
Future<MemesModel> getMemesApi() async {
final response =
await http.get(Uri.parse("https://api.imgflip.com/get_memes"));
var data = jsonDecode(response.body);
if (response.statusCode == 200) {
return MemesModel.fromJson(data);
} else {
return MemesModel.fromJson(data);
}
}
- memes是Future类型,它将包含获取到的数据。
- 注意:late关键字很重要。
- 在initState()中调用该方法
class _HomePageState extends State<HomePage> {
late Future<MemesModel> memes;
@override
void initState() {
super.initState();
memes = getMemesApi();
}
- 使用FutureBuilder将数据渲染到屏幕上
body: FutureBuilder<MemesModel>(
future: memes,
builder: (context, snapshot) {
- 我使用了DynamicHeightGridView(包 在此),您可以使用任何适合您的组件/包。
- 确保您提供了itemCount属性
- snapshot将包含数据或错误(
snapshot.hasData,snapshot.harError) - 如果
snapshot.hasData则渲染UI,否则如果snapshot.hasError则显示错误,否则渲染CircularProgressIndicator,即在等待数据获取时。
DynamicHeightGridView(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 25,
itemCount:
//nullcheck operator
snapshot.hasData ? snapshot.data!.data!.memes!.length : 1,
builder: (context, index) {
//data fetched successfully
if (snapshot.hasData) {
//render UI here
}
//data couldn't be fetched due to some error
else if (snapshot.hasError) {
return Center(
child: Text("Error Occured : ${snapshot.error}"),
);
//waiting for data to be fetched (just the way youtube videos show circular progressor while buffering)
else {
return const Center(
child: CircularProgressIndicator(
color: Colors.teal,
));
}
[ii]. 用于状态管理的Provider
- 我们已经渲染了UI。太棒了!
- 但是,想想购物车功能,每当点击“获取此Meme”按钮时
- 购物车图标右上角的计数器值如何增加?
- 购物车页面将如何相应地渲染UI?
这就是PROVIDER的作用!
Provider是一个状态管理工具,您可以在此处参考更多信息: Provider示例
注意:理解Provider可能需要一些时间,我的情况也一样。(记住:没有一蹴而就的事情,好事需要时间!)
- 一旦/如果您熟悉了Provider,我们就继续前进。
我们有两个基本要求,需要使用Provider。
情况1:在主页上的购物车图标上增加/减少计数器值(参考上面的图片)。
情况2:构建购物车页面。
- 定义Provider类
- 确保继承ChangeNotifier
- 声明一个整数cartCount
- 定义构造函数(初始化cartCount值)和getter方法
- 定义increment()和decrement()方法
- notifyListeners()
import 'package:flutter/cupertino.dart';
class CartCounterProvider extends ChangeNotifier {
int cartCount;
CartCounterProvider({this.cartCount = 0});
int get getCartCount => cartCount;
void increment() {
cartCount += 1;
notifyListeners();
}
void decrement() {
cartCount -= 1;
notifyListeners();
}
}
接下来,我们何时需要Provider?当按钮被按下时!因此,请将代码写在“获取此Meme”按钮的onPressed()函数中
代码 在此
context.read<CartCounterProvider>().increment();
代码 在此
//just inside the build method this cartCounter will watch the value of the counter
//button pressed -> increment() method called using provider -> cartCounter watches the value and updates accordingly
int cartCounter = context.watch<CartCounterProvider>().getCartCount;
使用read和watch是一种方法。另一种方法是使用您创建的Provider类的实例,让我们为情况2讨论这种方法。
//read below about CartCardModel first
import 'package:memeapp/models/cart_card_model.dart';
import 'package:flutter/cupertino.dart';
class MemeCartProvider extends ChangeNotifier {
//now it's convenient for us to display the memes, we just need a list of CartCardModel!
//each instance of this class will have the respective id, imageURL, and name of the meme.
//We just need to traverse and display this list in the cart page, thats it!
//_cartList will have the id, imageURL, name of only those memes that are added to cart by user
final List<CartCardModel> _cartList = <CartCardModel>[];
//memesIdList will only contain the meme ID's which will be used to add/remove memes from the cart
//also used to prevent duplicates
List<String>? memesIdList = <String>[];
List<CartCardModel> get getCartList => _cartList;
List<String>? get getMemesIdList => memesIdList;
void addItem(CartCardModel addThisItem) {
_cartList.add(addThisItem);
notifyListeners();
}
void removeItem(String memeId) {
_cartList.removeWhere((element) => element.id == memeId);
memesIdList!.remove(memeId);
notifyListeners();
}
}
- 好奇CartCardModel是什么?(抱歉命名混乱?)
- 看,我们需要显示相应Meme的图片和名称。
- 为了不让事情复杂化,我定义了一个名为“CartCardModel”的类,它将包含Meme的id、imageURL和name。
- 为什么要有id?为了避免重复!…id将确保添加后,相同的Meme不会再次添加。
- ID可以是任何对每个项目唯一的标识。
- 幸运的是,MemeAPI已经为每个项目包含了ID。
CartCardModel cart_card_model.dart
//each instance of this class (CartCardModel) will have the info of id, imageURL, and name.
//it will now be easier to display the memes in the cart page
class CartCardModel {
String id;
String? nameCart;
String? imageUrlCart;
CartCardModel({
required this.id,
this.nameCart,
this.imageUrlCart,
});
}
现在,我们可以在购物车页面方便地渲染Meme了。我们只需遍历List并相应地显示项目。
但是,我们如何确保只渲染用户添加的项目呢?还是Provider!
//define an instance of MemeCartProvider class like this inside the build method
Widget build(BuildContext context) {
var memeCartProvider = Provider.of<MemeCartProvider>(context);
代码 在此
//wrap the Listview.builder with CONSUMER<MemeCartProvider>
Consumer<MemeCartProvider>(
builder: (context, value, child) {
//ListView will traverse CartCardModel list and render memes accordingly
return ListView.builder(
//itemCount is simply the length of the list.
itemCount: value.getMemesIdList!.length,
itemBuilder: ((context, index) {
object/instance of CartCardModel class using MemeCartProvider
CartCardModel cartObject = value.getCartList[index];
代码 在此
//render the image using cartObject
Image.network(
"${cartObject.imageUrlCart}",
width: 140,
),
//render the name using cartObject
Text(
//limiting name to 20 characters
cartObject.nameCart!.length > 20
? "${cartObject.nameCart!.substring(0, 20)}..."
: "${cartObject.nameCart}",
最后但同样重要的是,我们需要实现删除功能
onPressed: () {
//we will remove the item from the list
value.removeItem(cartObject.id);
//why this line? you might've guessed it already, if not, dont worry
//remember we incrementes the counter value when user added items to the cart
//now user is deleting items, hence we need to decreament that counter value too!, right?
context.read<CartCounterProvider>().decrement();
},
[iii]. 将图片保存到本地存储
- 代码 很简单,易于理解。
好了,结束!希望这对您有所帮助,并且您学到了一些新东西。如果您愿意,可以考虑给这个仓库点个⭐。如果您有任何疑问,可以通过我的社交媒体与我联系。
祝您一切顺利?





