Flutter is a powerful framework for building beautiful, high-performance cross-platform applications. In this tutorial, we'll walk through creating a simple CRUD (Create, Read, Update, Delete) app using GetX, a lightweight and reactive state management library. The app will manage users by fetching, displaying, and adding new users from/to an API.
Overview of the App
The app performs the following operations:
- Fetch Users (Read): Retrieve a list of users from an API and display them in a scrollable list.
- Create User: Add a new user through a form and post it to the API.
- Side Menu Navigation: A user-friendly side menu provides quick access to key app features.
We'll use JSONPlaceholder as the API for demonstration purposes.
Tech Stack
- Flutter: Framework for cross-platform app development.
- GetX: For state management, navigation, and dependency injection.
- HTTP: For API communication.
- JSONPlaceholder API: Mock API for testing.
Folder Structure
A clear folder structure ensures maintainability:
bash
CopyEdit
lib/
├── controllers/
│ └── user_controller.dart # Handles business logic and state
├── models/
│ └── user_model.dart # Represents the user data model
├── services/
│ └── api_service.dart # Manages API interactions
├── views/
│ ├── home_view.dart # Displays the list of users
│ ├── post_user_view.dart # Form for creating a new user
│ └── widgets/
│ └── side_menu.dart # Side menu for navigation
├── routes/
│ └── app_routes.dart # Centralized route management
└── main.dart # App entry point
Code Connections
1. API Integration (api_service.dart)
The ApiService class handles HTTP requests. It includes methods to fetch and create users.
dart
CopyEdit
class ApiService {
final String apiUrl = 'https://jsonplaceholder.typicode.com/users';
// Fetch users (GET)
Future<List<User>> fetchUsers() async {
final response = await http.get(Uri.parse(apiUrl));
if (response.statusCode == 200) {
return (json.decode(response.body) as List)
.map((data) => User.fromJson(data))
.toList();
} else {
throw Exception('Failed to load users');
}
}
// Create user (POST)
Future<User> createUser(Map<String, dynamic> userData) async {
final response = await http.post(
Uri.parse(apiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode(userData),
);
if (response.statusCode == 201) {
return User.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to create user');
}
}
}
How it connects:
- The
fetchUsers method retrieves a list of users from the API. - The
createUser method sends new user data to the API. - These methods are called in the
UserController to update the app's state.
2. User State Management (user_controller.dart)
The UserController connects the API with the UI. It uses GetX observables to manage state.
dart
CopyEdit
class UserController extends GetxController {
final ApiService apiService = ApiService();
var users = <User>[].obs;
var isLoading = true.obs;
@override
void onInit() {
super.onInit();
fetchUsers();
}
// Fetch users and update state
void fetchUsers() async {
try {
isLoading(true);
users.value = await apiService.fetchUsers();
} catch (e) {
Get.snackbar('Error', 'Failed to load users');
} finally {
isLoading(false);
}
}
// Create a new user
void createUser(Map<String, dynamic> userData) async {
try {
isLoading(true);
final newUser = await apiService.createUser(userData);
users.add(newUser);
Get.snackbar('Success', 'User created successfully');
} catch (e) {
Get.snackbar('Error', 'Failed to create user');
} finally {
isLoading(false);
}
}
}
How it connects:
- The
fetchUsers method populates the users observable, updating the UI dynamically. - The
createUser method adds a new user to the users list upon success. - Snackbar notifications provide instant feedback.
3. UI Components
Home Screen (home_view.dart): Displays the list of users and includes a floating action button for adding users.
dart
CopyEdit
class HomeView extends StatelessWidget {
final UserController controller = Get.put(UserController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('User Management')),
drawer: SideMenu(), // Include side menu
body: Obx(() {
if (controller.isLoading.value) {
return Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: controller.users.length,
itemBuilder: (context, index) {
final user = controller.users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
);
},
);
}),
floatingActionButton: FloatingActionButton(
onPressed: () {
Get.toNamed('/post-user'); // Navigate to user creation screen
},
child: Icon(Icons.add),
),
);
}
}
Create User Screen (post_user_view.dart): A form for creating new users.
dart
CopyEdit
class PostUserView extends StatelessWidget {
final UserController controller = Get.find();
final TextEditingController nameController = TextEditingController();
final TextEditingController emailController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Create User')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: nameController,
decoration: InputDecoration(labelText: 'Name'),
),
SizedBox(height: 16),
TextField(
controller: emailController,
decoration: InputDecoration(labelText: 'Email'),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
final userData = {
'name': nameController.text,
'email': emailController.text,
};
controller.createUser(userData);
},
child: Text('Submit'),
),
],
),
),
);
}
}
4. Navigation (app_routes.dart)
Centralized route management simplifies navigation.
dart
CopyEdit
class AppRoutes {
static const home = '/';
static const postUser = '/post-user';
static final routes = [
GetPage(name: home, page: () => HomeView()),
GetPage(name: postUser, page: () => PostUserView()),
];
}
How it connects:
- Routes are defined once and used consistently across the app.
How It All Connects
- UserController:
- Fetches data from
ApiService and updates the users observable. - Adds new users to the list when
createUser is called. - API Service:
- Handles all HTTP operations, isolating networking logic.
- Views:
- Dynamically update based on changes to
users in the controller. - Use GetX's
Obx for reactive state updates. - Navigation:
- Simplifies screen transitions using GetX routes.
Conclusion
This CRUD app demonstrates how to use Flutter and GetX to build a scalable, responsive application. The modular approach separates concerns, making the app easy to maintain and extend.
What features would you add to improve this app? Let me know in the comments! 🚀
Comments 0