Implementing Secure Login Functionality in Flutter Using the MVC Pattern
MVC stands for Model-View-Controller, a popular software design pattern used to create scalable and maintainable applications. Flutter, being a popular framework for building mobile applications, also follows the MVC architecture.
Here's a brief overview of how the MVC architecture works in Flutter:
- Model: The model represents the data and business logic of the application. In Flutter, you can create models using Dart classes that define the structure and behaviour of your data.
- View: The view is responsible for rendering the user interface of the application. In Flutter, you can create views using widgets, which are building blocks that compose the UI.
- Controller: The controller is the intermediary between the model and the view. It handles user input, updates the model accordingly, and triggers UI updates in the view. In Flutter, you can create controllers using stateful widgets, which are widgets that can maintain their own state and update themselves when needed.
In Flutter, the MVC architecture is often implemented using the StatefulWidget class. The StatefulWidget class allows you to define a widget that has a mutable state, which can be used to represent the model of the application. The StatefulWidget class also allows you to define a separate widget to represent the view of the application, and a separate class to represent the controller.
Here's an example of how you might implement the MVC architecture in Flutter:
You can test this Login page with this Username and Password:
Username: eve.holt@reqres.in
Password: cityslicka
Now, let's see how we can split the code:
Model:- user.dart
In this code, the data that needs to be stored in the Model is the user's email and password.
class User {
final String email;
final String password;
User({required this.email, required this.password});
}
Controller:- sign_in.dart
The signIn function handles user input and updates the Model and View accordingly. It can be considered as the controller.
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../modal/user.dart';
class LoginController {
Future<String?> signIn(User user, String text) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
Map data = {'email': user.email, 'password': user.password};
var jsonResponse = null;
var response =
await http.post(Uri.parse('https://reqres.in/api/login'), body: data);
if (response.statusCode == 200) {
jsonResponse = json.decode(response.body);
if (jsonResponse != null) {
sharedPreferences.setString("token", jsonResponse['token']);
return jsonResponse['token'];
}
} else {
print(response.body);
return null;
}
return null;
}
}
View:- login.dart
The LoginPage class represents the view. It is responsible for displaying the user interface to the user.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../main.dart';
import '../modal/user.dart';
import '../services/sign_in.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final LoginController _loginController = LoginController();
bool _isLoading = false;
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
void _signIn() async {
setState(() {
_isLoading = true;
});
final user =
User(email: _emailController.text, password: _passwordController.text);
final token = await _loginController.signIn(user, 'text');
if (token != null) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const MainPage()),
(Route<dynamic> route) => false,
);
} else {
setState(() {
_isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Invalid email or password')),
);
}
}
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light.copyWith(
statusBarColor: Colors.transparent,
));
return Scaffold(
body: _isLoading
? const Center(child: CircularProgressIndicator())
: Column(
children: [
Container(
margin: const EdgeInsets.only(top: 50.0),
padding: const EdgeInsets.symmetric(
horizontal: 20.0, vertical: 30.0),
child: Image.network(
'https://blogger.googleusercontent.com/img/a/AVvXsEhdOsFuVngageLIphCbJ2cXqgdfhifZMKj3l01OlrtPkN2g-UMW0yOEnWGr_p8bVZ7iMAqIFpmQ3zLH3XxyGq4_LxUFVsvaznnw4fBqP81B6MnmV2BFU0HK-ypuHxvGkrFdGjprTyW22c6ApXwg9ByNFdVjMyh9PHYSpf7SL27xS4K52br8R4LhV2h2rA=s1600'),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 15.0, vertical: 20.0),
child: Column(
children: <Widget>[
TextFormField(
controller: _emailController,
cursorColor: Colors.black,
style: const TextStyle(color: Colors.black54),
decoration: const InputDecoration(
icon: Icon(Icons.email, color: Colors.black54),
hintText: "Email",
border: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.black54),
),
hintStyle: TextStyle(color: Colors.black54),
),
),
const SizedBox(height: 30.0),
TextFormField(
controller: _passwordController,
cursorColor: Colors.black,
obscureText: true,
style: const TextStyle(color: Colors.black54),
decoration: const InputDecoration(
icon: Icon(Icons.lock, color: Colors.black54),
hintText: "Password",
border: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.black54),
),
hintStyle: TextStyle(color: Colors.black54),
),
),
],
),
),
Container(
width: MediaQuery.of(context).size.width,
height: 40.0,
padding: const EdgeInsets.symmetric(horizontal: 15.0),
margin: const EdgeInsets.only(top: 15.0),
child: ElevatedButton(
onPressed: _emailController.text == '' ||
_passwordController.text == ''
? null
: _signIn,
child: const Text(
'Sign In',
style: TextStyle(color: Colors.black54),
),
),
),
],
),
);
}
}
So, to split this code into MVC architecture, we need to create a separate file for the Model and a separate file for the Controller.
Main:- main.dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:test_a_s/view/login.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Login Page",
debugShowCheckedModeBanner: false,
home: const MainPage(),
theme: ThemeData(primarySwatch: Colors.orange),
);
}
}
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
// ignore: library_private_types_in_public_api
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
late SharedPreferences sharedPreferences;
@override
void initState() {
super.initState();
checkLoginStatus();
}
checkLoginStatus() async {
sharedPreferences = await SharedPreferences.getInstance();
if (sharedPreferences.getString("token") == null) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) => LoginPage()),
(Route<dynamic> route) => false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Logged in Screen",
style: TextStyle(color: Colors.white)),
actions: <Widget>[
ElevatedButton(
onPressed: () {
sharedPreferences.clear();
// ignore: deprecated_member_use
sharedPreferences.commit();
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) => LoginPage()),
(Route<dynamic> route) => false);
},
child: const Text("Log Out", style: TextStyle(color: Colors.white)),
),
],
),
body: const Center(child: Text("Logged In Screen")),
drawer: const Drawer(),
);
}
}
Comments
Post a Comment