Secure some operation of CRUD in Flutter
To enable viewing CRUD data without login but restrict actions like creating, editing, and deleting to only logged-in users, you need to manage the visibility and accessibility of these actions based on the authentication state. Here's the complete implementation:
Step-by-Step Guide
Setup Dependencies: Use http for REST API calls and provider for state management.
Authentication Provider: Manage authentication state.
CRUD Operations: Implement fetching, creating, updating, and deleting data.
UI Logic: Enable or disable actions based on authentication state.
Step 1: Setup Dependencies
Add the following dependencies to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
provider: ^5.0.0
http: ^0.14.0
Run flutter pub get to install the packages.
Step 2: Authentication Provider
Create a class to manage the authentication state.
Create the Authentication Service
Create a file named auth_service.dart in your services directory (create one if it doesn't exist).
// services/auth_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class AuthService {
final String baseUrl = 'https://yourapi.com'; // Replace with your API base URL
Future<Map<String, dynamic>> login(String username, String password) async {
final url = Uri.parse('$baseUrl/login'); // Replace with your API login endpoint
try {
final response = await http.post(
url,
headers: {
'Content-Type': 'application/json',
},
body: json.encode({
'username': username,
'password': password,
}),
);
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to authenticate. Please check your credentials.');
}
} catch (error) {
throw Exception('Failed to authenticate. Please try again later.');
}
}
}
- Update the AuthProvider to Use the AuthService Modify your AuthProvider to use the AuthService for the login functionality.
// providers/auth_provider.dart
import 'package:flutter/material.dart';
import '../services/auth_service.dart';
class AuthProvider with ChangeNotifier {
final AuthService _authService = AuthService();
bool _isAuthenticated = false;
String _authToken = '';
bool get isAuthenticated => _isAuthenticated;
Future<void> login(String username, String password) async {
try {
final responseData = await _authService.login(username, password);
_authToken = responseData['token'];
_isAuthenticated = true;
notifyListeners();
} catch (error) {
_isAuthenticated = false;
throw error;
}
}
void logout() {
_authToken = '';
_isAuthenticated = false;
notifyListeners();
}
}
Step 3: CRUD Operations
Create a service class to handle API calls.
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
final String baseUrl = 'https://jsonplaceholder.typicode.com';
Future<List<dynamic>> fetchData() async {
final response = await http.get(Uri.parse('$baseUrl/posts'));
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to load data');
}
}
Future<void> createData(Map<String, dynamic> data) async {
final response = await http.post(
Uri.parse('$baseUrl/posts'),
headers: {"Content-Type": "application/json"},
body: json.encode(data),
);
if (response.statusCode != 201) {
throw Exception('Failed to create data');
}
}
Future<void> updateData(int id, Map<String, dynamic> data) async {
final response = await http.put(
Uri.parse('$baseUrl/posts/$id'),
headers: {"Content-Type": "application/json"},
body: json.encode(data),
);
if (response.statusCode != 200) {
throw Exception('Failed to update data');
}
}
Future<void> deleteData(int id) async {
final response = await http.delete(Uri.parse('$baseUrl/posts/$id'));
if (response.statusCode != 200) {
throw Exception('Failed to delete data');
}
}
}
Step 4: Main Entry Point and Routing
Set up the main entry point and define routes.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'auth_provider.dart';
import 'crud_screen.dart';
import 'login_screen.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => AuthProvider(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter CRUD App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Consumer<AuthProvider>(
builder: (context, auth, child) {
return auth.isAuthenticated ? CrudScreen() : CrudScreen(); // Always show CrudScreen, but control actions
},
),
routes: {
'/crud': (context) => CrudScreen(),
'/login': (context) => LoginScreen(),
},
);
}
}
Step 5: Login Screen
Create a login screen.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'auth_provider.dart';
class LoginScreen extends StatelessWidget {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
),
TextField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
String username = _usernameController.text;
String password = _passwordController.text;
Provider.of<AuthProvider>(context, listen: false).login(username, password);
},
child: Text('Login'),
),
],
),
),
);
}
}
Step 6: CRUD Screen
Create a screen to display and manage CRUD operations.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'auth_provider.dart';
import 'api_service.dart';
class CrudScreen extends StatefulWidget {
@override
_CrudScreenState createState() => _CrudScreenState();
}
class _CrudScreenState extends State<CrudScreen> {
final ApiService apiService = ApiService();
late Future<List<dynamic>> _data;
@override
void initState() {
super.initState();
_data = apiService.fetchData();
}
void _showCreateDialog() {
showDialog(
context: context,
builder: (context) {
return _DataDialog(
onSubmit: (data) async {
await apiService.createData(data);
setState(() {
_data = apiService.fetchData();
});
},
);
},
);
}
void _showEditDialog(int id, Map<String, dynamic> data) {
showDialog(
context: context,
builder: (context) {
return _DataDialog(
initialData: data,
onSubmit: (updatedData) async {
await apiService.updateData(id, updatedData);
setState(() {
_data = apiService.fetchData();
});
},
);
},
);
}
@override
Widget build(BuildContext context) {
bool isAuthenticated = Provider.of<AuthProvider>(context).isAuthenticated;
return Scaffold(
appBar: AppBar(
title: Text('CRUD Operations'),
actions: <Widget>[
isAuthenticated
? IconButton(
icon: Icon(Icons.logout),
onPressed: () {
Provider.of<AuthProvider>(context, listen: false).logout();
Navigator.pushReplacementNamed(context, '/login');
},
)
: IconButton(
icon: Icon(Icons.login),
onPressed: () {
Navigator.pushNamed(context, '/login');
},
),
],
),
body: FutureBuilder<List<dynamic>>(
future: _data,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No data available'));
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
var item = snapshot.data![index];
return ListTile(
title: Text(item['title']),
subtitle: Text(item['body']),
trailing: isAuthenticated
? Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () => _showEditDialog(item['id'], item),
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () async {
await apiService.deleteData(item['id']);
setState(() {
_data = apiService.fetchData();
});
},
),
],
)
: null,
);
},
);
}
},
),
floatingActionButton: isAuthenticated
? FloatingActionButton(
onPressed: _showCreateDialog,
child: Icon(Icons.add),
)
: null,
);
}
}
class _DataDialog extends StatefulWidget {
final Map<String, dynamic>? initialData;
final Function(Map<String, dynamic>) onSubmit;
_DataDialog({this.initialData, required this.onSubmit});
@override
__DataDialogState createState() => __DataDialogState();
}
class __DataDialogState extends State<_DataDialog> {
final TextEditingController _titleController = TextEditingController();
final TextEditingController _bodyController = TextEditingController();
@override
void initState() {
super.initState();
if (widget.initialData != null) {
_titleController.text = widget.initialData!['title'];
_bodyController.text = widget.initialData!['body'];
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(widget.initialData == null ? 'Create Data' : 'Edit Data'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
controller: _titleController,
decoration: InputDecoration(labelText: 'Title'),
),
TextField(
controller: _bodyController,
decoration: InputDecoration(labelText: 'Body'),
),
],
),
actions: <Widget>[
ElevatedButton(
onPressed: () {
final data = {
'title': _titleController.text,
'body': _bodyController.text,
};
widget.onSubmit(data);
Navigator.of(context).pop();
},
child: Text('Submit'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Cancel'),
),
],
);
}
}
Secure All operation of CRUD in Flutter
To implement an application where CRUD operations (Create, Read, Update, Delete) are displayed using a RESTful API but actions like create, edit, and delete are only enabled after login in Flutter, you can follow these steps:
Setup Dependencies: Use http for REST API calls and provider for state management.
Authentication Provider: Manage authentication state.
CRUD Operations: Implement fetching, creating, updating, and deleting data.
UI Logic: Enable or disable actions based on authentication state.
Here's a complete example:
Step 1: Setup Dependencies
Add the following dependencies to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
provider: ^5.0.0
http: ^0.14.0
Run flutter pub get to install the packages.
Step 2: Authentication Provider
Create a class to manage the authentication state.
import 'package:flutter/material.dart';
class AuthProvider with ChangeNotifier {
bool _isAuthenticated = false;
bool get isAuthenticated => _isAuthenticated;
void login(String username, String password) {
// Implement your authentication logic here.
// For demo purposes, we just set it to true.
_isAuthenticated = true;
notifyListeners();
}
void logout() {
_isAuthenticated = false;
notifyListeners();
}
}
Step 3: CRUD Operations
Create a service class to handle API calls.
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
final String baseUrl = 'https://jsonplaceholder.typicode.com';
Future<List<dynamic>> fetchData() async {
final response = await http.get(Uri.parse('$baseUrl/posts'));
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to load data');
}
}
Future<void> createData(Map<String, dynamic> data) async {
final response = await http.post(
Uri.parse('$baseUrl/posts'),
headers: {"Content-Type": "application/json"},
body: json.encode(data),
);
if (response.statusCode != 201) {
throw Exception('Failed to create data');
}
}
Future<void> updateData(int id, Map<String, dynamic> data) async {
final response = await http.put(
Uri.parse('$baseUrl/posts/$id'),
headers: {"Content-Type": "application/json"},
body: json.encode(data),
);
if (response.statusCode != 200) {
throw Exception('Failed to update data');
}
}
Future<void> deleteData(int id) async {
final response = await http.delete(Uri.parse('$baseUrl/posts/$id'));
if (response.statusCode != 200) {
throw Exception('Failed to delete data');
}
}
}
Step 4: Main Entry Point and Routing
Set up the main entry point and define routes.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'auth_provider.dart';
import 'crud_screen.dart';
import 'login_screen.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => AuthProvider(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter CRUD App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Consumer<AuthProvider>(
builder: (context, auth, child) {
return auth.isAuthenticated ? CrudScreen() : LoginScreen();
},
),
routes: {
'/crud': (context) => CrudScreen(),
'/login': (context) => LoginScreen(),
},
);
}
}
Step 5: Login Screen
Create a login screen.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'auth_provider.dart';
class LoginScreen extends StatelessWidget {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
),
TextField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
String username = _usernameController.text;
String password = _passwordController.text;
Provider.of<AuthProvider>(context, listen: false).login(username, password);
},
child: Text('Login'),
),
],
),
),
);
}
}
Step 6: CRUD Screen
Create a screen to display and manage CRUD operations.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'auth_provider.dart';
import 'api_service.dart';
class CrudScreen extends StatefulWidget {
@override
_CrudScreenState createState() => _CrudScreenState();
}
class _CrudScreenState extends State<CrudScreen> {
final ApiService apiService = ApiService();
late Future<List<dynamic>> _data;
@override
void initState() {
super.initState();
_data = apiService.fetchData();
}
void _showCreateDialog() {
showDialog(
context: context,
builder: (context) {
return _DataDialog(
onSubmit: (data) async {
await apiService.createData(data);
setState(() {
_data = apiService.fetchData();
});
},
);
},
);
}
void _showEditDialog(int id, Map<String, dynamic> data) {
showDialog(
context: context,
builder: (context) {
return _DataDialog(
initialData: data,
onSubmit: (updatedData) async {
await apiService.updateData(id, updatedData);
setState(() {
_data = apiService.fetchData();
});
},
);
},
);
}
@override
Widget build(BuildContext context) {
bool isAuthenticated = Provider.of<AuthProvider>(context).isAuthenticated;
return Scaffold(
appBar: AppBar(
title: Text('CRUD Operations'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.logout),
onPressed: () {
Provider.of<AuthProvider>(context, listen: false).logout();
Navigator.pushReplacementNamed(context, '/login');
},
),
],
),
body: FutureBuilder<List<dynamic>>(
future: _data,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No data available'));
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
var item = snapshot.data![index];
return ListTile(
title: Text(item['title']),
subtitle: Text(item['body']),
trailing: isAuthenticated
? Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () => _showEditDialog(item['id'], item),
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () async {
await apiService.deleteData(item['id']);
setState(() {
_data = apiService.fetchData();
});
},
),
],
)
: null,
);
},
);
}
},
),
floatingActionButton: isAuthenticated
? FloatingActionButton(
onPressed: _showCreateDialog,
child: Icon(Icons.add),
)
: null,
);
}
}
class _DataDialog extends StatefulWidget {
final Map<String, dynamic>? initialData;
final Function(Map<String, dynamic>) onSubmit;
_DataDialog({this.initialData, required this.onSubmit});
@override
__DataDialogState createState() => __DataDialogState();
}
class __DataDialogState extends State<_DataDialog> {
final TextEditingController _titleController = TextEditingController();
final TextEditingController _bodyController = TextEditingController();
@override
void initState() {
super.initState();
if (widget.initialData != null) {
_titleController.text = widget.initialData!['title'];
_bodyController.text = widget.initialData!['body'];
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(widget.initialData == null ? 'Create Data' : 'Edit Data'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
controller: _titleController,
decoration: InputDecoration(labelText: 'Title'),
),
Top comments (0)