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
Post a Comment