Dio API Integration - Login API with Provider and MVVM in Flutter
Learn how to integrate Dio (HTTP client) with a Login API in Flutter using the Provider package and MVVM architecture.
Follow step-by-step tutorials and best practices to efficiently manage API calls and user authentication.
Analyze log output and explore sample code for a seamless login experience using Dio, Provider, and MVVM.
Explore sample code, step-by-step tutorial, and best practices for seamless API integration.
dependencies:
flutter:
sdk: flutter
dio: ^5.3.0
provider: ^6.0.5
main.dart : The main entry point of the Flutter app.
// main.dart
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutteryfly/viewmodels/login_view_model.dart';
import 'package:flutteryfly/views/login_page.dart';
import 'package:provider/provider.dart';
import './data/services/api_service.dart';
import 'data/repositories/login_repository.dart';
void main() {
// Create Dio instance for HTTP requests
final Dio dio = Dio();
// Create ApiService instance with the Dio instance
final ApiService apiService = ApiService(dio: dio);
// Create LoginRepository instance with the ApiService instance
final LoginRepository loginRepository = LoginRepository(apiService: apiService);
// Provider
runApp(
MultiProvider(
providers: [
// Provide the LoginViewModel with LoginViewModel dependency to manage user data and API calls
ChangeNotifierProvider<LoginViewModel>(
create: (context) => LoginViewModel(userRepository: loginRepository),
),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login', // Meta Title for the App
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: LoginPage(),
);
}
}
..
login_page.dart :To build a login page.
// views/login_page.dart
import 'package:flutter/material.dart';
import 'package:flutteryfly/views/widgets/common_text_field.dart';
import 'package:provider/provider.dart';
import '../viewmodels/login_view_model.dart';
class LoginPage extends StatelessWidget {
LoginPage({Key? key}) : super(key: key);
// Declare the TextEditingControllers at the class level
final TextEditingController name = TextEditingController();
final TextEditingController password = TextEditingController();
@override
Widget build(BuildContext context) {
final viewModel = Provider.of<LoginViewModel>(context);
// Set the initial value for the username text field
name.text = 'eve.holt@reqres.in';
return Scaffold(
appBar: AppBar(
title: const Text('Login'),
),
body: Padding(
padding: const EdgeInsets.all(30.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Show CircularProgressIndicator while loading
if (viewModel.isLoading) const CircularProgressIndicator(),
// Show error message if login fails
if (!viewModel.isLoading && viewModel.loginError != null)
Text(
viewModel.loginError!,
style: const TextStyle(color: Colors.red),
),
// Show response message if login is successful
if (viewModel.response != null)
Text(
viewModel.response!.toString(),
style: const TextStyle(color: Colors.green),
),
const SizedBox(height: 16),
// Username text field
CommonTextField(
controller: name,
hintText: 'Enter your username',
keyboardType: TextInputType.text,
onChanged: (value) {
// Handle username changes
},
),
Container(height: 40),
// Password text field
CommonTextField(
controller: password,
obscureText: true,
hintText: 'Enter your password',
keyboardType: TextInputType.text,
onChanged: (value) {
// Handle password changes
},
),
Container(height: 40),
// Login button
ElevatedButton(
onPressed: () {
// Construct login request with the entered username and password
Map<dynamic, dynamic> req = {
"email": name.text,
"password": password.text,
};
// Call the login method in the view model with the request
viewModel.login(req);
},
child: const Text('Login'),
),
],
),
),
),
);
}
}
..
login_view_model.dart : ViewModel class for managing user data and API calls.
// view_models/login_view_model.dart
import 'package:flutter/material.dart';
import '../data/models/login_response.dart';
import '../data/repositories/login_repository.dart';
// Create the LoginViewModel class and extend it with ChangeNotifier to enable state management.
class LoginViewModel with ChangeNotifier {
// Instance of the LoginRepository class to interact with the user login data.
final LoginRepository userRepository;
// Constructor to initialize the LoginViewModel with the LoginRepository instance.
LoginViewModel({required this.userRepository});
// Private variables to store response and error data from the login process.
String? _response; // Response from the login API call.
String? get response => _response; // Getter to access the login response.
String? _loginError; // Error message in case the login process fails.
bool _isLoading = false; // A flag to track the loading state during the login process.
// Getters to access the error message and loading state.
String? get loginError => _loginError;
bool get isLoading => _isLoading;
// Method to handle the login process asynchronously.
Future<void> login(Map<dynamic, dynamic> req) async {
// Set the isLoading flag to true to indicate that the login process is ongoing.
_isLoading = true;
// Notify the listeners (usually UI elements) that the state has changed.
notifyListeners();
try {
// Call the login method from the userRepository to initiate the login process.
_response = await userRepository.login(req);
// If the login is successful, set the loginError to null.
_loginError = null;
} catch (e) {
// If an error occurs during the login process, catch the error and set the loginError with the error message.
_loginError = e.toString();
}
// Set the isLoading flag back to false to indicate that the login process has completed.
_isLoading = false;
// Notify the listeners that the state has changed after completing the login process.
notifyListeners();
}
}
..
login_repository.dart : Handles fetching user data from the API.
// data/repositories/login_repository.dart
import '../services/api_service.dart';
class LoginRepository {
final ApiService
apiService; // Instance of the ApiService class to perform API requests.
LoginRepository({required this.apiService});
Future<String?> login(Map<dynamic, dynamic> req) async {
try {
// Attempt to make the API call to login the user using the apiService.
final response = await apiService.loginUser(req);
// Store the response from the API call in the 'response' variable.
return response;
// If the API call is successful, return the response (String) to the caller.
} catch (e) {
// If an exception occurs during the API call, catch it and handle the error.
throw Exception('Failed to login');
// Throw a new Exception with the message 'Failed to login', indicating that the login process failed.
// The caller of this function can catch this exception and handle the error appropriately.
}
}
}
..
api_service.dart : Provides methods to interact with the API using Dio or other HTTP clients.
// data/services/api_service.dart
import 'package:dio/dio.dart';
import '../../utils/logger_interceptor.dart';
class ApiService {
late Dio _dio; // Dio instance to perform HTTP requests.
// ApiService({required Dio dio}) : _dio = dio;
ApiService({required Dio dio}) {
_dio = Dio(BaseOptions(
//baseUrl: "https://dummyjson.com/products/",
// connectTimeout: const Duration(seconds:5),
// receiveTimeout: const Duration(seconds: 3),
responseType: ResponseType.json,
))
..interceptors.addAll([
//LoggerInterceptor(), //custom logger interceptor.
]);
}
Future<String?> loginUser(Map<dynamic, dynamic> req) async {
try {
final response =
await _dio.post('https://reqres.in/api/login', data: req);
if (response.statusCode == 200) {
// Success
//return response.data['token'];
// Check if 'token' key exists in the response data
if (response.data.containsKey('token')) {
return response.data['token'];
}
// If 'error' key doesn't exist, return a generic error message
return 'An error occurred';
} else {
// Handle other status codes as needed
return 'Failed to login';
}
} catch (e) {
if (e is DioException) {
if (e.response != null &&
e.response!.data != null &&
e.response!.data['error'] != null) {
final errorMessage = e.response!.data['error'];
print('Error Message: $errorMessage');
return errorMessage;
}
}
// Handle other errors or exceptions
print('Error: $e');
return null;
}
}
}
..
..
https://reqres.in/api/login
Request:
{
"email": "eve.holt@reqres.in",
"password": "cityslicka"
}
success: 201 response
{
"error": "user not found"
}
failure: 404 response
{
"token": "QpwL5tke4Pnpja7X4"
}
// view/widget/common_text_field.dart
import 'package:flutter/material.dart';
class CommonTextField extends StatelessWidget {
final TextEditingController controller;
final String? hintText;
final TextInputType? keyboardType;
final bool obscureText;
final Function(String)? onChanged;
const CommonTextField({
Key? key,
required this.controller,
this.hintText,
this.keyboardType,
this.obscureText = false,
this.onChanged,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return TextField(
controller: controller,
keyboardType: keyboardType,
obscureText: obscureText,
onChanged: onChanged,
decoration: InputDecoration(
hintText: hintText,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: theme.primaryColor, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: theme.colorScheme.secondary, width: 2),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: theme.disabledColor, width: 1),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.red, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.red, width: 2),
),
// You can add more customization to the decoration as needed
// For example, adding icons, labels, etc.
),
);
}
}
Comments
Post a Comment