API call with Change Notifier Provider - Flutter

In this Blog, we are gonna see how to integrate Rest API with the State Management approach - ChangeNotifierProvider.

Before we pitch into that we need to understand what is state management and why we are using that in API calls. 



We have multiple state management approaches like Provided, Riverpod, SetState, Redux, Fish Redux, BloC, GetIt, Mobx, Getx etc each one has its own methodology and now we are going to see one of its call called Provider.

What is Provider?

A recommended approach by flutter, you can refer to it on their official flutter website . Providers allow us to not only expose a value, but also create, listen, and dispose of it when needed.

How to install Provider?

You can get the dependency details on the Flutter official site and also you will be navigated to the official site by clicking this link - Flutter - Provider.

Step 1: Copy the Provider version:


Step 2: Paste it to your project file pubspec.yaml:


Step 3: We are going to the coding part:


File Name: main.dart

This is the Main file where we are using ChangeNotifierProvider. 

import 'package:Demo/screens/profile_detailed_screen.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'providers/provider.dart';
import 'screens/dashboard.dart';

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

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
        // this is used only when we perform multiple providers
        providers: [
          ChangeNotifierProvider<ProfileProvider>(
            create: (_) => ProfileProvider(), // Calling Provider
          ),
        ],
        child: MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'API Call',
          theme: ThemeData(
            primarySwatch: Colors.orange,
          ),
          home: const Dashboard(),
          routes: {
            // Navigating the page using Named Routes
            ProfileDetailedScreen.routeName: (ctx) => ProfileDetailedScreen(),
          },
        ));
  }
}


File Name: data_class.dart

Creating the data class to hold the values.

import 'package:flutter/material.dart';

class Profile {
  final int id;
  final String name;
  final String image;
  final String creationAt;
  final String updatedAt;

  Profile({
    required this.id,
    required this.name,
    required this.image,
    required this.creationAt,
    required this.updatedAt,
  });
}

File Name: provider.dart

Creating the Class to configure Provider that extends ChangeNotifier

import 'package:Demo/providers/remote_service.dart';
import 'package:flutter/material.dart';

import '../models/data_class.dart';

class ProfileProvider extends ChangeNotifier {
  final _service = ProfileServices();
  bool isLoading = false;
  List<Profile> _profiles = [];
  List<Profile> get Profiles => _profiles;

  Future<void> getAllProfile() async {
    isLoading = true;
    notifyListeners();

    final response = await _service.getAll();

    _profiles = response;
    isLoading = false;
    notifyListeners();
  }

  Profile findById(int id) {
    return _profiles.firstWhere((ctx) => ctx.id == id);
  }
}

File Name: remote_service.dart

Getting data from API and Sending it to our data class.

import 'dart:convert';
import 'package:http/http.dart' as http;

import 'package:Demo/models/data_class.dart';

class ProfileServices {
  Future<List<Profile>> getAll() async {
    const url = 'https://api.escuelajs.co/api/v1/categories';
    final uri = Uri.parse(url);
    final response = await http.get(uri);
    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as List;
      final todos = json.map((e) {
        return Profile(
          id: e['id'],
          name: e['name'],
          image: e['image'],
          creationAt: e['creationAt'],
          updatedAt: e['updatedAt'],
        );
      }).toList();
      return todos;
    }
    return [];
    // throw "Something went wrong";
  }
}

File Name: dashboard.dart

A dashboard screen is created for the bottom navigation.

import 'package:Demo/screens/fragments/favorites.dart';
import 'package:flutter/material.dart';

import 'fragments/home_screen.dart';

class Dashboard extends StatefulWidget {
  const Dashboard({super.key});

  @override
  State<Dashboard> createState() => _DashboardState();
}

class _DashboardState extends State<Dashboard> {
  int currentTab = 0;
  List<Widget> tabs = [
    HomePage(),
    Favorites(),
    HomePage(),
    HomePage(),
  ];

  @override
  void initState() {
    super.initState();
    init();
  }

  Future<void> init() async {
    //
  }

  @override
  void setState(fn) {
    if (mounted) super.setState(fn);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: tabs[currentTab],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: currentTab,
        onTap: (index) {
          currentTab = index;
          setState(() {});
        },
        type: BottomNavigationBarType.fixed,
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: ''),
          BottomNavigationBarItem(
              icon: Icon(Icons.favorite_border_outlined), label: ''),
          BottomNavigationBarItem(
              icon: Icon(Icons.chat_bubble_outline_outlined), label: ''),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: ''),
        ],
      ),
    );
  }
}

File Name: profile_detailed_screen.dart

Profile Detailed Screen is created to view images.

import 'package:Demo/models/data_class.dart';
import 'package:Demo/providers/remote_service.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/provider.dart';

class ProfileDetailedScreen extends StatefulWidget {
  const ProfileDetailedScreen({super.key});

  static const routeName = '/product-detail';
  @override
  State<ProfileDetailedScreen> createState() => _ProfileDetailedScreenState();
}

class _ProfileDetailedScreenState extends State<ProfileDetailedScreen> {
  @override
  Widget build(BuildContext context) {
    final profileId = ModalRoute.of(context)!.settings.arguments as int;
    final loadedProduct = Provider.of<ProfileProvider>(
      context,
    ).findById(profileId);

    return Scaffold(
      appBar: AppBar(title: Text(loadedProduct.name)),
      body: Column(
        children: [
          Image.network(loadedProduct.image),
          Text(
            loadedProduct.name,
            style: TextStyle(
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }
}

File Name: favorites.dart

Data will be shown in Grid View.

import 'package:Demo/providers/provider.dart';
import 'package:Demo/widgets/image_view_list.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Favorites extends StatelessWidget {
  const Favorites({super.key});

  @override
  Widget build(BuildContext context) {
    final productData = Provider.of<ProfileProvider>(context);
    final products = productData.Profiles;

    // fetching the data from the product file where we added the data
    return Scaffold(
        appBar: AppBar(title: Text('Favorites')),
        body: GridView.builder(
            itemCount: products.length,
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 2,
                mainAxisSpacing: 2,
                childAspectRatio: 3 / 2),
            itemBuilder: (context, i) => ImageView(
                products[i].id, products[i].image, products[i].name)));
  }
}

File Name: home_screen.dart

This will be our first screen that holds List and Grid View.

import 'package:Demo/providers/provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../../models/data_class.dart';
import '../../widgets/image_view_grid.dart';
import '../../widgets/image_view_list.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<Profile>? profiles;
  var isLoaded = false;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      Provider.of<ProfileProvider>(context, listen: false).getAllProfile();
    });
  }

  @override
  Widget build(BuildContext context) {
    final profileData = Provider.of<ProfileProvider>(context);
    final profiles = profileData.Profiles;
    double height() => MediaQuery.of(context).size.height;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample API'),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                const Text(
                  ' New Matches',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
                TextButton(
                  onPressed: () {},
                  child: const Text('see all'),
                )
              ],
            ),
            SingleChildScrollView(
              child: SizedBox(
                height: height() * 0.2,
                child: ListView.builder(
                    itemCount: profiles?.take(12).length,
                    shrinkWrap: true,
                    scrollDirection: Axis.horizontal,
                    itemBuilder: (context, index) {
                      return Container(
                        margin: const EdgeInsets.all(8),
                        child: ImageView(profiles![index].id,
                            profiles[index].image, profiles[index].name),
                      );
                    }),
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                const Text(' All Matches',
                    style: TextStyle(fontWeight: FontWeight.bold)),
                TextButton(
                  onPressed: () {},
                  child: const Text('see all'),
                )
              ],
            ),
            SingleChildScrollView(
              child: Container(
                margin: const EdgeInsets.all(8),
                height: height() * 0.6,
                child: GridView.builder(
                    itemCount: profiles.length,
                    gridDelegate:
                        const SliverGridDelegateWithFixedCrossAxisCount(
                            crossAxisCount: 2,
                            childAspectRatio: 1 / 1,
                            mainAxisSpacing: 10,
                            crossAxisSpacing: 10),
                    itemBuilder: (ctx, i) => ImageViewGrid(
                        profiles![i].id, profiles[i].image, profiles[i].name)),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

File Name: image_view_grid.dart

Widgets that are created to align the data in grid view.

import 'package:Demo/screens/profile_detailed_screen.dart';
import 'package:flutter/material.dart';

class ImageViewGrid extends StatelessWidget {
  final int? id;
  final String? imageUrl;
  final String? title;

  ImageViewGrid(this.id, this.imageUrl, this.title);

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(10),
      child: GridTile(
        footer: GridTileBar(
          backgroundColor: Colors.black87,
          leading: IconButton(
            icon: const Icon(Icons.favorite),
            color: Theme.of(context).accentColor,
            onPressed: () {},
          ),
          title: Text(
            title!,
            textAlign: TextAlign.center,
          ),
          subtitle: Text(title!),
          trailing: IconButton(
            icon: const Icon(
              Icons.call,
            ),
            onPressed: () {},
            color: Theme.of(context).accentColor,
          ),
        ),
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).pushNamed(
              ProfileDetailedScreen.routeName,
              arguments: id,
            );
          },
          child: Image.network(
            imageUrl!,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

File Name: image_view_list.dart

Widgets are created to align the data in the list view.

import 'package:flutter/material.dart';

import '../screens/profile_detailed_screen.dart';

class ImageView extends StatelessWidget {
  final String? imageUrl;
  final String? title;
  final int? id;

  ImageView(this.id, this.imageUrl, this.title);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        InkWell(
          onTap: (() {
            Navigator.of(context).pushNamed(
              ProfileDetailedScreen.routeName,
              arguments: id,
            );
          }),
          child: SizedBox(
            height: 250,
            width: 250,
            child: ClipRRect(
              borderRadius: BorderRadius.circular(10),
              child: Image.network(
                imageUrl!,
                fit: BoxFit.cover,
              ),
            ),
          ),
        ),
        Positioned(
            bottom: 0,
            child: Container(
              width: 250,
              height: 30,
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(5),
                  color: Colors.black54),
              alignment: Alignment.center,
              child: Text(
                title!,
                textAlign: TextAlign.center,
                style:
                    const TextStyle(color: Color.fromARGB(221, 255, 255, 255)),
              ),
            ))
      ],
    );
  }
}


Comments

Popular posts from this blog

Error Handling in Flutter - Gradle issue

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

Understanding API integration with Getx State management