Understanding API integration with Getx State management

API integration with GetX is a process of connecting an API (Application Programming Interface) with GetX, a Flutter-based state management library.

handle_Api_with_getx
GetX provides a simple and efficient way to manage state in Flutter applications, which can be combined with API calls to fetch and manipulate data. To integrate an API with GetX, you will need to perform the following steps:
  • Define the API endpoint and make a request to the API to fetch data.
  • Use GetX's controllers to store the data received from the API.
  • Update the UI based on the state stored in the controllers.

FineName: main.dart

This is the main Dart file for a Flutter application. The main function creates an instance of the MyApp widget and runs it using the runApp method.

The MyApp class extends StatelessWidget and implements the build method to define the widget tree of the app. The MaterialApp widget is the root of the widget tree, and it provides a basic Material Design visual layout structure.

The MaterialApp widget takes several properties as parameters, such as:
  • title: a string that represents the title of the app.
  • theme: a ThemeData object that defines the visual theme of the app.
  • debugShowCheckedModeBanner: a boolean value that determines whether to show the "debug" banner in the app.
  • home: the widget that will be displayed as the first screen of the app. In this case, it is an instance of the HomePage widget.
  • The HomePage widget is defined in the homepage.dart file and will be the first screen displayed when the app runs.

import 'package:flutter/material.dart';

import 'views/homepage.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      debugShowCheckedModeBanner: false,
      home: HomePage(),
    );
  }
}

Filename: product.dart


This code defines data classes for representing product information in a Flutter app, using the json library for encoding and decoding JSON data, and the Get library for observable state.

The Product class has properties for storing information about a product, such as its ID, brand, name, price, description, and product colors. The isFavorite property is an observable boolean value, indicating whether the product is a favorite of the user or not. The Product class also provides methods for serializing its data to JSON, and for deserializing JSON data into a Product instance.

The Brand enum defines a set of possible brands for a product.

The ProductColor class represents a color associated with a product, with properties for the color's hexadecimal value and name.

The EnumValues class is a helper class for mapping between string values in JSON data and their corresponding enum values in Dart code. The map property stores the forward mapping, and the reverse getter provides the reverse mapping.
import 'dart:convert';

import 'package:get/get.dart';

List<Product> productFromJson(String str) =>
    List<Product>.from(json.decode(str).map((x) => Product.fromJson(x)));

String productToJson(List<Product> data) =>
    json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class Product {
  Product({
    required this.id,
    required this.brand,
    required this.name,
    required this.price,
    this.priceSign,
    this.currency,
    required this.imageLink,
    required this.productLink,
    required this.websiteLink,
    required this.description,
    this.rating,
    this.category,
    required this.productType,
    required this.tagList,
    required this.createdAt,
    required this.updatedAt,
    required this.productApiUrl,
    required this.apiFeaturedImage,
    required this.productColors,
  });

  int id;
  Brand brand;
  String name;
  String price;
  dynamic priceSign;
  dynamic currency;
  String imageLink;
  String productLink;
  String websiteLink;
  String description;
  double? rating;
  String? category;
  String productType;
  List<dynamic> tagList;
  DateTime createdAt;
  DateTime updatedAt;
  String productApiUrl;
  String apiFeaturedImage;
  List<ProductColor> productColors;

  var isFavorite = false.obs;

  factory Product.fromJson(Map<String, dynamic> json) => Product(
        id: json["id"],
        brand: brandValues.map[json["brand"]]!,
        name: json["name"],
        price: json["price"],
        priceSign: json["price_sign"],
        currency: json["currency"],
        imageLink: json["image_link"],
        productLink: json["product_link"],
        websiteLink: json["website_link"],
        description: json["description"],
        rating: json["rating"]?.toDouble(),
        category: json["category"],
        productType: json["product_type"],
        tagList: List<dynamic>.from(json["tag_list"].map((x) => x)),
        createdAt: DateTime.parse(json["created_at"]),
        updatedAt: DateTime.parse(json["updated_at"]),
        productApiUrl: json["product_api_url"],
        apiFeaturedImage: json["api_featured_image"],
        productColors: List<ProductColor>.from(
            json["product_colors"].map((x) => ProductColor.fromJson(x))),
      );

  Map<String, dynamic> toJson() => {
        "id": id,
        "brand": brandValues.reverse[brand],
        "name": name,
        "price": price,
        "price_sign": priceSign,
        "currency": currency,
        "image_link": imageLink,
        "product_link": productLink,
        "website_link": websiteLink,
        "description": description,
        "rating": rating,
        "category": category,
        "product_type": productType,
        "tag_list": List<dynamic>.from(tagList.map((x) => x)),
        "created_at": createdAt.toIso8601String(),
        "updated_at": updatedAt.toIso8601String(),
        "product_api_url": productApiUrl,
        "api_featured_image": apiFeaturedImage,
        "product_colors":
            List<dynamic>.from(productColors.map((x) => x.toJson())),
      };
}

enum Brand { MAYBELLINE }

final brandValues = EnumValues({"maybelline": Brand.MAYBELLINE});

class ProductColor {
  ProductColor({
    required this.hexValue,
    this.colourName,
  });

  String hexValue;
  String? colourName;

  factory ProductColor.fromJson(Map<String, dynamic> json) => ProductColor(
        hexValue: json["hex_value"],
        colourName: json["colour_name"],
      );

  Map<String, dynamic> toJson() => {
        "hex_value": hexValue,
        "colour_name": colourName,
      };
}

class EnumValues<T> {
  Map<String, T> map;
  late Map<T, String> reverseMap;

  EnumValues(this.map);

  Map<T, String> get reverse {
    reverseMap = map.map((k, v) => MapEntry(v, k));
    return reverseMap;
  }
}

FileName: product_controller.dart


This is a Flutter code written in Dart language. The code defines a ProductController class that extends GetxController from the get package. This class serves as the controller for a screen displaying a list of products.

The class has two reactive variables isLoading and productList of type Observable which will automatically notify the UI of any changes made to their values. The isLoading variable is used to show/hide a loading indicator, and productList stores the list of products fetched from the remote API.

The fetchProducts function is called in the onInit method, which is called when the controller is initialized. This function retrieves the list of products from a remote service RemoteServices and updates the productList with the returned data. The isLoading variable is set to true before the data is fetched, and to false when the data fetching is completed.

import 'package:get/state_manager.dart';

import '../models/product.dart';
import '../services/remote_services.dart';

class ProductController extends GetxController {
  var isLoading = true.obs;
  var productList = <Product>[].obs;

  @override
  void onInit() {
    fetchProducts();
    super.onInit();
  }

  void fetchProducts() async {
    try {
      isLoading(true);
      var products = await RemoteServices.fetchProducts();
      if (products != null) {
        productList.value = products;
      }
    } finally {
      isLoading(false);
    }
  }
}

FileName: remote_services.dart


This code defines a RemoteServices class with a static method fetchProducts() that sends a GET request to the specified URL and returns a list of Product objects if the request was successful. The productFromJson function is not defined here, so you'll need to add that yourself.
import 'package:http/http.dart' as http;

import '../models/product.dart';

class RemoteServices {
  static var client = http.Client();

  static Future<List<Product>?> fetchProducts() async {
    var response = await client.get(Uri.parse(
        'https://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline'));
    if (response.statusCode == 200) {
      var jsonString = response.body;
      return productFromJson(jsonString);
    } else {
      //show error message
      return null;
    }
  }
}

FileName: homepage.dart

The code imports the necessary packages and files, and implements a HomePage widget that is used to display a list of products.

The HomePage uses the ProductController to control the data of the products and its loading state. The ProductController fetches the data from a remote API using the RemoteServices class.

The HomePage widget has an app bar with a back arrow icon and a shopping cart icon and a body that consists of a title and a grid view. The title has a text and two icons for list and grid view. The grid view is constructed using the data from the ProductController and a ProductTile widget.

When the data is being fetched, a circular progress indicator is displayed. When the data is loaded, the grid view is populated with the ProductTile widgets, each representing a single product.import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../controllers/product_controller.dart';
import 'product_tile.dart';

class HomePage extends StatelessWidget {
  final ProductController productController = Get.put(ProductController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 0,
        leading: Icon(
          Icons.arrow_back_ios,
        ),
        actions: [
          IconButton(
            icon: Icon(
              Icons.shopping_cart,
            ),
            onPressed: () {},
          )
        ],
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              children: [
                Expanded(
                  child: Text(
                    'Learn with pradeep',
                    style: TextStyle(
                        fontFamily: 'avenir',
                        fontSize: 32,
                        fontWeight: FontWeight.w900),
                  ),
                ),
                IconButton(
                    icon: Icon(Icons.view_list_rounded), onPressed: () {}),
                IconButton(icon: Icon(Icons.grid_view), onPressed: () {}),
              ],
            ),
          ),
          Expanded(
            child: Obx(() {
              if (productController.isLoading.value)
                return Center(child: CircularProgressIndicator());
              else
                return GridView.builder(
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 2,
                      mainAxisSpacing: 10,
                      crossAxisSpacing: 10,
                      childAspectRatio: 1 / 1.5),
                  itemCount: productController.productList.length,
                  itemBuilder: (context, i) =>
                      ProductTile(productController.productList[i]),
                );
            }),
          )
        ],
      ),
    );
  }
}

FileName: product_tile.dart

This is the code for a ProductTile widget that takes a Product object as its argument and displays it in a card. The card includes an image of the product, its name, rating, and price. The image is displayed in a container with a fixed height and width, and is fit to cover the entire container. The rating is displayed as a text with a green background and an icon of a star. There is also a toggle button for marking the product as a favorite. The button is an IconButton inside a CircularAvatar widget and its icon changes based on the value of the isFavorite property of the Product object.

import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../models/product.dart';

class ProductTile extends StatelessWidget {
  final Product product;
  const ProductTile(this.product);

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 2,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Stack(
              children: [
                Container(
                  height: 150,
                  width: double.infinity,
                  clipBehavior: Clip.antiAlias,
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(4),
                  ),
                  child: Image.network(
                    product.imageLink,
                    fit: BoxFit.cover,
                  ),
                ),
                Positioned(
                  right: 0,
                  child: Obx(() => CircleAvatar(
                        backgroundColor: Colors.white,
                        child: IconButton(
                          icon: product.isFavorite.value
                              ? Icon(Icons.favorite_rounded)
                              : Icon(Icons.favorite_border),
                          onPressed: () {
                            product.isFavorite.toggle();
                          },
                        ),
                      )),
                )
              ],
            ),
            SizedBox(height: 8),
            Text(
              product.name,
              maxLines: 2,
              style:
                  TextStyle(fontFamily: 'avenir', fontWeight: FontWeight.w800),
              overflow: TextOverflow.ellipsis,
            ),
            SizedBox(height: 8),
            if (product.rating != null)
              Container(
                decoration: BoxDecoration(
                  color: Colors.green,
                  borderRadius: BorderRadius.circular(12),
                ),
                padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(
                      product.rating.toString(),
                      style: TextStyle(color: Colors.white),
                    ),
                    Icon(
                      Icons.star,
                      size: 16,
                      color: Colors.white,
                    ),
                  ],
                ),
              ),
            SizedBox(height: 8),
            Text('\$${product.price}',
                style: TextStyle(fontSize: 32, fontFamily: 'avenir')),
          ],
        ),
      ),
    );
  }
}

...

GitHub Links

Follow @PradeepTheDeveloper Star Issue Sponsor

Comments

Popular posts from this blog

Error Handling in Flutter - Gradle issue

How to Make a Dynamic and Trending ListView with Flutter Widgets?