使用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!

代码 cart_page.dart

  //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]. 将图片保存到本地存储

好了,结束!希望这对您有所帮助,并且您学到了一些新东西。如果您愿意,可以考虑给这个仓库点个⭐。如果您有任何疑问,可以通过我的社交媒体与我联系。

祝您一切顺利?

GitHub

查看 Github